From 43af87fc49341b5bc26cb51e58992029c45a05cb Mon Sep 17 00:00:00 2001 From: gpotter2 <gabriel@potter.fr> Date: Wed, 29 Mar 2017 18:17:05 +0200 Subject: [PATCH] Improve routes on windows --- scapy/arch/windows/__init__.py | 48 +++++++++++++++++++++++++++++----- scapy/consts.py | 8 ++++++ scapy/layers/l2.py | 4 +-- scapy/route.py | 15 +++++++---- scapy/route6.py | 20 +++++++++----- scapy/utils6.py | 4 +-- test/regression.uts | 11 ++++++++ 7 files changed, 88 insertions(+), 22 deletions(-) diff --git a/scapy/arch/windows/__init__.py b/scapy/arch/windows/__init__.py index a3585e0e..6918f98f 100755 --- a/scapy/arch/windows/__init__.py +++ b/scapy/arch/windows/__init__.py @@ -16,7 +16,7 @@ from scapy.error import Scapy_Exception, log_loading, log_runtime, warning from scapy.utils import atol, itom, inet_aton, inet_ntoa, PcapReader from scapy.base_classes import Gen, Net, SetGen from scapy.data import MTU, ETHER_BROADCAST, ETH_P_ARP -from scapy.consts import LOOPBACK_NAME +from scapy.consts import LOOPBACK_NAME, getLoopbackInterface conf.use_pcap = False conf.use_dnet = False @@ -337,6 +337,7 @@ def get_ip_from_name(ifname, v6=False): ['Description', 'IPAddress']): if descr == ifname.strip(): return ipaddr.split(",", 1)[v6].strip('{}').strip() + return None class NetworkInterface(object): """A network interface of your local host""" @@ -371,6 +372,9 @@ class NetworkInterface(object): try: if not self.ip: self.ip=get_ip_from_name(data['name']) + if not self.ip: + # No IP detected + self.invalid = True except (KeyError, AttributeError, NameError) as e: print e if not self.ip and self.name == LOOPBACK_NAME: @@ -413,6 +417,14 @@ class NetworkInterfaceDict(UserDict): "You probably won't be able to send packets. " "Deactivating unneeded interfaces and restarting Scapy might help." "Check your winpcap and powershell installation, and access rights.", True) + else: + # Loading state: remove invalid interfaces + self.remove_invalid_ifaces() + # Replace interface for getLoopbackInterface() + try: + scapy.consts.LOOPBACK_INTERFACE = self.dev_from_name(LOOPBACK_NAME) + except: + pass def dev_from_name(self, name): """Return the first pcap device name for a given Windows @@ -435,8 +447,19 @@ class NetworkInterfaceDict(UserDict): for devname, iface in self.items(): if iface.win_index == str(if_index): return iface + if str(if_index) == "1": + # Local loopback + loopback = getLoopbackInterface() + if loopback != LOOPBACK_NAME: + return loopback raise ValueError("Unknown network interface index %r" % if_index) + def remove_invalid_ifaces(self): + """Remove all invalid interfaces""" + for devname, iface in self.items(): + if iface.is_invalid(): + self.data.pop(devname) + def show(self, resolve_mac=True): """Print list of available network interfaces in human readable form""" print "%s %s %s %s" % ("INDEX".ljust(5), "IFACE".ljust(35), "IP".ljust(15), "MAC") @@ -635,14 +658,11 @@ def read_routes6(): cset = ['::1'] else: devaddrs = filter(lambda x: x[2] == iface, lifaddr) - cset = scapy.utils6.construct_source_candidate_set(d, dp, devaddrs, LOOPBACK_NAME) + cset = scapy.utils6.construct_source_candidate_set(d, dp, devaddrs, getLoopbackInterface()) # APPEND (DESTINATION, NETMASK, NEXT HOP, IFACE, CANDIDATS) routes.append((d, dp, nh, iface, cset)) return routes - - - if conf.interactive_shell != 'ipython' and conf.interactive: try: __IPYTHON__ @@ -680,12 +700,16 @@ def get_working_if(): return min(read_routes(), key=lambda x: x[1])[3] except ValueError: # no route - return LOOPBACK_NAME + return getLoopbackInterface() conf.iface = get_working_if() def route_add_loopback(routes=None, ipv6=False, iflist=None): """Add a route to 127.0.0.1 and ::1 to simplify unit tests on Windows""" + if not WINDOWS: + warning("Not available") + return + warning("This will completly mess up the routes. Testing purpose only !") # Add only if some adpaters already exist if ipv6: if len(conf.route6.routes) == 0: @@ -699,10 +723,22 @@ def route_add_loopback(routes=None, ipv6=False, iflist=None): data['win_index'] = -1 data['guid'] = "{0XX00000-X000-0X0X-X00X-00XXXX000XXX}" data['invalid'] = True + data['mac'] = '00:00:00:00:00:00' adapter = NetworkInterface(data) if iflist: iflist.append(unicode("\\Device\\NPF_" + adapter.guid)) return + # Remove all LOOPBACK_NAME routes + for route in list(conf.route.routes): + iface = route[3] + if iface.name == LOOPBACK_NAME: + conf.route.routes.remove(route) + # Remove LOOPBACK_NAME interface + for devname, iface in IFACES.items(): + if iface.name == LOOPBACK_NAME: + IFACES.pop(devname) + # Inject interface + IFACES[data['guid']] = adapter # Build the packed network addresses loop_net = struct.unpack("!I", socket.inet_aton("127.0.0.0"))[0] loop_mask = struct.unpack("!I", socket.inet_aton("255.0.0.0"))[0] diff --git a/scapy/consts.py b/scapy/consts.py index cb498333..2e7f2f1a 100644 --- a/scapy/consts.py +++ b/scapy/consts.py @@ -59,6 +59,8 @@ WINDOWS = platform.startswith("win32") BSD = DARWIN or FREEBSD or OPENBSD or NETBSD # See https://docs.python.org/3/library/platform.html#cross-platform IS_64BITS = maxsize > 2**32 +# Windows only +LOOPBACK_INTERFACE = None if WINDOWS: try: @@ -72,5 +74,11 @@ else: uname = os.uname() LOOPBACK_NAME = "lo" if LINUX else "lo0" +def getLoopbackInterface(): + if LOOPBACK_INTERFACE and WINDOWS: + return LOOPBACK_INTERFACE + else: + return LOOPBACK_NAME + def parent_function(): return inspect.getouterframes(inspect.currentframe()) diff --git a/scapy/layers/l2.py b/scapy/layers/l2.py index 9b3292d0..127514d0 100644 --- a/scapy/layers/l2.py +++ b/scapy/layers/l2.py @@ -18,7 +18,7 @@ from scapy.plist import SndRcvList from scapy.fields import * from scapy.sendrecv import srp, srp1, srpflood from scapy.arch import get_if_hwaddr -from scapy.consts import LOOPBACK_NAME +from scapy.consts import LOOPBACK_NAME, getLoopbackInterface from scapy.utils import inet_ntoa, inet_aton from scapy.error import warning if conf.route is None: @@ -63,7 +63,7 @@ def getmacbyip(ip, chainCC=0): if (tmp[0] & 0xf0) == 0xe0: # mcast @ return "01:00:5e:%.2x:%.2x:%.2x" % (tmp[1]&0x7f,tmp[2],tmp[3]) iff,a,gw = conf.route.route(ip) - if ( (iff == LOOPBACK_NAME) or (ip == conf.route.get_if_bcast(iff)) ): + if ( (iff == getLoopbackInterface()) or (ip == conf.route.get_if_bcast(iff)) ): return "ff:ff:ff:ff:ff:ff" if gw != "0.0.0.0": ip = gw diff --git a/scapy/route.py b/scapy/route.py index 017ac7b7..cb216175 100644 --- a/scapy/route.py +++ b/scapy/route.py @@ -8,7 +8,7 @@ Routing and handling of network interfaces. """ import socket -from scapy.consts import LOOPBACK_NAME +from scapy.consts import LOOPBACK_NAME, getLoopbackInterface from scapy.utils import atol, ltoa, itom from scapy.config import conf from scapy.error import Scapy_Exception, warning @@ -32,7 +32,7 @@ class Route: self.routes = read_routes() def __repr__(self): - rtlst = [("Network", "Netmask", "Gateway", "Iface", "Output IP")] + rtlst = [] for net,msk,gw,iface,addr in self.routes: rtlst.append((ltoa(net), @@ -40,6 +40,11 @@ class Route: gw, (iface.name if not isinstance(iface, basestring) else iface), addr)) + + # Sort correctly + rtlst.sort(key=lambda x: x[0]) + # Append tag + rtlst = [("Network", "Netmask", "Gateway", "Iface", "Output IP")] + rtlst colwidth = map(lambda x: max(map(lambda y: len(y), x)), apply(zip, rtlst)) fmt = " ".join(map(lambda x: "%%-%ds"%x, colwidth)) @@ -154,13 +159,13 @@ class Route: continue aa = atol(a) if aa == dst: - pathes.append((0xffffffffL,(LOOPBACK_NAME,a,"0.0.0.0"))) + pathes.append((0xffffffffL,(getLoopbackInterface(),a,"0.0.0.0"))) if (dst & m) == (d & m): pathes.append((m,(i,a,gw))) if not pathes: if verbose: warning("No route found (no default route?)") - return LOOPBACK_NAME,"0.0.0.0","0.0.0.0" #XXX linux specific! + return getLoopbackInterface(),"0.0.0.0","0.0.0.0" # Choose the more specific route (greatest netmask). # XXX: we don't care about metrics pathes.sort() @@ -185,6 +190,6 @@ conf.route=Route() #XXX use "with" _betteriface = conf.route.route("0.0.0.0", verbose=0)[0] -if _betteriface != LOOPBACK_NAME: +if ((_betteriface.name != LOOPBACK_NAME) if WINDOWS else (_betteriface != LOOPBACK_NAME)): conf.iface = _betteriface del(_betteriface) diff --git a/scapy/route6.py b/scapy/route6.py index 8a7cb1ad..7c88d6d5 100644 --- a/scapy/route6.py +++ b/scapy/route6.py @@ -22,6 +22,7 @@ from scapy.utils6 import * from scapy.arch import * from scapy.pton_ntop import * from scapy.error import warning, log_loading +from scapy.consts import getLoopbackInterface class Route6: @@ -46,14 +47,19 @@ class Route6: log_loading.info("No IPv6 support in kernel") def __repr__(self): - rtlst = [('Destination', 'Next Hop', "iface", "src candidates")] + rtlst = [] for net,msk,gw,iface,cset in self.routes: rtlst.append(('%s/%i'% (net,msk), gw, (iface if isinstance(iface, basestring) else iface.name), ", ".join(cset))) - colwidth = map(lambda x: max(map(lambda y: len(y), x)), apply(zip, rtlst)) - fmt = " ".join(map(lambda x: "%%-%ds"%x, colwidth)) - rt = "\n".join(map(lambda x: fmt % x, rtlst)) + # Sort correctly + rtlst.sort(key=lambda x: x[0]) + # Append tag + rtlst = [('Destination', 'Next Hop', "iface", "src candidates")] + rtlst + + colwidth = [max([len(y) for y in x]) for x in zip(*rtlst)] + fmt = " ".join(["%%-%ds"%x for x in colwidth]) + rt = "\n".join([fmt % x for x in rtlst]) return rt @@ -77,7 +83,7 @@ class Route6: # replace that unique address by the list of all addresses lifaddr = in6_getifaddr() devaddrs = filter(lambda x: x[2] == dev, lifaddr) - ifaddr = construct_source_candidate_set(prefix, plen, devaddrs, LOOPBACK_NAME) + ifaddr = construct_source_candidate_set(prefix, plen, devaddrs, getLoopbackInterface()) return (prefix, plen, gw, dev, ifaddr) @@ -220,7 +226,7 @@ class Route6: if not pathes: warning("No route found for IPv6 destination %s (no default route?)" % dst) - return (LOOPBACK_NAME, "::", "::") # XXX Linux specific + return (getLoopbackInterface(), "::", "::") # Sort with longest prefix first pathes.sort(reverse=True) @@ -237,7 +243,7 @@ class Route6: if res == []: warning("Found a route for IPv6 destination '%s', but no possible source address." % dst) - return (LOOPBACK_NAME, "::", "::") # XXX Linux specific + return (getLoopbackInterface(), "::", "::") # Symptom : 2 routes with same weight (our weight is plen) # Solution : diff --git a/scapy/utils6.py b/scapy/utils6.py index cb78d2cf..99292911 100644 --- a/scapy/utils6.py +++ b/scapy/utils6.py @@ -21,7 +21,7 @@ from scapy.volatile import RandMAC from scapy.error import warning -def construct_source_candidate_set(addr, plen, laddr, loname): +def construct_source_candidate_set(addr, plen, laddr, loiface): """ Given all addresses assigned to a specific interface ('laddr' parameter), this function returns the "candidate set" associated with 'addr/plen'. @@ -57,7 +57,7 @@ def construct_source_candidate_set(addr, plen, laddr, loname): cset = filter(lambda x: x[1] == IPV6_ADDR_SITELOCAL, laddr) elif in6_ismaddr(addr): if in6_ismnladdr(addr): - cset = [('::1', 16, loname)] + cset = [('::1', 16, loiface)] elif in6_ismgladdr(addr): cset = filter(lambda x: x[1] == IPV6_ADDR_GLOBAL, laddr) elif in6_ismlladdr(addr): diff --git a/test/regression.uts b/test/regression.uts index 80de536c..0f17307e 100644 --- a/test/regression.uts +++ b/test/regression.uts @@ -11,6 +11,8 @@ * Dump the current configuration conf +IP().src + = List layers ~ conf command ls() @@ -92,6 +94,15 @@ else: conf.route6.ifchange(LOOPBACK_NAME, "::1/128") True += UTscapy route check +* Check that UTscapy has correctly replaced the routes. Many tests won't work otherwise + +if WINDOWS: + route_add_loopback() + +IP().src +assert _ == "127.0.0.1" + ############ ############ + Main.py tests -- GitLab