iface-groups (8375B)
1 #!/bin/bash 2 # written by me on October 1, 2022. 3 4 # enumerates the properties of each network interface 5 # eventually, it may be possible to assign rules on a per-network basis 6 # i.e., all ifaces on 10.111.13/24 should request DHCP 7 # unless, that's better done as a set of logic outside this program 8 9 # we need to know: 10 # - which interfaces are wired (compatible with PXE boots) 11 # - which interfaces are connected 12 # - which interfaces are on the same L2 network 13 14 # ASSUMPTION: one interface is NOT connected to multiple L2 switches 15 16 set -e 17 18 sudo -v 19 20 help () { 21 22 echo -e "usage:" 23 echo -e "\t-i , --ifacetype=[TYPE]" 24 echo -e "\t-t , --timelimit=[seconds]" 25 echo -e "Multiple interface types may be specified, like so:" 26 echo -e "\tiface-groups -i eth -i tun -t 5" 27 echo -e "Available interface types (at least one must be specified):" 28 echo -e "\teth, wlan, bridge, vlan, bond, tap, dummy, ib, ibchild, ppp," 29 echo -e "\tipip, ip6tnl, lo, sit, gre, irda, wlan_aux, tun, isdn, mip6mnha" 30 31 } 32 33 eecho () { echo "$@" 1>&2 ; } 34 35 # timelimit="$1" ; [ -z "$timelimit" ] && timelimit='4' 36 37 ip_address_prefix='203.0.113' 38 39 # +----------------------+----------------------------+ 40 # | Attribute | Value | 41 # +----------------------+----------------------------+ 42 # | Address Block | 203.0.113.0/24 | 43 # | Name | Documentation (TEST-NET-3) | 44 # | RFC | [RFC5737] | 45 # | Allocation Date | January 2010 | 46 # | Termination Date | N/A | 47 # | Source | False | 48 # | Destination | False | 49 # | Forwardable | False | 50 # | Global | False | 51 # | Reserved-by-Protocol | False | 52 # +----------------------+----------------------------+ 53 # 54 # Table 14: TEST-NET-3 55 56 ifacetype=eth timelimit=3 lopts=ifacetype:,timelimit: sopts=i:t: 57 58 PARSED=$(getopt --options=$sopts --longoptions=$lopts --name "$0" -- "$@") 59 60 eval set -- "$PARSED" 61 62 while true; do case "$1" in 63 64 -i|--ifacetype) 65 66 allowed_iface_types="${allowed_iface_types:+$allowed_iface_types } $2" 67 shift 2 68 ;; 69 70 -t|--timelimit) 71 72 timelimit="$2" 73 shift 2 74 ;; 75 76 --) shift ; break ;; 77 *) help ; exit 1 ;; 78 79 esac done 80 81 [ -z "$allowed_iface_types" ] && help && exit 1 82 83 tempfile=/tmp/ifacegroups 84 groupingfolder=/tmp/ifacegrouping 85 86 rm -rf $tempfile $groupingfolder 87 mkdir -p $tempfile 88 mkdir -p $groupingfolder 89 90 if ! command -v tcpdump &>/dev/null ; then echo "tcpdump required" ; exit 2 ; fi 91 92 get_iface_type () { 93 94 # function came from a dead link via stackoverflow 95 # http://gitorious.org/opensuse/sysconfig/blobs/master/scripts/functions 96 # https://github.com/openSUSE/sysconfig/blob/master/scripts/functions 97 # (GPL v2) 98 # (may want to rewrite this in TCL or lisp) 99 100 local IF=$1 TYPE 101 test -n "$IF" || return 1 102 test -d /sys/class/net/$IF || return 2 103 case "`cat /sys/class/net/$IF/type`" in 104 1) 105 TYPE=eth 106 # Ethernet, may also be wireless, ... 107 if test -d /sys/class/net/$IF/wireless -o \ 108 -L /sys/class/net/$IF/phy80211 ; then 109 TYPE=wlan 110 elif test -d /sys/class/net/$IF/bridge ; then 111 TYPE=bridge 112 elif test -f /proc/net/vlan/$IF ; then 113 TYPE=vlan 114 elif test -d /sys/class/net/$IF/bonding ; then 115 TYPE=bond 116 elif test -f /sys/class/net/$IF/tun_flags ; then 117 TYPE=tap 118 elif test -d /sys/devices/virtual/net/$IF ; then 119 case $IF in 120 (dummy*) TYPE=dummy ;; 121 esac 122 fi 123 ;; 124 24) TYPE=eth ;; # firewire ;; # IEEE 1394 IPv4 - RFC 2734 125 32) # InfiniBand 126 if test -d /sys/class/net/$IF/bonding ; then 127 TYPE=bond 128 elif test -d /sys/class/net/$IF/create_child ; then 129 TYPE=ib 130 else 131 TYPE=ibchild 132 fi 133 ;; 134 512) TYPE=ppp ;; 135 768) TYPE=ipip ;; # IPIP tunnel 136 769) TYPE=ip6tnl ;; # IP6IP6 tunnel 137 772) TYPE=lo ;; 138 776) TYPE=sit ;; # sit0 device - IPv6-in-IPv4 139 778) TYPE=gre ;; # GRE over IP 140 783) TYPE=irda ;; # Linux-IrDA 141 801) TYPE=wlan_aux ;; 142 65534) TYPE=tun ;; 143 esac 144 # The following case statement still has to be replaced by something 145 # which does not rely on the interface names. 146 case $IF in 147 ippp*|isdn*) TYPE=isdn;; 148 mip6mnha*) TYPE=mip6mnha;; 149 esac 150 test -n "$TYPE" && echo $TYPE && return 0 151 return 3 152 } 153 154 eecho "analysing available physical L2 networks..." 155 156 # for every interface on the system 157 for interface in /sys/class/net/* 158 do 159 160 interface=$(basename $interface) 161 162 # figure out what it is: wifi? ethernet? virtual bridge? infiniband? 163 iface_type=$(get_iface_type $interface) 164 165 # for each of the useful interface types, specified above (or in an arg) 166 for allowed_iface_type in ${allowed_iface_types[@]} 167 do 168 169 # if the interface we're looking at is one of those allowed 170 if [ "$iface_type" == "$allowed_iface_type" ] 171 then 172 173 # if the interface is connected to something: switch, router, etc 174 if tcpdump --list-interfaces | grep $interface | grep -q 'Running' 175 then 176 177 # add it do the list of viable interfaces 178 viable_ifaces="${viable_ifaces:+$viable_ifaces } $interface" 179 180 eecho "found relevant iface: $interface" 181 182 fi 183 184 # don't check more types after a match: "there can be only one" 185 break 186 187 fi 188 189 done 190 191 done 192 193 [ -z "${viable_ifaces[@]}" ] && echo '' && exit 1 194 195 ip_iterator=0 196 197 declare -A iface_ip 198 199 starttime=$(date +%s) 200 201 # eecho "broadcasting and sniffing ARP on all ifaces for $timelimit seconds..." 202 203 # for each viable interface we found 204 for interface in ${viable_ifaces[@]} 205 do 206 207 ip_iterator=$((ip_iterator+1)) 208 209 # create a unique, unused ip address (taken from TEST-NET-3, RFC 6890) 210 ip_address="$ip_address_prefix.$ip_iterator" 211 212 # associate that ip address with the current interface 213 iface_ip+=(["$ip_address"]="$interface") 214 215 eecho "$interface broadcasting ARP with $ip_address and dumping results to $tempfile/$interface" 216 217 # start listening on that interface for ARP requests 218 sudo timeout $timelimit tcpdump --immediate-mode -i $interface arp > $tempfile/$interface 2>/dev/null & 219 220 # start sending ARP requests for the unique IP address on that interface 221 sudo arping -I $interface -w $timelimit $ip_address &>/dev/null & 222 223 done 224 225 # total time it took to start the listeners and senders 226 endtime=$(date +%s) 227 initializationtime=$(( $endtime - $starttime )) 228 229 # wait for the listeners and senders to finish 230 sleep $(($timelimit + $initializationtime + 1)) 231 232 # for each viable interface, get the file with the results from the listener 233 for ifacefile in $tempfile/* 234 do 235 236 ifacename=$(basename $ifacefile) 237 238 # if the iface is not already in a group of ifaces that can see each other 239 if ! grep -q $ifacename $groupingfolder/* 2>/dev/null 240 then 241 242 # create a new group with just that interface 243 echo $ifacename > $groupingfolder/$(date +%s%N) 244 245 fi 246 247 # for each ARP request seen by this interface, get the IP address requested 248 grep $ip_address_prefix $ifacefile | cut -d ' ' -f 5 | while read ipaddress 249 do 250 251 # get the group file of the interface that saw the request 252 groupfile=$(grep -l $ifacename $groupingfolder/* 2>/dev/null) 253 254 # convert the ip address to the sender interface and add it to the group 255 echo ${iface_ip[$ipaddress]} >> $groupfile 256 257 done 258 259 done 260 261 eecho "scan results:" 262 263 # for each group of interfaces that we collected 264 for group in $groupingfolder/* 265 do 266 267 # filter out duplicate interfaces within the group and print to stdout 268 echo $(sort $group | uniq | xargs) 269 270 done 271