archey4/archey/entries/lan_ip.py

98 lines
3.5 KiB
Python

"""Local IP addresses detection class"""
import ipaddress
from itertools import islice
from typing import Iterator
try:
import netifaces
except ImportError:
netifaces = None
from archey.entry import Entry
class LanIP(Entry):
"""Relies on the `netifaces` module to detect LAN IP addresses"""
_ICON = "\U000f0a60" # md_ip_network
_PRETTY_NAME = "LAN IP"
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if not netifaces:
self._logger.warning(
"`netifaces` Python module couldn't be found. "
"Please either install it or explicitly disable `LAN_IP` entry in configuration."
)
return
# IPv4 will be enabled by default.
addr_families = [netifaces.AF_INET]
if self.options.get("ipv6_support", True):
addr_families.append(netifaces.AF_INET6)
max_count = self.options.get("max_count", 2)
# Consistency with other entries' configuration: Infinite count if false.
if max_count is False:
max_count = None
# Global IP addresses (in RFC1918 terms) will be hidden by default.
show_global = bool(self.options.get("show_global"))
# Link-local IP addresses (in RFC3927 terms) will be shown by default.
show_link_local = bool(self.options.get("show_link_local", True))
self.value = list(
islice(
self._lan_ip_addresses_generator(addr_families, show_global, show_link_local),
max_count,
)
)
@staticmethod
def _lan_ip_addresses_generator(
addr_families: list, show_global: bool, show_link_local: bool
) -> Iterator[str]:
"""Generator yielding local IP address according to passed address families"""
# Loop through all available network interfaces.
for if_name in netifaces.interfaces():
# Fetch associated addresses elements.
if_addrs = netifaces.ifaddresses(if_name)
for addr_family in addr_families:
for if_addr in if_addrs.get(addr_family, []):
# IPv6 addresses may contain '%' token separator.
ip_addr = ipaddress.ip_address(if_addr["addr"].split("%")[0])
# Filter out loopback and public/link-local IP addresses (if enabled).
if (
not ip_addr.is_loopback
and (not ip_addr.is_global or show_global)
and (not ip_addr.is_link_local or show_link_local)
):
# Finally, yield the address compressed representation.
yield ip_addr.compressed
def output(self, output) -> None:
"""Adds the entry to `output` after pretty-formatting the IP address list."""
# If we found IP addresses, join them together nicely.
# If not, fall back on default strings according to `netifaces` availability.
if self.value:
if not self.options.get("one_line", True):
# One-line output has been disabled, add one IP address per item.
for ip_address in self.value:
output.append(self.name, ip_address)
return
text_output = ", ".join(self.value)
elif netifaces:
text_output = self._default_strings.get("no_address")
else:
text_output = self._default_strings.get("not_detected")
output.append(self.name, text_output)