unix.py revision 291400c1b6f65363e33cc982aaf0d43d31cc424e
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 if not "G" in flg: 98 gw = '0.0.0.0' 99 if netif is not None: 100 try: 101 ifaddr = get_if_addr(netif) 102 routes.append((dest,netmask,gw,netif,ifaddr)) 103 except OSError as exc: 104 if exc.message == 'Device not configured': 105 # This means the interface name is probably truncated by 106 # netstat -nr. We attempt to guess it's name and if not we 107 # ignore it. 108 guessed_netif = _guess_iface_name(netif) 109 if guessed_netif is not None: 110 ifaddr = get_if_addr(guessed_netif) 111 routes.append((dest, netmask, gw, guessed_netif, ifaddr)) 112 else: 113 warning("Could not guess partial interface name: %s" % netif) 114 else: 115 raise 116 else: 117 pending_if.append((dest,netmask,gw)) 118 f.close() 119 120 # On Solaris, netstat does not provide output interfaces for some routes 121 # We need to parse completely the routing table to route their gw and 122 # know their output interface 123 for dest,netmask,gw in pending_if: 124 gw_l = scapy.utils.atol(gw) 125 max_rtmask,gw_if,gw_if_addr, = 0,None,None 126 for rtdst,rtmask,_,rtif,rtaddr in routes[:]: 127 if gw_l & rtmask == rtdst: 128 if rtmask >= max_rtmask: 129 max_rtmask = rtmask 130 gw_if = rtif 131 gw_if_addr = rtaddr 132 if gw_if: 133 routes.append((dest,netmask,gw,gw_if,gw_if_addr)) 134 else: 135 warning("Did not find output interface to reach gateway %s" % gw) 136 137 return routes 138 139############ 140### IPv6 ### 141############ 142 143def _in6_getifaddr(ifname): 144 """ 145 Returns a list of IPv6 addresses configured on the interface ifname. 146 """ 147 148 # Get the output of ifconfig 149 try: 150 f = os.popen("%s %s" % (conf.prog.ifconfig, ifname)) 151 except OSError,msg: 152 log_interactive.warning("Failed to execute ifconfig.") 153 return [] 154 155 # Iterate over lines and extract IPv6 addresses 156 ret = [] 157 for line in f: 158 if "inet6" in line: 159 addr = line.rstrip().split(None, 2)[1] # The second element is the IPv6 address 160 else: 161 continue 162 if '%' in line: # Remove the interface identifier if present 163 addr = addr.split("%", 1)[0] 164 165 # Check if it is a valid IPv6 address 166 try: 167 socket.inet_pton(socket.AF_INET6, addr) 168 except: 169 continue 170 171 # Get the scope and keep the address 172 scope = in6_getscope(addr) 173 ret.append((addr, scope, ifname)) 174 175 return ret 176 177def in6_getifaddr(): 178 """ 179 Returns a list of 3-tuples of the form (addr, scope, iface) where 180 'addr' is the address of scope 'scope' associated to the interface 181 'iface'. 182 183 This is the list of all addresses of all interfaces available on 184 the system. 185 """ 186 187 # List all network interfaces 188 if OPENBSD: 189 try: 190 f = os.popen("%s" % conf.prog.ifconfig) 191 except OSError,msg: 192 log_interactive.warning("Failed to execute ifconfig.") 193 return [] 194 195 # Get the list of network interfaces 196 splitted_line = [] 197 for l in f: 198 if "flags" in l: 199 iface = l.split()[0].rstrip(':') 200 splitted_line.append(iface) 201 202 else: # FreeBSD, NetBSD or Darwin 203 try: 204 f = os.popen("%s -l" % conf.prog.ifconfig) 205 except OSError,msg: 206 log_interactive.warning("Failed to execute ifconfig.") 207 return [] 208 209 # Get the list of network interfaces 210 splitted_line = f.readline().rstrip().split() 211 212 ret = [] 213 for i in splitted_line: 214 ret += _in6_getifaddr(i) 215 return ret 216 217 218def read_routes6(): 219 """Return a list of IPv6 routes than can be used by Scapy.""" 220 221 # Call netstat to retrieve IPv6 routes 222 fd_netstat = os.popen("netstat -rn -f inet6") 223 224 # List interfaces IPv6 addresses 225 lifaddr = in6_getifaddr() 226 if not lifaddr: 227 return [] 228 229 # Routes header information 230 got_header = False 231 mtu_present = False 232 prio_present = False 233 234 # Parse the routes 235 routes = [] 236 for line in fd_netstat.readlines(): 237 238 # Parse the routes header and try to identify extra columns 239 if not got_header: 240 if "Destination" == line[:11]: 241 got_header = True 242 mtu_present = "Mtu" in line 243 prio_present = "Prio" in line 244 continue 245 246 # Parse a route entry according to the operating system 247 splitted_line = line.split() 248 if OPENBSD or NETBSD: 249 index = 5 + mtu_present + prio_present 250 if len(splitted_line) < index: 251 warning("Not enough columns in route entry !") 252 continue 253 destination, next_hop, flags = splitted_line[:3] 254 dev = splitted_line[index] 255 else: 256 # FREEBSD or DARWIN 257 if len(splitted_line) < 4: 258 warning("Not enough columns in route entry !") 259 continue 260 destination, next_hop, flags, dev = splitted_line[:4] 261 262 # Check flags 263 if not "U" in flags: # usable route 264 continue 265 if "R" in flags: # Host or net unreachable 266 continue 267 if "m" in flags: # multicast address 268 # Note: multicast routing is handled in Route6.route() 269 continue 270 271 # Replace link with the default route in next_hop 272 if "link" in next_hop: 273 next_hop = "::" 274 275 # Default prefix length 276 destination_plen = 128 277 278 # Extract network interface from the zone id 279 if '%' in destination: 280 destination, dev = destination.split('%') 281 if '/' in dev: 282 # Example: fe80::%lo0/64 ; dev = "lo0/64" 283 dev, destination_plen = dev.split('/') 284 if '%' in next_hop: 285 next_hop, dev = next_hop.split('%') 286 287 # Ensure that the next hop is a valid IPv6 address 288 if not in6_isvalid(next_hop): 289 # Note: the 'Gateway' column might contain a MAC address 290 next_hop = "::" 291 292 # Modify parsed routing entries 293 # Note: these rules are OS specific and may evolve over time 294 if destination == "default": 295 destination, destination_plen = "::", 0 296 elif '/' in destination: 297 # Example: fe80::/10 298 destination, destination_plen = destination.split('/') 299 if '/' in dev: 300 # Example: ff02::%lo0/32 ; dev = "lo0/32" 301 dev, destination_plen = dev.split('/') 302 303 # Check route entries parameters consistency 304 if not in6_isvalid(destination): 305 warning("Invalid destination IPv6 address in route entry !") 306 continue 307 try: 308 destination_plen = int(destination_plen) 309 except: 310 warning("Invalid IPv6 prefix length in route entry !") 311 continue 312 if in6_ismlladdr(destination) or in6_ismnladdr(destination): 313 # Note: multicast routing is handled in Route6.route() 314 continue 315 316 if LOOPBACK_NAME in dev: 317 # Handle ::1 separately 318 cset = ["::1"] 319 next_hop = "::" 320 else: 321 # Get possible IPv6 source addresses 322 devaddrs = filter(lambda x: x[2] == dev, lifaddr) 323 cset = construct_source_candidate_set(destination, destination_plen, devaddrs, LOOPBACK_NAME) 324 325 if len(cset): 326 routes.append((destination, destination_plen, next_hop, dev, cset)) 327 328 fd_netstat.close() 329 return routes 330