From f0159bdea7eec9540cb09bdc20091e0b78fda3dd Mon Sep 17 00:00:00 2001 From: Menno van Leeuwen Date: Wed, 26 Mar 2025 14:20:31 +0100 Subject: [PATCH] feat: add Go file compilation and enhance network interface display functionality --- config/ansible/tasks/global/utils.yml | 8 + .../ansible/tasks/global/utils/helloworld.go | 7 + config/ansible/tasks/global/utils/ipaddr | 150 ++++++++++++++---- 3 files changed, 136 insertions(+), 29 deletions(-) create mode 100644 config/ansible/tasks/global/utils/helloworld.go diff --git a/config/ansible/tasks/global/utils.yml b/config/ansible/tasks/global/utils.yml index ea5f965..9c7758c 100644 --- a/config/ansible/tasks/global/utils.yml +++ b/config/ansible/tasks/global/utils.yml @@ -21,4 +21,12 @@ dest: "{{ ansible_env.HOME }}/.local/bin/{{ item.path | basename }}" state: link loop: "{{ utils_files.files }}" + when: item.path | regex_search('\.go$') is not defined + become: false + +- name: Compile Go files and place binaries in ~/.local/bin + ansible.builtin.command: + cmd: go build -o "{{ ansible_env.HOME }}/.local/bin/{{ item.path | basename | regex_replace('\.go$', '') }}" "{{ item.path }}" + loop: "{{ utils_files.files }}" + when: item.path | regex_search('\.go$') become: false diff --git a/config/ansible/tasks/global/utils/helloworld.go b/config/ansible/tasks/global/utils/helloworld.go new file mode 100644 index 0000000..a3dd973 --- /dev/null +++ b/config/ansible/tasks/global/utils/helloworld.go @@ -0,0 +1,7 @@ +package main + +import "fmt" + +func main() { + fmt.Println("Hello, World!") +} diff --git a/config/ansible/tasks/global/utils/ipaddr b/config/ansible/tasks/global/utils/ipaddr index 9f19f75..2056350 100755 --- a/config/ansible/tasks/global/utils/ipaddr +++ b/config/ansible/tasks/global/utils/ipaddr @@ -3,6 +3,7 @@ import os import subprocess import argparse +import requests def get_physical_interfaces(): """ @@ -50,57 +51,140 @@ def get_virtual_interfaces(): return virtual_interfaces -def display_interface_details(show_all=False): +def get_up_interfaces(interfaces): """ - Display details of network interfaces, including their IP address, subnet mask, - and MAC address, in a formatted table. + Filters the given list of interfaces to include only those that are up or unknown. Args: - show_all (bool): If True, includes both physical and virtual interfaces - in the output. Defaults to False, which only displays - physical interfaces. + interfaces (list): A list of interface names. - Output: - Prints a table with the following columns: - - Interface: The name of the network interface. - - IP Address: The IP address assigned to the interface. - - Subnet: The subnet mask associated with the IP address. - - MAC Address: The MAC address of the interface. + 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: - - The function uses the `ip` command to fetch interface details. - - If an interface does not have an IP address, "No IP" is displayed in the - IP Address column. - - If an error occurs while fetching details for an interface, an error - message is printed for that interface. + - 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. """ - interfaces = get_physical_interfaces() + if show_external_ip: + external_ip = get_external_ip() + print(f"External IP: {external_ip}") + print("-" * 50) + + interfaces = [] if show_all: - interfaces.extend(get_virtual_interfaces()) - interfaces.sort() + 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, # Increased to accommodate longer interface names - 'ip': 18, # Increased for longer IP addresses + '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']}} {'MAC Address':<{col_widths['mac']}}") - print("-" * (col_widths['interface'] + col_widths['ip'] + col_widths['subnet'] + col_widths['mac'])) + 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] - mac = parts[1] ip_with_mask = parts[2] # Split IP/mask @@ -111,14 +195,22 @@ def display_interface_details(show_all=False): ip = ip_with_mask subnet = "" - print(f"{name:<{col_widths['interface']}} {ip:<{col_widths['ip']}} {subnet:<{col_widths['subnet']}} {mac:<{col_widths['mac']}}") + 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']}} {parts[1] if len(parts) > 1 else 'N/A':<{col_widths['mac']}}") + 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('-a', '--all', action='store_true', help='Show all interfaces including virtual ones like Tailscale and WireGuard') + 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() - display_interface_details(args.all) + + # 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)