Menno van Leeuwen f0159bdea7
Some checks failed
Nix Format Check / check-format (push) Waiting to run
Ansible Lint Check / check-ansible (push) Failing after 17s
Python Lint Check / check-python (push) Failing after 42s
feat: add Go file compilation and enhance network interface display functionality
2025-03-26 14:20:31 +01:00

217 lines
8.5 KiB
Python
Executable File

#!/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_ip():
"""
Fetch the external IP address of the machine.
This function attempts to retrieve the external IP address using the services
`https://ifconfig.co`, `https://ifconfig.io`, and `https://ifconfig.me`. Each
request has a timeout of 200ms to ensure speed. The user agent is explicitly
set to `curl` to ensure proper response formatting.
Returns:
str: The external IP address if successful, or an error message if all services fail.
"""
services = ["https://ifconfig.co", "https://ifconfig.io", "https://ifconfig.me"]
headers = {"User-Agent": "curl"}
for service in services:
try:
response = requests.get(service, headers=headers, timeout=0.2)
if response.status_code == 200:
return response.text.strip()
except requests.RequestException:
continue
return "Unable to fetch external IP"
def display_interface_details(show_physical=False, show_virtual=False, show_all=False, show_external_ip=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.
Notes:
- If no flags are specified, both UP physical and virtual interfaces are shown by default.
- If `-a` is specified, it includes all physical interfaces by default.
- If `-a` and `-v` are combined, it includes all physical and virtual interfaces.
- If `-e` is specified, the external IP address is displayed.
"""
if show_external_ip:
external_ip = get_external_ip()
print(f"External IP: {external_ip}")
print("-" * 50)
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,
'ip': 18,
'subnet': 10,
'state': 10,
'mac': 18
}
# Print header with proper formatting
print(f"{'Interface':<{col_widths['interface']}} {'IP Address':<{col_widths['ip']}} {'Subnet':<{col_widths['subnet']}} {'State':<{col_widths['state']}} {'MAC Address':<{col_widths['mac']}}")
print("-" * (col_widths['interface'] + col_widths['ip'] + 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:
parts = result.stdout.strip().split()
if len(parts) >= 3:
name = parts[0]
ip_with_mask = parts[2]
# Split IP/mask
if '/' in ip_with_mask:
ip = ip_with_mask.split('/')[0]
subnet = ip_with_mask.split('/')[1]
else:
ip = ip_with_mask
subnet = ""
print(f"{name:<{col_widths['interface']}} {ip:<{col_widths['ip']}} {subnet:<{col_widths['subnet']}} {state:<{col_widths['state']}} {mac:<{col_widths['mac']}}")
else:
print(f"{interface:<{col_widths['interface']}} {'No IP':<{col_widths['ip']}} {'':<{col_widths['subnet']}} {state:<{col_widths['state']}} {mac:<{col_widths['mac']}}")
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')
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)