commit 33bf3f3dd23136fa99d764007917976ea8ff3728
parent 89e9098f4e9d69311d5379f498b643b4ad91824b
Author: um.hau <um.hau@outlook.com>
Date: Fri, 27 Dec 2024 17:05:02 -0500
initial upload
Diffstat:
| A | define-networks | | | 541 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | iface-groups | | | 271 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
2 files changed, 812 insertions(+), 0 deletions(-)
diff --git a/define-networks b/define-networks
@@ -0,0 +1,541 @@
+#!/usr/bin/python3
+
+# KNOWN BUGS
+# - can use mac addresses, which dont work for all iface types
+# - duplicate mac addresses are ignored
+# - only eth and tun iface types are tested
+
+import ipaddress
+import argparse
+import queue
+import subprocess
+import re
+import sys
+import os
+from typing import Optional
+
+def eprint(*args, color=None, indents=0, **kwargs):
+ color_codes = {
+ 'black': '30',
+ 'red': '31',
+ 'green': '32',
+ 'yellow': '33',
+ 'blue': '34',
+ 'magenta': '35',
+ 'cyan': '36',
+ 'white': '37'
+ }
+
+ indent_str = ' ' * indents # 4 spaces per indent level, adjust as desired
+
+ if color in color_codes:
+ args = ['\033[' + color_codes[color] + 'm' + indent_str + arg + '\033[0m' for arg in args]
+ else:
+ args = [indent_str + arg for arg in args]
+
+ print(*args, file=sys.stderr, **kwargs)
+
+def check_root():
+ if os.geteuid() != 0:
+ exit("You need to have root privileges to run this script.\nPlease try again, this time using 'sudo'. Exiting.")
+
+def is_mac_address(string: str) -> bool:
+ """Check if the string is a valid MAC address"""
+ return re.fullmatch(r"(?:[0-9a-fA-F]{2}[:-]){5}[0-9a-fA-F]{2}|[0-9a-fA-F]{12}", string) is not None
+
+def get_interface_by_mac(mac_address: str) -> Optional[str]:
+ """Return the network interface corresponding to a given MAC address"""
+ if len(mac_address) == 12: # If MAC address has no separators
+ # Add colons to MAC address
+ mac_address = ':'.join(mac_address[i:i+2] for i in range(0, 12, 2))
+
+ result = subprocess.run(['ifconfig', '-a'], capture_output=True, text=True)
+ interfaces = result.stdout.split('\n\n')
+
+ for interface in interfaces:
+ if mac_address in interface:
+ interface_name = re.match(r"^\w+", interface).group()
+ return interface_name.replace('Link', '') if 'Link' in interface_name else interface_name
+
+ return None
+
+def get_network_interface(string: str) -> Optional[str]:
+ """Get the network interface given a MAC address or interface name"""
+ if is_mac_address(string):
+ mac = get_interface_by_mac(string)
+ eprint("converted " + str(string) + " to " + str(mac), indents=1)
+ return mac
+ else:
+ return string
+
+def extract_dhcp_info():
+
+ eprint("Running nmap to extract DHCP info...")
+
+ command = ['nmap', '-T4', '--script', 'broadcast-dhcp-discover']
+
+ # Run the command and get the output
+ result = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+
+ # Ensure the command didn't produce an error
+ if result.returncode != 0:
+ raise Exception(f"Command failed with error {result.stderr.decode()}")
+
+ # print(result)
+
+ # Split the output into lines
+ output = result.stdout.decode('utf-8').split('\n')
+
+ servers = []
+
+ # Define regex to match interface, server identifier and subnet mask
+ interface_regex = re.compile(r'^\|\s*Interface:\s*(.*)$')
+ server_regex = re.compile(r'^\|\s*Server Identifier:\s*(.*)$')
+ subnet_regex = re.compile(r'^\|\s*Subnet Mask:\s*(.*)$')
+
+ current_interface = None
+ current_server = None
+
+ for line in output:
+
+ # If the line contains an interface, save it
+ interface_match = interface_regex.match(line)
+ if interface_match:
+ current_interface = interface_match.group(1)
+ # eprint("found interface: " + str(current_interface))
+
+ # If the line contains a server identifier, save it
+ server_match = server_regex.match(line)
+ if server_match:
+ current_server = server_match.group(1)
+ # eprint("found server: " + str(current_server))
+
+ # If the line contains a subnet mask, save it and convert to CIDR
+ subnet_match = subnet_regex.match(line)
+ if subnet_match and current_interface and current_server:
+
+ subnet_mask = subnet_match.group(1)
+ cidr = ipaddress.ip_network(f"0.0.0.0/{subnet_mask}").prefixlen # Convert subnet mask to CIDR
+ # eprint("found CIDR subnet range: " + str(cidr))
+
+ servers.append((current_interface, f"{current_server}/{cidr}"))
+ current_interface = None
+ current_server = None
+
+ # eprint("--appended new subnet entry--")
+
+ for server in servers:
+ eprint("found server: " + str(server), indents=1)
+
+ return servers
+
+def merge_interface_groups(iface_groups, join_groups):
+
+ eprint("merging interface groups...")
+
+ merged_iface_groups = iface_groups.copy()
+
+ for _join_group in join_groups:
+
+ eprint("join group: " + str(_join_group), indents=1)
+
+ join_group = _join_group.split()
+
+ for i in range(len(join_group)):
+ join_group[i] = get_network_interface(join_group[i])
+
+ indices_to_merge = []
+
+ # Find the indices of the groups that contain interfaces from the join_group
+ for i, group in enumerate(merged_iface_groups):
+ if any(iface in group for iface in join_group):
+ indices_to_merge.append(i)
+
+ # Merge the groups together
+ if indices_to_merge:
+ merged_group = []
+ for index in indices_to_merge:
+ merged_group.extend(merged_iface_groups[index])
+ merged_group = list(set(merged_group)) # remove duplicates
+
+ eprint(f"Merging groups at indices {indices_to_merge} into {merged_group}", indents=1)
+
+ # Remove the old groups and add the merged group
+ indices_to_merge.sort(reverse=True)
+ for index in indices_to_merge:
+ del merged_iface_groups[index]
+ merged_iface_groups.append(merged_group)
+
+ return merged_iface_groups
+
+def get_iface_groups(iface_types, timelimit):
+
+ eprint("running iface-groups to get interface groups...")
+
+ eprint("given iface types: " + str(iface_types), indents=1)
+ eprint("time limit: " + str(timelimit), indents=1)
+
+ cmd = ['/bin/iface-groups']
+
+ for iface_type in iface_types:
+ cmd.extend(['-i', iface_type])
+
+ cmd.extend(['-t', str(timelimit)])
+
+ output = subprocess.run(cmd, capture_output=True, text=True)
+
+ if output.returncode != 0:
+ eprint('Error running iface-groups')
+ return 1
+
+ interface_groups = []
+ for line in output.stdout.splitlines():
+ interface_group = line.split()
+ interface_groups.append(interface_group)
+
+ eprint("interface group: " + str(interface_group), indents=1)
+
+ return interface_groups
+
+def assign_discovered_subnets(dhcp_info, iface_groups):
+
+ eprint("assigning discovered subnets...")
+
+ assigned_subnets = []
+
+ for iface_group in iface_groups:
+
+ subnet_found = False
+
+ for _iface in iface_group:
+
+ # convert MAC to iface
+ iface = get_network_interface(_iface)
+
+ subnet = next((s for i, s in dhcp_info if i == iface), None)
+
+ if subnet is not None:
+ assigned_subnets.append([iface_group, subnet, 'discovered'])
+ subnet_found = True
+
+ eprint("ASSIGNED: " + str(assigned_subnets[-1]), color='green')
+ break
+
+ if not subnet_found:
+
+ assigned_subnets.append([iface_group, None, 'none'])
+
+ # assigned_subnet_overlap(assigned_subnets, '192.168.0.0/8')
+
+ return assigned_subnets
+
+def assigned_subnet_overlap(assigned_subnets, new_subnet):
+
+ eprint("checking if " + str(new_subnet) + " overlaps with existing subnets...", indents=1)
+
+ new_subnet = ipaddress.IPv4Network(str(new_subnet), False)
+
+ # [[['enp0s20f0u3', 'enp0s31f6'], '192.168.1.1/24', 'discovered'], [['eth0'], None, 'none']]
+ for assigned_subnet in assigned_subnets:
+
+ a_subnet = assigned_subnet[1]
+
+ if a_subnet is not None:
+ if new_subnet.overlaps(ipaddress.IPv4Network(str(a_subnet), False)):
+
+ eprint("subnet " + str(new_subnet) + " overlaps existing subnet " + str(a_subnet), indents=2)
+ return False
+
+ return True
+
+def assign_manual_subnets(assigned_subnets, manual_subnets):
+
+ eprint("Manually assigning subnets...")
+
+ if manual_subnets is None:
+ eprint("No manual subnets provided", indents=1)
+ return
+
+ for _m_iface, m_subnet in manual_subnets:
+
+ m_iface = get_network_interface(_m_iface)
+
+ if m_subnet.isdigit():
+ eprint('manual pair (' + str(m_iface) + ', ' + str(m_subnet) + ') will be used for dynamic generation', indents=1)
+ continue
+
+ # we assign manual subnets in order of precedence
+ if not assigned_subnet_overlap(assigned_subnets, m_subnet):
+ continue
+
+ # [[['enp0s20f0u3', 'enp0s31f6'], '192.168.1.1/24', 'discovered'], [['eth0'], None, 'none']]
+ for index, assigned_subnet in enumerate(assigned_subnets):
+
+ a_group = assigned_subnet[0]
+ a_subnet = assigned_subnet[1]
+
+ if m_iface in a_group:
+
+ eprint('found ' + str(m_iface) + ' in ' + str(a_group) + ' with subnet ' + str(a_subnet), indents=1)
+
+ if a_subnet is None:
+
+ eprint("Manually assigning " + str(m_iface) + ' the subnet ' + str(m_subnet), indents=1)
+
+ assigned_subnets[index][1] = m_subnet
+ assigned_subnets[index][2] = 'manual'
+
+ eprint("ASSIGNED: " + str(assigned_subnets[index]), color='green')
+
+ else:
+
+ eprint("DHCP server already exists, ignoring manual assignment", color='red', indents=1)
+
+ break
+
+def possible_subnets(main, taken):
+
+ # eprint("searching for possible subnets within " + str(main) + "...", indents=2)
+
+ # we assume no subnets are available intially
+ available = []
+ q = queue.Queue()
+
+ # add first node for expansion in the BFS process
+ q.put(main)
+
+ while q.qsize() > 0:
+ subnet = q.get()
+ has_overlap = False
+
+ if taken:
+
+ for taken_subnet in taken:
+
+ if subnet.overlaps(taken_subnet):
+ # still has overlaps somewhere in children, keep expanding
+
+ if subnet.prefixlen < 31: # avoid adding /32 and /31 subnets to the queue
+ for sub_subnet in subnet.subnets():
+ q.put(sub_subnet)
+ has_overlap = True
+ break
+
+ if not has_overlap:
+ # no overlaps with taken - this subnet is entirely available
+ available.append(subnet)
+ else:
+ # if no subnets are taken, all subnets are available
+ available.append(subnet)
+
+ return available
+
+def get_new_subnet(available_subnets, taken_subnets, desired_subnet_size):
+
+ eprint("generating a list of subnets already in use...", indents=1)
+
+ if taken_subnets:
+ for taken_subnet in taken_subnets:
+ eprint("unavailable subnet: " + str(taken_subnet), indents=2)
+ else:
+ eprint("no known subnets are in use", indents=2)
+
+ available_subnets_complete = []
+
+ eprint("generating a sorted list of available subnets, after subtracting unavailable ranges...", indents=1)
+
+ for available_supernet in available_subnets:
+ eprint("candidate supernet: " + str(available_supernet), indents=2)
+
+ for available_subnet in available_subnets:
+ available_subnets_complete.extend(possible_subnets(available_subnet, taken_subnets))
+
+ # now we have an exhaustive list of the known free space
+
+ sorted_subnets = sorted(available_subnets_complete, key=lambda subnet: subnet.prefixlen, reverse=True)
+
+ # find the smallest subnet that fits our needs
+
+ matching_subnet = []
+
+ eprint("searching available subnets for smallest that is at least a /" + str(desired_subnet_size) + " ...", indents=1)
+
+ for subnet in sorted_subnets:
+
+ eprint("candidate subnet: " + str(subnet), indents=2)
+
+ if int(subnet.prefixlen) <= int(desired_subnet_size):
+ matching_subnet = subnet
+ # eprint("subnet is suitable: " + str(subnet))
+ break
+
+ if not matching_subnet:
+ eprint("no matching subnet found", indents=3)
+ return 1
+ else:
+ resized = next(matching_subnet.subnets(new_prefix=int(desired_subnet_size)))
+ eprint("available subnet found: " + str(resized), indents=3)
+ return resized
+
+def assign_dynamic_subnets(assigned_subnets, manual_subnets, subnet_pool, default_subnet_size):
+
+ eprint("dynamically assigning subnets...")
+
+ existing_subnets = [ipaddress.IPv4Network(subnet, False) for _, subnet, _ in assigned_subnets if subnet]
+
+ # Create a dictionary mapping interfaces to preferred subnet sizes for easy lookup
+ preferred_sizes = {}
+
+ manual_subnets = manual_subnets if manual_subnets is not None else []
+
+ for _iface, size in manual_subnets:
+
+ iface = get_network_interface(_iface)
+
+ if size.isdigit():
+ preferred_sizes[iface] = int(size)
+
+ # Iterate over assigned_subnets looking for interface groups without assigned subnets
+ for index, (iface_group, assigned_subnet, method) in enumerate(assigned_subnets):
+
+ if assigned_subnet is not None:
+ continue # Skip interface groups that already have a subnet assigned
+
+ # Get the preferred subnet size for the first interface in the group that has a preference,
+ # or use the default subnet size if no preference is found
+ subnet_size = default_subnet_size
+ for _iface in iface_group:
+
+ iface = get_network_interface(_iface)
+
+ if iface in preferred_sizes:
+ subnet_size = preferred_sizes[iface]
+ break
+
+ eprint("Finding a /" + str(subnet_size) + " subnet for interface group " + str(iface_group), indents=1)
+
+ # Create a list to hold the generated subnets
+ generated_subnets = []
+
+ # Generate all possible subnets of the required size from the subnet pool
+ for pool_subnet in subnet_pool:
+ pool_subnet = ipaddress.IPv4Network(pool_subnet, False) # ensure pool_subnet is an IPv4Network object
+ generated_subnets.extend(possible_subnets(pool_subnet, [ipaddress.IPv4Network(s[1], False) for s in assigned_subnets if s[1] is not None]))
+
+ new_subnet = get_new_subnet(generated_subnets, existing_subnets, subnet_size)
+
+ if new_subnet:
+
+ eprint(f"assigning {new_subnet} to {iface_group}", indents=1)
+ existing_subnets.append(new_subnet)
+ assigned_subnets[index][1] = str(new_subnet)
+ assigned_subnets[index][2] = 'dynamic'
+
+ eprint("ASSIGNED: " + str(assigned_subnets[index]), color='green')
+ # continue
+
+ else:
+ eprint(f"No available subnets for {iface_group}", indents=1)
+
+def find_and_assign_subnets(iface_types, timelimit, subnet_pool, manual_subnets, default_subnet_size, join_groups):
+
+ eprint("Finding and assigning subnets...", color='green')
+
+ iface_groups = get_iface_groups(iface_types, timelimit)
+
+ # eprint("iface groups: " + str(iface_groups))
+
+ if join_groups:
+ iface_groups = merge_interface_groups(iface_groups, join_groups)
+
+ dhcp_info = extract_dhcp_info()
+
+ assigned_subnets = assign_discovered_subnets(dhcp_info, iface_groups)
+
+ # eprint("dhcp results: " + str(dhcp_info), indents=1)
+ # eprint("RAW GROUPS > " + str(iface_groups))
+ # eprint("ASSIGNED: " + str(assigned_subnets), color='green')
+ # eprint("MANUAL SUBNETS > " + str(manual_subnets))
+ # eprint("IFACE GROUPS > " + str(iface_groups))
+
+ assign_manual_subnets(assigned_subnets, manual_subnets)
+
+ # eprint("ASSIGNED: " + str(assigned_subnets), color='green')
+
+ assign_dynamic_subnets(assigned_subnets, manual_subnets, subnet_pool, default_subnet_size)
+
+ # eprint("ASSIGNED: " + str(assigned_subnets), color='green')
+
+ eprint("done finding and assigning subnets", color='green')
+
+ return assigned_subnets
+
+def valid_cidr(string):
+ try:
+ ipaddress.ip_network(string)
+ return string
+ except ValueError:
+ msg = "%r is not a valid CIDR block" % string
+ raise argparse.ArgumentTypeError(msg)
+
+def valid_pair(string):
+ parts = string.split()
+ if len(parts) != 2:
+ msg = "%r is not a valid interface-subnet pair" % string
+ raise argparse.ArgumentTypeError(msg)
+ iface, cidr = parts
+
+ # Check if cidr is a valid IP network
+ try:
+ ipaddress.ip_network(cidr)
+ return iface, cidr
+ except ValueError:
+ pass
+
+ # Check if cidr is a valid digit between 32 and 0
+ try:
+ cidr_value = int(cidr)
+ if 0 <= cidr_value <= 32:
+ return iface, cidr
+ else:
+ msg = "%r in the pair is not a valid CIDR block or digit" % cidr
+ raise argparse.ArgumentTypeError(msg)
+ except ValueError:
+ pass
+
+ msg = "%r in the pair is not a valid CIDR block or digit" % cidr
+ raise argparse.ArgumentTypeError(msg)
+
+def main(args):
+
+ parser = argparse.ArgumentParser(description="Find and assign subnets")
+
+ parser.add_argument('-i', '--interfaces', nargs='+', required=True,
+ choices=['eth', 'wlan', 'bridge', 'vlan', 'bond', 'tap', 'dummy', 'ppp', 'ipip',
+ 'ib', 'ibchild',
+ 'ip6tnl', 'lo', 'sit', 'gre', 'irda', 'wlan_aux', 'tun', 'isdn', 'mip6mnha'],
+ help="Network interfaces")
+ parser.add_argument('-t', '--timeout', type=int, required=True, help="Timeout period")
+ parser.add_argument('-s', '--subnets', nargs='+', required=True, type=valid_cidr, help="Subnet pools in CIDR format")
+ parser.add_argument('-m', '--manual', nargs='+', type=valid_pair, help="Manually assigned interface-subnet pairs")
+ parser.add_argument('-d', '--subnetsize', type=int, default=24, help="Default size for dynamically generated subnets")
+ parser.add_argument('-j', '--join', nargs='+', type=str, help="Specify interfaces to be assumed on the same L2 network")
+
+ # TODO (later - important ones are done)
+ # define a default subnet size associated with an interface: -d 'eth0 21' 'eth5 16'
+ # exclude interfaces and their interface groups: -X 'eth0 eth4'
+ # exclude interfaces but not their interface groups: -x 'eth2 eth3'
+ # only use these interfaces, and their interface groups: -O 'eth1 eth8'
+ # only use these interfaces, not even others in their iface group: -o 'eth2 eth4'
+
+ args = parser.parse_args(args)
+
+ check_root()
+
+ results = find_and_assign_subnets(args.interfaces, args.timeout, args.subnets, args.manual, args.subnetsize, args.join)
+
+ for result in results:
+ print('ifaces: ' + str(' '.join(result[0])) + ' status: ' + str(result[2]) + ' subnet: ' + str(result[1]))
+
+if __name__ == "__main__":
+ main(sys.argv[1:])
diff --git a/iface-groups b/iface-groups
@@ -0,0 +1,271 @@
+#!/bin/bash
+# written by me on October 1, 2022.
+
+# enumerates the properties of each network interface
+# eventually, it may be possible to assign rules on a per-network basis
+# i.e., all ifaces on 10.111.13/24 should request DHCP
+# unless, that's better done as a set of logic outside this program
+
+# we need to know:
+# - which interfaces are wired (compatible with PXE boots)
+# - which interfaces are connected
+# - which interfaces are on the same L2 network
+
+# ASSUMPTION: one interface is NOT connected to multiple L2 switches
+
+set -e
+
+sudo -v
+
+help () {
+
+ echo -e "usage:"
+ echo -e "\t-i , --ifacetype=[TYPE]"
+ echo -e "\t-t , --timelimit=[seconds]"
+ echo -e "Multiple interface types may be specified, like so:"
+ echo -e "\tiface-groups -i eth -i tun -t 5"
+ echo -e "Available interface types (at least one must be specified):"
+ echo -e "\teth, wlan, bridge, vlan, bond, tap, dummy, ib, ibchild, ppp,"
+ echo -e "\tipip, ip6tnl, lo, sit, gre, irda, wlan_aux, tun, isdn, mip6mnha"
+
+}
+
+eecho () { echo "$@" 1>&2 ; }
+
+# timelimit="$1" ; [ -z "$timelimit" ] && timelimit='4'
+
+ip_address_prefix='203.0.113'
+
+# +----------------------+----------------------------+
+# | Attribute | Value |
+# +----------------------+----------------------------+
+# | Address Block | 203.0.113.0/24 |
+# | Name | Documentation (TEST-NET-3) |
+# | RFC | [RFC5737] |
+# | Allocation Date | January 2010 |
+# | Termination Date | N/A |
+# | Source | False |
+# | Destination | False |
+# | Forwardable | False |
+# | Global | False |
+# | Reserved-by-Protocol | False |
+# +----------------------+----------------------------+
+#
+# Table 14: TEST-NET-3
+
+ifacetype=eth timelimit=3 lopts=ifacetype:,timelimit: sopts=i:t:
+
+PARSED=$(getopt --options=$sopts --longoptions=$lopts --name "$0" -- "$@")
+
+eval set -- "$PARSED"
+
+while true; do case "$1" in
+
+ -i|--ifacetype)
+
+ allowed_iface_types="${allowed_iface_types:+$allowed_iface_types } $2"
+ shift 2
+ ;;
+
+ -t|--timelimit)
+
+ timelimit="$2"
+ shift 2
+ ;;
+
+ --) shift ; break ;;
+ *) help ; exit 1 ;;
+
+esac done
+
+[ -z "$allowed_iface_types" ] && help && exit 1
+
+tempfile=/tmp/ifacegroups
+groupingfolder=/tmp/ifacegrouping
+
+rm -rf $tempfile $groupingfolder
+mkdir -p $tempfile
+mkdir -p $groupingfolder
+
+if ! command -v tcpdump &>/dev/null ; then echo "tcpdump required" ; exit 2 ; fi
+
+get_iface_type () {
+
+ # function came from a dead link via stackoverflow
+ # http://gitorious.org/opensuse/sysconfig/blobs/master/scripts/functions
+ # https://github.com/openSUSE/sysconfig/blob/master/scripts/functions
+ # (GPL v2)
+ # (may want to rewrite this in TCL or lisp)
+
+ local IF=$1 TYPE
+ test -n "$IF" || return 1
+ test -d /sys/class/net/$IF || return 2
+ case "`cat /sys/class/net/$IF/type`" in
+ 1)
+ TYPE=eth
+ # Ethernet, may also be wireless, ...
+ if test -d /sys/class/net/$IF/wireless -o \
+ -L /sys/class/net/$IF/phy80211 ; then
+ TYPE=wlan
+ elif test -d /sys/class/net/$IF/bridge ; then
+ TYPE=bridge
+ elif test -f /proc/net/vlan/$IF ; then
+ TYPE=vlan
+ elif test -d /sys/class/net/$IF/bonding ; then
+ TYPE=bond
+ elif test -f /sys/class/net/$IF/tun_flags ; then
+ TYPE=tap
+ elif test -d /sys/devices/virtual/net/$IF ; then
+ case $IF in
+ (dummy*) TYPE=dummy ;;
+ esac
+ fi
+ ;;
+ 24) TYPE=eth ;; # firewire ;; # IEEE 1394 IPv4 - RFC 2734
+ 32) # InfiniBand
+ if test -d /sys/class/net/$IF/bonding ; then
+ TYPE=bond
+ elif test -d /sys/class/net/$IF/create_child ; then
+ TYPE=ib
+ else
+ TYPE=ibchild
+ fi
+ ;;
+ 512) TYPE=ppp ;;
+ 768) TYPE=ipip ;; # IPIP tunnel
+ 769) TYPE=ip6tnl ;; # IP6IP6 tunnel
+ 772) TYPE=lo ;;
+ 776) TYPE=sit ;; # sit0 device - IPv6-in-IPv4
+ 778) TYPE=gre ;; # GRE over IP
+ 783) TYPE=irda ;; # Linux-IrDA
+ 801) TYPE=wlan_aux ;;
+ 65534) TYPE=tun ;;
+ esac
+ # The following case statement still has to be replaced by something
+ # which does not rely on the interface names.
+ case $IF in
+ ippp*|isdn*) TYPE=isdn;;
+ mip6mnha*) TYPE=mip6mnha;;
+ esac
+ test -n "$TYPE" && echo $TYPE && return 0
+ return 3
+}
+
+eecho "analysing available physical L2 networks..."
+
+# for every interface on the system
+for interface in /sys/class/net/*
+do
+
+ interface=$(basename $interface)
+
+ # figure out what it is: wifi? ethernet? virtual bridge? infiniband?
+ iface_type=$(get_iface_type $interface)
+
+ # for each of the useful interface types, specified above (or in an arg)
+ for allowed_iface_type in ${allowed_iface_types[@]}
+ do
+
+ # if the interface we're looking at is one of those allowed
+ if [ "$iface_type" == "$allowed_iface_type" ]
+ then
+
+ # if the interface is connected to something: switch, router, etc
+ if tcpdump --list-interfaces | grep $interface | grep -q 'Running'
+ then
+
+ # add it do the list of viable interfaces
+ viable_ifaces="${viable_ifaces:+$viable_ifaces } $interface"
+
+ eecho "found relevant iface: $interface"
+
+ fi
+
+ # don't check more types after a match: "there can be only one"
+ break
+
+ fi
+
+ done
+
+done
+
+[ -z "${viable_ifaces[@]}" ] && echo '' && exit 1
+
+ip_iterator=0
+
+declare -A iface_ip
+
+starttime=$(date +%s)
+
+# eecho "broadcasting and sniffing ARP on all ifaces for $timelimit seconds..."
+
+# for each viable interface we found
+for interface in ${viable_ifaces[@]}
+do
+
+ ip_iterator=$((ip_iterator+1))
+
+ # create a unique, unused ip address (taken from TEST-NET-3, RFC 6890)
+ ip_address="$ip_address_prefix.$ip_iterator"
+
+ # associate that ip address with the current interface
+ iface_ip+=(["$ip_address"]="$interface")
+
+ eecho "$interface broadcasting ARP with $ip_address and dumping results to $tempfile/$interface"
+
+ # start listening on that interface for ARP requests
+ sudo timeout $timelimit tcpdump --immediate-mode -i $interface arp > $tempfile/$interface 2>/dev/null &
+
+ # start sending ARP requests for the unique IP address on that interface
+ sudo arping -I $interface -w $timelimit $ip_address &>/dev/null &
+
+done
+
+# total time it took to start the listeners and senders
+endtime=$(date +%s)
+initializationtime=$(( $endtime - $starttime ))
+
+# wait for the listeners and senders to finish
+sleep $(($timelimit + $initializationtime + 1))
+
+# for each viable interface, get the file with the results from the listener
+for ifacefile in $tempfile/*
+do
+
+ ifacename=$(basename $ifacefile)
+
+ # if the iface is not already in a group of ifaces that can see each other
+ if ! grep -q $ifacename $groupingfolder/* 2>/dev/null
+ then
+
+ # create a new group with just that interface
+ echo $ifacename > $groupingfolder/$(date +%s%N)
+
+ fi
+
+ # for each ARP request seen by this interface, get the IP address requested
+ grep $ip_address_prefix $ifacefile | cut -d ' ' -f 5 | while read ipaddress
+ do
+
+ # get the group file of the interface that saw the request
+ groupfile=$(grep -l $ifacename $groupingfolder/* 2>/dev/null)
+
+ # convert the ip address to the sender interface and add it to the group
+ echo ${iface_ip[$ipaddress]} >> $groupfile
+
+ done
+
+done
+
+eecho "scan results:"
+
+# for each group of interfaces that we collected
+for group in $groupingfolder/*
+do
+
+ # filter out duplicate interfaces within the group and print to stdout
+ echo $(sort $group | uniq | xargs)
+
+done
+