1## This file is part of Scapy 2## See http://www.secdev.org/projects/scapy for more informations 3## Copyright (C) Philippe Biondi <phil@secdev.org> 4## This program is published under a GPLv2 license 5 6""" 7Common customizations for all Unix-like operating systems other than Linux 8""" 9 10import sys,os,struct,socket,time 11from fcntl import ioctl 12import socket 13 14from scapy.error import warning, log_interactive 15import scapy.config 16import scapy.utils 17from scapy.utils6 import in6_getscope, construct_source_candidate_set 18from scapy.utils6 import in6_isvalid, in6_ismlladdr, in6_ismnladdr 19from scapy.consts import FREEBSD, NETBSD, OPENBSD, SOLARIS, LOOPBACK_NAME 20from scapy.arch import get_if_addr 21from scapy.config import conf 22 23 24################## 25## Routes stuff ## 26################## 27 28def _guess_iface_name(netif): 29 """ 30 We attempt to guess the name of interfaces that are truncated from the 31 output of ifconfig -l. 32 If there is only one possible candidate matching the interface name then we 33 return it. 34 If there are none or more, then we return None. 35 """ 36 with os.popen('%s -l' % conf.prog.ifconfig) as fdesc: 37 ifaces = fdesc.readline().strip().split(' ') 38 matches = [iface for iface in ifaces if iface.startswith(netif)] 39 if len(matches) == 1: 40 return matches[0] 41 return None 42 43 44def read_routes(): 45 if SOLARIS: 46 f=os.popen("netstat -rvn") # -f inet 47 elif FREEBSD: 48 f=os.popen("netstat -rnW") # -W to handle long interface names 49 else: 50 f=os.popen("netstat -rn") # -f inet 51 ok = 0 52 mtu_present = False 53 prio_present = False 54 routes = [] 55 pending_if = [] 56 for l in f.readlines(): 57 if not l: 58 break 59 l = l.strip() 60 if l.find("----") >= 0: # a separation line 61 continue 62 if not ok: 63 if l.find("Destination") >= 0: 64 ok = 1 65 mtu_present = "Mtu" in l 66 prio_present = "Prio" in l 67 refs_present = "Refs" in l 68 continue 69 if not l: 70 break 71 if SOLARIS: 72 lspl = l.split() 73 if len(lspl) == 10: 74 dest,mask,gw,netif,mxfrg,rtt,ref,flg = lspl[:8] 75 else: # missing interface 76 dest,mask,gw,mxfrg,rtt,ref,flg = lspl[:7] 77 netif=None 78 else: 79 rt = l.split() 80 dest,gw,flg = rt[:3] 81 netif = rt[4 + mtu_present + prio_present + refs_present] 82 if flg.find("Lc") >= 0: 83 continue 84 if dest == "default": 85 dest = 0 86 netmask = 0 87 else: 88 if SOLARIS: 89 netmask = scapy.utils.atol(mask) 90 elif "/" in dest: 91 dest,netmask = dest.split("/") 92 netmask = scapy.utils.itom(int(netmask)) 93 else: 94 netmask = scapy.utils.itom((dest.count(".") + 1) * 8) 95 dest += ".0"*(3-dest.count(".")) 96 dest = scapy.utils.atol(dest) 97 # XXX: TODO: add metrics for unix.py (use -e option on netstat) 98 metric = 1 99 if not "G" in flg: 100 gw = '0.0.0.0' 101 if netif is not None: 102 try: 103 ifaddr = get_if_addr(netif) 104 routes.append((dest,netmask, gw, netif, ifaddr, metric)) 105 except OSError as exc: 106 if exc.message == 'Device not configured': 107 # This means the interface name is probably truncated by 108 # netstat -nr. We attempt to guess it's name and if not we 109 # ignore it. 110 guessed_netif = _guess_iface_name(netif) 111 if guessed_netif is not None: 112 ifaddr = get_if_addr(guessed_netif) 113 routes.append((dest, netmask, gw, guessed_netif, ifaddr, metric)) 114 else: 115 warning("Could not guess partial interface name: %s", netif) 116 else: 117 raise 118 else: 119 pending_if.append((dest,netmask,gw)) 120 f.close() 121 122 # On Solaris, netstat does not provide output interfaces for some routes 123 # We need to parse completely the routing table to route their gw and 124 # know their output interface 125 for dest,netmask,gw in pending_if: 126 gw_l = scapy.utils.atol(gw) 127 max_rtmask,gw_if,gw_if_addr, = 0,None,None 128 for rtdst,rtmask,_,rtif,rtaddr in routes[:]: 129 if gw_l & rtmask == rtdst: 130 if rtmask >= max_rtmask: 131 max_rtmask = rtmask 132 gw_if = rtif 133 gw_if_addr = rtaddr 134 # XXX: TODO add metrics 135 metric = 1 136 if gw_if: 137 routes.append((dest,netmask, gw, gw_if, gw_if_addr, metric)) 138 else: 139 warning("Did not find output interface to reach gateway %s", gw) 140 141 return routes 142 143############ 144### IPv6 ### 145############ 146 147def _in6_getifaddr(ifname): 148 """ 149 Returns a list of IPv6 addresses configured on the interface ifname. 150 """ 151 152 # Get the output of ifconfig 153 try: 154 f = os.popen("%s %s" % (conf.prog.ifconfig, ifname)) 155 except OSError as msg: 156 log_interactive.warning("Failed to execute ifconfig.") 157 return [] 158 159 # Iterate over lines and extract IPv6 addresses 160 ret = [] 161 for line in f: 162 if "inet6" in line: 163 addr = line.rstrip().split(None, 2)[1] # The second element is the IPv6 address 164 else: 165 continue 166 if '%' in line: # Remove the interface identifier if present 167 addr = addr.split("%", 1)[0] 168 169 # Check if it is a valid IPv6 address 170 try: 171 socket.inet_pton(socket.AF_INET6, addr) 172 except: 173 continue 174 175 # Get the scope and keep the address 176 scope = in6_getscope(addr) 177 ret.append((addr, scope, ifname)) 178 179 return ret 180 181def in6_getifaddr(): 182 """ 183 Returns a list of 3-tuples of the form (addr, scope, iface) where 184 'addr' is the address of scope 'scope' associated to the interface 185 'iface'. 186 187 This is the list of all addresses of all interfaces available on 188 the system. 189 """ 190 191 # List all network interfaces 192 if OPENBSD: 193 try: 194 f = os.popen("%s" % conf.prog.ifconfig) 195 except OSError as msg: 196 log_interactive.warning("Failed to execute ifconfig.") 197 return [] 198 199 # Get the list of network interfaces 200 splitted_line = [] 201 for l in f: 202 if "flags" in l: 203 iface = l.split()[0].rstrip(':') 204 splitted_line.append(iface) 205 206 else: # FreeBSD, NetBSD or Darwin 207 try: 208 f = os.popen("%s -l" % conf.prog.ifconfig) 209 except OSError as msg: 210 log_interactive.warning("Failed to execute ifconfig.") 211 return [] 212 213 # Get the list of network interfaces 214 splitted_line = f.readline().rstrip().split() 215 216 ret = [] 217 for i in splitted_line: 218 ret += _in6_getifaddr(i) 219 return ret 220 221 222def read_routes6(): 223 """Return a list of IPv6 routes than can be used by Scapy.""" 224 225 # Call netstat to retrieve IPv6 routes 226 fd_netstat = os.popen("netstat -rn -f inet6") 227 228 # List interfaces IPv6 addresses 229 lifaddr = in6_getifaddr() 230 if not lifaddr: 231 return [] 232 233 # Routes header information 234 got_header = False 235 mtu_present = False 236 prio_present = False 237 238 # Parse the routes 239 routes = [] 240 for line in fd_netstat.readlines(): 241 242 # Parse the routes header and try to identify extra columns 243 if not got_header: 244 if "Destination" == line[:11]: 245 got_header = True 246 mtu_present = "Mtu" in line 247 prio_present = "Prio" in line 248 continue 249 250 # Parse a route entry according to the operating system 251 splitted_line = line.split() 252 if OPENBSD or NETBSD: 253 index = 5 + mtu_present + prio_present 254 if len(splitted_line) < index: 255 warning("Not enough columns in route entry !") 256 continue 257 destination, next_hop, flags = splitted_line[:3] 258 dev = splitted_line[index] 259 else: 260 # FREEBSD or DARWIN 261 if len(splitted_line) < 4: 262 warning("Not enough columns in route entry !") 263 continue 264 destination, next_hop, flags, dev = splitted_line[:4] 265 266 # XXX: TODO: add metrics for unix.py (use -e option on netstat) 267 metric = 1 268 269 # Check flags 270 if not "U" in flags: # usable route 271 continue 272 if "R" in flags: # Host or net unreachable 273 continue 274 if "m" in flags: # multicast address 275 # Note: multicast routing is handled in Route6.route() 276 continue 277 278 # Replace link with the default route in next_hop 279 if "link" in next_hop: 280 next_hop = "::" 281 282 # Default prefix length 283 destination_plen = 128 284 285 # Extract network interface from the zone id 286 if '%' in destination: 287 destination, dev = destination.split('%') 288 if '/' in dev: 289 # Example: fe80::%lo0/64 ; dev = "lo0/64" 290 dev, destination_plen = dev.split('/') 291 if '%' in next_hop: 292 next_hop, dev = next_hop.split('%') 293 294 # Ensure that the next hop is a valid IPv6 address 295 if not in6_isvalid(next_hop): 296 # Note: the 'Gateway' column might contain a MAC address 297 next_hop = "::" 298 299 # Modify parsed routing entries 300 # Note: these rules are OS specific and may evolve over time 301 if destination == "default": 302 destination, destination_plen = "::", 0 303 elif '/' in destination: 304 # Example: fe80::/10 305 destination, destination_plen = destination.split('/') 306 if '/' in dev: 307 # Example: ff02::%lo0/32 ; dev = "lo0/32" 308 dev, destination_plen = dev.split('/') 309 310 # Check route entries parameters consistency 311 if not in6_isvalid(destination): 312 warning("Invalid destination IPv6 address in route entry !") 313 continue 314 try: 315 destination_plen = int(destination_plen) 316 except: 317 warning("Invalid IPv6 prefix length in route entry !") 318 continue 319 if in6_ismlladdr(destination) or in6_ismnladdr(destination): 320 # Note: multicast routing is handled in Route6.route() 321 continue 322 323 if LOOPBACK_NAME in dev: 324 # Handle ::1 separately 325 cset = ["::1"] 326 next_hop = "::" 327 else: 328 # Get possible IPv6 source addresses 329 devaddrs = (x for x in lifaddr if x[2] == dev) 330 cset = construct_source_candidate_set(destination, destination_plen, devaddrs) 331 332 if len(cset): 333 routes.append((destination, destination_plen, next_hop, dev, cset, metric)) 334 335 fd_netstat.close() 336 return routes 337