250 lines
9.8 KiB
Python
Executable File
250 lines
9.8 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_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)
|