#!/usr/bin/env python3 import os import subprocess import argparse import requests def get_physical_interfaces(): """ Retrieve a list of physical network interfaces on the system. This function checks the `/sys/class/net/` directory to identify physical network interfaces. It determines if an interface is physical by verifying the presence of a symbolic link to a `device` directory. Returns: list: A list of strings, where each string is the name of a physical network interface. """ interfaces_path = '/sys/class/net/' physical_interfaces = [] for interface in os.listdir(interfaces_path): if not os.path.islink(os.path.join(interfaces_path, interface, 'device')): continue physical_interfaces.append(interface) return physical_interfaces def get_virtual_interfaces(): """ Retrieves a list of virtual network interfaces on the system. This function scans the network interfaces available in the '/sys/class/net/' directory and filters out physical interfaces and the loopback interface ('lo'). It identifies virtual interfaces by checking if the 'device' path is not a symbolic link. Returns: list: A list of virtual network interface names as strings. """ interfaces_path = '/sys/class/net/' virtual_interfaces = [] for interface in os.listdir(interfaces_path): if os.path.islink(os.path.join(interfaces_path, interface, 'device')): continue if interface == 'lo': continue virtual_interfaces.append(interface) return virtual_interfaces def get_up_interfaces(interfaces): """ Filters the given list of interfaces to include only those that are up or unknown. Args: interfaces (list): A list of interface names. Returns: list: A list of interface names that are up or treated as up (e.g., UNKNOWN). """ up_interfaces = [] for interface in interfaces: try: result = subprocess.run(['ip', 'link', 'show', interface], capture_output=True, text=True, check=True) if "state UP" in result.stdout or "state UNKNOWN" in result.stdout: up_interfaces.append(interface) except Exception: continue return up_interfaces def get_interface_state(interface): """ Retrieve the state and MAC address of a network interface. Args: interface (str): The name of the network interface. Returns: tuple: A tuple containing the state (str) and MAC address (str) of the interface. """ try: result = subprocess.run(['ip', 'link', 'show', interface], capture_output=True, text=True, check=True) lines = result.stdout.splitlines() state = "UNKNOWN" mac = "N/A" if len(lines) > 0: if "state UP" in lines[0]: state = "UP" elif "state DOWN" in lines[0]: state = "DOWN" elif "state UNKNOWN" in lines[0]: state = "UP" # Treat UNKNOWN as UP if len(lines) > 1: mac = lines[1].strip().split()[1] if len(lines[1].strip().split()) > 1 else "N/A" return state, mac except Exception: return "UNKNOWN", "N/A" def get_external_ips(): """ Fetch both IPv4 and IPv6 external IP addresses of the machine. This function first attempts to retrieve an IP address using the services `https://ifconfig.co`, `https://ifconfig.io`, and `https://ifconfig.me`. If the first IP fetched is IPv6, it explicitly tries to fetch an IPv4 address using curl's `-4` option. Returns: tuple: A tuple containing the IPv4 and IPv6 addresses as strings. If either address cannot be fetched, it will be set to "Unavailable". """ services = ["https://ifconfig.co", "https://api.ipify.org", "https://myexternalip.com/raw", "https://ifconfig.io", "https://ifconfig.me"] headers = {"User-Agent": "curl"} ipv4, ipv6 = "Unavailable", "Unavailable" for service in services: try: response = requests.get(service, headers=headers, timeout=0.2) if response.status_code == 200: ip = response.text.strip() if ":" in ip: # IPv6 address ipv6 = ip # Try to fetch IPv4 explicitly ipv4_response = subprocess.run( ["curl", "-4", "--silent", service], capture_output=True, text=True, timeout=0.2, check=True ) if ipv4_response.returncode == 0: ipv4 = ipv4_response.stdout.strip() else: # IPv4 address ipv4 = ip if ipv4 != "Unavailable" and ipv6 != "Unavailable": break except (requests.RequestException, subprocess.TimeoutExpired): continue return ipv4, ipv6 def display_interface_details(show_physical=False, show_virtual=False, show_all=False, show_external_ip=False, show_ipv6=False): """ Display details of network interfaces based on the specified flags. Args: show_physical (bool): Show physical interfaces (UP by default unless combined with show_all). show_virtual (bool): Show virtual interfaces (UP by default unless combined with show_all). show_all (bool): Include all interfaces (UP, DOWN, UNKNOWN). show_external_ip (bool): Fetch and display the external IP address. show_ipv6 (bool): Include IPv6 addresses in the output. Notes: - By default, only IPv4 addresses are shown unless `-6` is specified. - IPv6 addresses are displayed in a separate column if `-6` is specified. """ if show_external_ip: ipv4, ipv6 = get_external_ips() print(f"External IPv4: {ipv4}") print(f"External IPv6: {ipv6}") print("-" * 70) interfaces = [] if show_all: if show_physical or not show_virtual: # Default to physical if no `-v` interfaces.extend(get_physical_interfaces()) if show_virtual: interfaces.extend(get_virtual_interfaces()) else: if show_physical or not show_virtual: # Default to physical if no `-v` interfaces.extend(get_up_interfaces(get_physical_interfaces())) if show_virtual or not show_physical: # Default to virtual if no `-p` interfaces.extend(get_up_interfaces(get_virtual_interfaces())) interfaces.sort() # Define column widths based on expected maximum content length col_widths = { 'interface': 15, 'ipv4': 18, 'ipv6': 40 if show_ipv6 else 0, # Hide IPv6 column if not showing IPv6 'subnet': 10, 'state': 10, 'mac': 18 } # Print header with proper formatting header = f"{'Interface':<{col_widths['interface']}} {'IPv4 Address':<{col_widths['ipv4']}}" if show_ipv6: header += f" {'IPv6 Address':<{col_widths['ipv6']}}" header += f" {'Subnet':<{col_widths['subnet']}} {'State':<{col_widths['state']}} {'MAC Address':<{col_widths['mac']}}" print(header) print("-" * (col_widths['interface'] + col_widths['ipv4'] + (col_widths['ipv6'] if show_ipv6 else 0) + col_widths['subnet'] + col_widths['state'] + col_widths['mac'])) for interface in interfaces: try: result = subprocess.run(['ip', '-br', 'addr', 'show', interface], capture_output=True, text=True, check=True) state, mac = get_interface_state(interface) if result.returncode == 0: lines = result.stdout.strip().splitlines() ipv4 = "N/A" ipv6 = "N/A" subnet = "" for line in lines: parts = line.split() if len(parts) >= 3: ip_with_mask = parts[2] # Check if the address is IPv4 or IPv6 if ":" in ip_with_mask: # IPv6 ipv6 = ip_with_mask.split('/')[0] else: # IPv4 ipv4 = ip_with_mask.split('/')[0] subnet = ip_with_mask.split('/')[1] if '/' in ip_with_mask else "" row = f"{interface:<{col_widths['interface']}} {ipv4:<{col_widths['ipv4']}}" if show_ipv6: row += f" {ipv6:<{col_widths['ipv6']}}" row += f" {subnet:<{col_widths['subnet']}} {state:<{col_widths['state']}} {mac:<{col_widths['mac']}}" print(row) except Exception as e: print(f"Error fetching details for {interface}: {e}") if __name__ == "__main__": parser = argparse.ArgumentParser(description='Display network interface information') parser.add_argument('-p', action='store_true', help='Show physical interfaces (UP by default)') parser.add_argument('-v', action='store_true', help='Show virtual interfaces (UP by default)') parser.add_argument('-a', action='store_true', help='Include all interfaces (UP, DOWN, UNKNOWN)') parser.add_argument('-e', action='store_true', help='Fetch and display the external IP address') parser.add_argument('--ipv6', '-6', action='store_true', help='Include IPv6 addresses in the output') args = parser.parse_args() # Default to showing both UP physical and virtual interfaces if no flags are specified display_interface_details(show_physical=args.p or not (args.p or args.v or args.a or args.e), show_virtual=args.v or not (args.p or args.v or args.a or args.e), show_all=args.a, show_external_ip=args.e, show_ipv6=args.ipv6)