diff --git a/scapy/arch/linux.py b/scapy/arch/linux.py index a75ab448dbfe53a607d5b1385531c3f99fe7eb66..fed3efc52b6b9814627819bf0f925dcce242afee 100644 --- a/scapy/arch/linux.py +++ b/scapy/arch/linux.py @@ -9,6 +9,7 @@ import sys,os,struct,socket,time from select import select from fcntl import ioctl import scapy.utils +import scapy.utils6 from scapy.config import conf from scapy.data import * from scapy.supersocket import SuperSocket @@ -203,7 +204,7 @@ def in6_getifaddr(): # addr, index, plen, scope, flags, ifname tmp = i.split() addr = struct.unpack('4s4s4s4s4s4s4s4s', tmp[0]) - addr = in6_ptop(':'.join(addr)) + addr = scapy.utils6.in6_ptop(':'.join(addr)) ret.append((addr, int(tmp[3], 16), tmp[5])) # (addr, scope, iface) return ret @@ -226,7 +227,7 @@ def read_routes6(): def proc2r(p): ret = struct.unpack('4s4s4s4s4s4s4s4s', p) ret = ':'.join(ret) - return in6_ptop(ret) + return scapy.utils6.in6_ptop(ret) lifaddr = in6_getifaddr() for l in f.readlines(): diff --git a/scapy/arch/unix.py b/scapy/arch/unix.py index dc10077529ce0266e91a75078c752c9a24058de0..90b740715682264277754240b025e8315e321533 100644 --- a/scapy/arch/unix.py +++ b/scapy/arch/unix.py @@ -9,6 +9,7 @@ from fcntl import ioctl from scapy.error import warning import scapy.config import scapy.utils +import scapy.utils6 import scapy.arch scapy.config.conf.use_pcap = 1 @@ -131,9 +132,9 @@ def in6_getifaddr(): continue xx = str(a).split('/')[0] - addr = in6_ptop(xx) + addr = scapy.utils6.in6_ptop(xx) - scope = in6_getscope(addr) + scope = scapy.utils6.in6_getscope(addr) ret.append((xx, scope, ifname)) return ret diff --git a/scapy/layers/inet6.py b/scapy/layers/inet6.py index 60f548593f7264114d161005b189d5844b0a9676..f23db6ee943034c49ff3bb144a9824d6a753887a 100644 --- a/scapy/layers/inet6.py +++ b/scapy/layers/inet6.py @@ -31,6 +31,7 @@ from scapy.sendrecv import sr,sr1,srp1 from scapy.as_resolvers import AS_resolver_riswhois from scapy.supersocket import SuperSocket,L3RawSocket from scapy.arch import * +from scapy.utils6 import * ############################################################################# @@ -564,622 +565,9 @@ class Net6(Gen): # syntax ex. fec0::/126 return "<Net6 %s>" % self.repr -# Think before modify it : for instance, FE::1 does exist and is unicast -# there are many others like that. -# TODO : integrate Unique Local Addresses -def in6_getAddrType(addr): - naddr = inet_pton(socket.AF_INET6, addr) - paddr = inet_ntop(socket.AF_INET6, naddr) # normalize - addrType = 0 - # _Assignable_ Global Unicast Address space - # is defined in RFC 3513 as those in 2000::/3 - if ((struct.unpack("B", naddr[0])[0] & 0xE0) == 0x20): - addrType = (IPV6_ADDR_UNICAST | IPV6_ADDR_GLOBAL) - if naddr[:2] == ' \x02': # Mark 6to4 @ - addrType |= IPV6_ADDR_6TO4 - elif naddr[0] == '\xff': # multicast - addrScope = paddr[3] - if addrScope == '2': - addrType = (IPV6_ADDR_LINKLOCAL | IPV6_ADDR_MULTICAST) - elif addrScope == 'e': - addrType = (IPV6_ADDR_GLOBAL | IPV6_ADDR_MULTICAST) - else: - addrType = (IPV6_ADDR_GLOBAL | IPV6_ADDR_MULTICAST) - elif ((naddr[0] == '\xfe') and ((int(paddr[2], 16) & 0xC) == 0x8)): - addrType = (IPV6_ADDR_UNICAST | IPV6_ADDR_LINKLOCAL) - elif paddr == "::1": - addrType = IPV6_ADDR_LOOPBACK - elif paddr == "::": - addrType = IPV6_ADDR_UNSPECIFIED - else: - # Everything else is global unicast (RFC 3513) - # Even old deprecated (RFC3879) Site-Local addresses - addrType = (IPV6_ADDR_GLOBAL | IPV6_ADDR_UNICAST) - - return addrType - -def find_ifaddr2(addr, plen, laddr): - dstAddrType = in6_getAddrType(addr) - - if dstAddrType == IPV6_ADDR_UNSPECIFIED: # Shouldn't happen as dst addr - return None - - if dstAddrType == IPV6_ADDR_LOOPBACK: - return None - - tmp = [[]] + map(lambda (x,y,z): (in6_getAddrType(x), x, y, z), laddr) - def filterSameScope(l, t): - if (t[0] & dstAddrType & IPV6_ADDR_SCOPE_MASK) == 0: - l.append(t) - return l - sameScope = reduce(filterSameScope, tmp) - - l = len(sameScope) - if l == 1: # Only one address for our scope - return sameScope[0][1] - - elif l > 1: # Muliple addresses for our scope - stfAddr = filter(lambda x: x[0] & IPV6_ADDR_6TO4, sameScope) - nativeAddr = filter(lambda x: not (x[0] & IPV6_ADDR_6TO4), sameScope) - - if not (dstAddrType & IPV6_ADDR_6TO4): # destination is not 6to4 - if len(nativeAddr) != 0: - return nativeAddr[0][1] - return stfAddr[0][1] - - else: # Destination is 6to4, try to use source 6to4 addr if any - if len(stfAddr) != 0: - return stfAddr[0][1] - return nativeAddr[0][1] - else: - return None - - -def in6_mactoifaceid(mac, ulbit=None): - """ - Compute the interface ID in modified EUI-64 format associated - to the Ethernet address provided as input. - value taken by U/L bit in the interface identifier is basically - the reversed value of that in given MAC address it can be forced - to a specific value by using optional 'ulbit' parameter. - """ - if len(mac) != 17: return None - m = "".join(mac.split(':')) - if len(m) != 12: return None - first = int(m[0:2], 16) - if ulbit is None or not (ulbit == 0 or ulbit == 1): - ulbit = [1,'-',0][first & 0x02] - ulbit *= 2 - first = "%.02x" % ((first & 0xFD) | ulbit) - eui64 = first + m[2:4] + ":" + m[4:6] + "FF:FE" + m[6:8] + ":" + m[8:12] - return eui64.upper() - -def in6_ifaceidtomac(ifaceid): # TODO: finish commenting function behavior - """ - Extract the mac address from provided iface ID. Iface ID is provided - in printable format ("XXXX:XXFF:FEXX:XXXX", eventually compressed). None - is returned on error. - """ - try: - ifaceid = inet_pton(socket.AF_INET6, "::"+ifaceid)[8:16] - except: - return None - if ifaceid[3:5] != '\xff\xfe': - return None - first = struct.unpack("B", ifaceid[:1])[0] - ulbit = 2*[1,'-',0][first & 0x02] - first = struct.pack("B", ((first & 0xFD) | ulbit)) - oui = first + ifaceid[1:3] - end = ifaceid[5:] - l = map(lambda x: "%.02x" % struct.unpack("B", x)[0], list(oui+end)) - return ":".join(l) - -def in6_addrtomac(addr): - """ - Extract the mac address from provided address. None is returned - on error. - """ - mask = inet_pton(socket.AF_INET6, "::ffff:ffff:ffff:ffff") - x = in6_and(mask, inet_pton(socket.AF_INET6, addr)) - ifaceid = inet_ntop(socket.AF_INET6, x)[2:] - return in6_ifaceidtomac(ifaceid) - -def in6_addrtovendor(addr): - """ - Extract the MAC address from a modified EUI-64 constructed IPv6 - address provided and use the IANA oui.txt file to get the vendor. - The database used for the conversion is the one loaded by Scapy, - based on Wireshark (/usr/share/wireshark/wireshark/manuf) None - is returned on error, "UNKNOWN" if the vendor is unknown. - """ - mac = in6_addrtomac(addr) - if mac is None: - return None - - res = conf.manufdb._get_manuf(mac) - if len(res) == 17 and res.count(':') != 5: # Mac address, i.e. unknown - res = "UNKNOWN" - - return res - -def in6_getLinkScopedMcastAddr(addr, grpid=None, scope=2): - """ - Generate a Link-Scoped Multicast Address as described in RFC 4489. - Returned value is in printable notation. - - 'addr' parameter specifies the link-local address to use for generating - Link-scoped multicast address IID. - - By default, the function returns a ::/96 prefix (aka last 32 bits of - returned address are null). If a group id is provided through 'grpid' - parameter, last 32 bits of the address are set to that value (accepted - formats : '\x12\x34\x56\x78' or '12345678' or 0x12345678 or 305419896). - - By default, generated address scope is Link-Local (2). That value can - be modified by passing a specific 'scope' value as an argument of the - function. RFC 4489 only authorizes scope values <= 2. Enforcement - is performed by the function (None will be returned). - - If no link-local address can be used to generate the Link-Scoped IPv6 - Multicast address, or if another error occurs, None is returned. - """ - if not scope in [0, 1, 2]: - return None - try: - if not in6_islladdr(addr): - return None - addr = inet_pton(socket.AF_INET6, addr) - except: - warning("in6_getLinkScopedMcastPrefix(): Invalid address provided") - return None - - iid = addr[8:] - - if grpid is None: - grpid = '\x00\x00\x00\x00' - else: - if type(grpid) is str: - if len(grpid) == 8: - try: - grpid = int(grpid, 16) & 0xffffffff - except: - warning("in6_getLinkScopedMcastPrefix(): Invalid group id provided") - return None - elif len(grpid) == 4: - try: - grpid = struct.unpack("!I", grpid)[0] - except: - warning("in6_getLinkScopedMcastPrefix(): Invalid group id provided") - return None - grpid = struct.pack("!I", grpid) - - flgscope = struct.pack("B", 0xff & ((0x3 << 4) | scope)) - plen = '\xff' - res = '\x00' - a = '\xff' + flgscope + res + plen + iid + grpid - return inet_ntop(socket.AF_INET6, a) -def in6_get6to4Prefix(addr): - """ - Returns the /48 6to4 prefix associated with provided IPv4 address - On error, None is returned. No check is performed on public/private - status of the address - """ - try: - addr = inet_pton(socket.AF_INET, addr) - addr = inet_ntop(socket.AF_INET6, '\x20\x02'+addr+'\x00'*10) - except: - return None - return addr -def in6_6to4ExtractAddr(addr): - """ - Extract IPv4 address embbeded in 6to4 address. Passed address must be - a 6to4 addrees. None is returned on error. - """ - try: - addr = inet_pton(socket.AF_INET6, addr) - except: - return None - if addr[:2] != " \x02": - return None - return inet_ntop(socket.AF_INET, addr[2:6]) - - -def in6_getLocalUniquePrefix(): - """ - Returns a pseudo-randomly generated Local Unique prefix. Function - follows recommandation of Section 3.2.2 of RFC 4193 for prefix - generation. - """ - # Extracted from RFC 1305 (NTP) : - # NTP timestamps are represented as a 64-bit unsigned fixed-point number, - # in seconds relative to 0h on 1 January 1900. The integer part is in the - # first 32 bits and the fraction part in the last 32 bits. - - # epoch = (1900, 1, 1, 0, 0, 0, 5, 1, 0) - # x = time.time() - # from time import gmtime, strftime, gmtime, mktime - # delta = mktime(gmtime(0)) - mktime(self.epoch) - # x = x-delta - - tod = time.time() # time of day. Will bother with epoch later - i = int(tod) - j = int((tod - i)*(2**32)) - tod = struct.pack("!II", i,j) - # TODO: Add some check regarding system address gathering - rawmac = get_if_raw_hwaddr(conf.iface6)[1] - mac = ":".join(map(lambda x: "%.02x" % ord(x), list(rawmac))) - # construct modified EUI-64 ID - eui64 = inet_pton(socket.AF_INET6, '::' + in6_mactoifaceid(mac))[8:] - import sha - globalid = sha.new(tod+eui64).digest()[:5] - return inet_ntop(socket.AF_INET6, '\xfd' + globalid + '\x00'*10) - -def in6_getRandomizedIfaceId(ifaceid, previous=None): - """ - Implements the interface ID generation algorithm described in RFC 3041. - The function takes the Modified EUI-64 interface identifier generated - as described in RFC 4291 and an optional previous history value (the - first element of the output of this function). If no previous interface - identifier is provided, a random one is generated. The function returns - a tuple containing the randomized interface identifier and the history - value (for possible future use). Input and output values are provided in - a "printable" format as depicted below. - - ex: - - >>> in6_getRandomizedIfaceId('20b:93ff:feeb:2d3') - ('4c61:76ff:f46a:a5f3', 'd006:d540:db11:b092') - - >>> in6_getRandomizedIfaceId('20b:93ff:feeb:2d3', - previous='d006:d540:db11:b092') - ('fe97:46fe:9871:bd38', 'eeed:d79c:2e3f:62e') - """ - - s = "" - if previous is None: - d = "".join(map(chr, range(256))) - for i in range(8): - s += random.choice(d) - previous = s - s = inet_pton(socket.AF_INET6, "::"+ifaceid)[8:] + previous - import md5 - s = md5.new(s).digest() - s1,s2 = s[:8],s[8:] - s1 = chr(ord(s1[0]) | 0x04) + s1[1:] - s1 = inet_ntop(socket.AF_INET6, "\xff"*8 + s1)[20:] - s2 = inet_ntop(socket.AF_INET6, "\xff"*8 + s2)[20:] - return (s1, s2) - - -_rfc1924map = [ '0','1','2','3','4','5','6','7','8','9','A','B','C','D','E', - 'F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T', - 'U','V','W','X','Y','Z','a','b','c','d','e','f','g','h','i', - 'j','k','l','m','n','o','p','q','r','s','t','u','v','w','x', - 'y','z','!','#','$','%','&','(',')','*','+','-',';','<','=', - '>','?','@','^','_','`','{','|','}','~' ] - -def in6_ctop(addr): - """ - Convert an IPv6 address in Compact Representation Notation - (RFC 1924) to printable representation ;-) - Returns None on error. - """ - if len(addr) != 20 or not reduce(lambda x,y: x and y, - map(lambda x: x in _rfc1924map, addr)): - return None - i = 0 - for c in addr: - j = _rfc1924map.index(c) - i = 85*i + j - res = [] - for j in range(4): - res.append(struct.pack("!I", i%2**32)) - i = i/(2**32) - res.reverse() - return inet_ntop(socket.AF_INET6, "".join(res)) - -def in6_ptoc(addr): - """ - Converts an IPv6 address in printable representation to RFC - 1924 Compact Representation ;-) - Returns None on error. - """ - try: - d=struct.unpack("!IIII", inet_pton(socket.AF_INET6, addr)) - except: - return None - res = 0 - m = [2**96, 2**64, 2**32, 1] - for i in range(4): - res += d[i]*m[i] - rem = res - res = [] - while rem: - res.append(_rfc1924map[rem%85]) - rem = rem/85 - res.reverse() - return "".join(res) - - -def in6_isaddr6to4(x): - """ - Return True if provided address (in printable format) is a 6to4 - address (being in 2002::/16). - """ - x = inet_pton(socket.AF_INET6, x) - return x[:2] == ' \x02' - -conf.teredoPrefix = "2001::" # old one was 3ffe:831f (it is a /32) -conf.teredoServerPort = 3544 - -def in6_isaddrTeredo(x): - """ - Return True if provided address is a Teredo, meaning it is under - the /32 conf.teredoPrefix prefix value (by default, 2001::). - Otherwise, False is returned. Address must be passed in printable - format. - """ - our = inet_pton(socket.AF_INET6, x)[0:4] - teredoPrefix = inet_pton(socket.AF_INET6, conf.teredoPrefix)[0:4] - return teredoPrefix == our - -def teredoAddrExtractInfo(x): - """ - Extract information from a Teredo address. Return value is - a 4-tuple made of IPv4 address of Teredo server, flag value (int), - mapped address (non obfuscated) and mapped port (non obfuscated). - No specific checks are performed on passed address. - """ - addr = inet_pton(socket.AF_INET6, x) - server = inet_ntop(socket.AF_INET, addr[4:8]) - flag = struct.unpack("!H",addr[8:10])[0] - mappedport = struct.unpack("!H",strxor(addr[10:12],'\xff'*2))[0] - mappedaddr = inet_ntop(socket.AF_INET, strxor(addr[12:16],'\xff'*4)) - return server, flag, mappedaddr, mappedport - -def in6_iseui64(x): - """ - Return True if provided address has an interface identifier part - created in modified EUI-64 format (meaning it matches *::*:*ff:fe*:*). - Otherwise, False is returned. Address must be passed in printable - format. - """ - eui64 = inet_pton(socket.AF_INET6, '::ff:fe00:0') - x = in6_and(inet_pton(socket.AF_INET6, x), eui64) - return x == eui64 - -def in6_isanycast(x): # RFC 2526 - if in6_iseui64(x): - s = '::fdff:ffff:ffff:ff80' - x = in6_and(x, inet_pton(socket.AF_INET6, '::ffff:ffff:ffff:ff80')) - x = in6_and(x, inet_pton(socket.AF_INET6, s)) - return x == inet_pton(socket.AF_INET6, s) - else: - # not EUI-64 - #| n bits | 121-n bits | 7 bits | - #+---------------------------------+------------------+------------+ - #| subnet prefix | 1111111...111111 | anycast ID | - #+---------------------------------+------------------+------------+ - # | interface identifier field | - warning('in6_isanycast(): TODO not EUI-64') - return 0 - -def _in6_bitops(a1, a2, operator=0): - a1 = struct.unpack('4I', a1) - a2 = struct.unpack('4I', a2) - fop = [ lambda x,y: x | y, - lambda x,y: x & y, - lambda x,y: x ^ y - ] - ret = map(fop[operator%len(fop)], a1, a2) - t = ''.join(map(lambda x: struct.pack('I', x), ret)) - return t - -def in6_or(a1, a2): - """ - Provides a bit to bit OR of provided addresses. They must be - passed in network format. Return value is also an IPv6 address - in network format. - """ - return _in6_bitops(a1, a2, 0) - -def in6_and(a1, a2): - """ - Provides a bit to bit AND of provided addresses. They must be - passed in network format. Return value is also an IPv6 address - in network format. - """ - return _in6_bitops(a1, a2, 1) - -def in6_xor(a1, a2): - """ - Provides a bit to bit XOR of provided addresses. They must be - passed in network format. Return value is also an IPv6 address - in network format. - """ - return _in6_bitops(a1, a2, 2) - -def in6_cidr2mask(m): - """ - Return the mask (bitstring) associated with provided length - value. For instance if function is called on 48, return value is - '\xff\xff\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'. - - """ - if m > 128 or m < 0: - raise Scapy_Exception("value provided to in6_cidr2mask outside [0, 128] domain (%d)" % m) - - t = [] - for i in xrange(0, 4): - t.append(max(0, 2**32 - 2**(32-min(32, m)))) - m -= 32 - - return ''.join(map(lambda x: struct.pack('!I', x), t)) - -def in6_getnsma(a): - """ - Return link-local solicited-node multicast address for given - address. Passed address must be provided in network format. - Returned value is also in network format. - """ - - r = in6_and(a, inet_pton(socket.AF_INET6, '::ff:ffff')) - r = in6_or(inet_pton(socket.AF_INET6, 'ff02::1:ff00:0'), r) - return r - -def in6_getnsmac(a): # return multicast Ethernet address associated with multicast v6 destination - """ - Return the multicast mac address associated with provided - IPv6 address. Passed address must be in network format. - """ - - a = struct.unpack('16B', a)[-4:] - mac = '33:33:' - mac += ':'.join(map(lambda x: '%.2x' %x, a)) - return mac - -def in6_getha(prefix): - """ - Return the anycast address associated with all home agents on a given - subnet. - """ - r = in6_and(inet_pton(socket.AF_INET6, prefix), in6_cidr2mask(64)) - r = in6_or(r, inet_pton(socket.AF_INET6, '::fdff:ffff:ffff:fffe')) - return inet_ntop(socket.AF_INET6, r) - -def in6_ptop(str): - """ - Normalizes IPv6 addresses provided in printable format, returning the - same address in printable format. (2001:0db8:0:0::1 -> 2001:db8::1) - """ - return inet_ntop(socket.AF_INET6, inet_pton(socket.AF_INET6, str)) - -def in6_isincluded(addr, prefix, plen): - """ - Returns True when 'addr' belongs to prefix/plen. False otherwise. - """ - temp = inet_pton(socket.AF_INET6, addr) - pref = in6_cidr2mask(plen) - zero = inet_pton(socket.AF_INET6, prefix) - return zero == in6_and(temp, pref) - -def in6_isdocaddr(str): - """ - Returns True if provided address in printable format belongs to - 2001:db8::/32 address space reserved for documentation (as defined - in RFC 3849). - """ - return in6_isincluded(str, '2001:db8::', 32) - -def in6_islladdr(str): - """ - Returns True if provided address in printable format belongs to - _allocated_ link-local unicast address space (fe80::/10) - """ - return in6_isincluded(str, 'fe80::', 10) - -def in6_issladdr(str): - """ - Returns True if provided address in printable format belongs to - _allocated_ site-local address space (fec0::/10). This prefix has - been deprecated, address being now reserved by IANA. Function - will remain for historic reasons. - """ - return in6_isincluded(str, 'fec0::', 10) - -def in6_isuladdr(str): - """ - Returns True if provided address in printable format belongs to - Unique local address space (fc00::/7). - """ - return in6_isincluded(str, 'fc::', 7) - -# TODO : we should see the status of Unique Local addresses against -# global address space. -# Up-to-date information is available through RFC 3587. -# We should review function behavior based on its content. -def in6_isgladdr(str): - """ - Returns True if provided address in printable format belongs to - _allocated_ global address space (2000::/3). Please note that, - Unique Local addresses (FC00::/7) are not part of global address - space, and won't match. - """ - return in6_isincluded(str, '2000::', 3) - -def in6_ismaddr(str): - """ - Returns True if provided address in printable format belongs to - allocated Multicast address space (ff00::/8). - """ - return in6_isincluded(str, 'ff00::', 8) - -def in6_ismnladdr(str): - """ - Returns True if address belongs to node-local multicast address - space (ff01::/16) as defined in RFC - """ - return in6_isincluded(str, 'ff01::', 16) - -def in6_ismgladdr(str): - """ - Returns True if address belongs to global multicast address - space (ff0e::/16). - """ - return in6_isincluded(str, 'ff0e::', 16) - -def in6_ismlladdr(str): - """ - Returns True if address balongs to link-local multicast address - space (ff02::/16) - """ - return in6_isincluded(str, 'ff02::', 16) - -def in6_ismsladdr(str): - """ - Returns True if address belongs to site-local multicast address - space (ff05::/16). Site local address space has been deprecated. - Function remains for historic reasons. - """ - return in6_isincluded(str, 'ff05::', 16) - -def in6_isaddrllallnodes(str): - """ - Returns True if address is the link-local all-nodes multicast - address (ff02::1). - """ - return (inet_pton(socket.AF_INET6, "ff02::1") == - inet_pton(socket.AF_INET6, str)) - -def in6_isaddrllallservers(str): - """ - Returns True if address is the link-local all-servers multicast - address (ff02::2). - """ - return (inet_pton(socket.AF_INET6, "ff02::2") == - inet_pton(socket.AF_INET6, str)) - - -def in6_getscope(addr): - """ - Returns the scope of the address. - """ - if in6_isgladdr(addr): - scope = IPV6_ADDR_GLOBAL - elif in6_islladdr(addr): - scope = IPV6_ADDR_LINKLOCAL - elif in6_issladdr(addr): - scope = IPV6_ADDR_SITELOCAL - elif in6_ismaddr(addr): - scope = IPV6_ADDR_MULTICAST - elif addr == '::1': - scope = IPV6_ADDR_LOOPBACK - else: - scope = -1 - return scope ############################################################################# ############################################################################# diff --git a/scapy/utils6.py b/scapy/utils6.py new file mode 100644 index 0000000000000000000000000000000000000000..c3d67e38dd64e51ccb2ce66e2a3b76a66a979ada --- /dev/null +++ b/scapy/utils6.py @@ -0,0 +1,630 @@ +## This file is part of Scapy +## See http://www.secdev.org/projects/scapy for more informations +## Copyright (C) Philippe Biondi <phil@secdev.org> +## This program is published under a GPLv2 license + +## Copyright (C) 2005 Guillaume Valadon <guedou@hongo.wide.ad.jp> +## Arnaud Ebalard <arnaud.ebalard@eads.net> + + +from config import conf +from data import * +from utils import * + + + +def find_ifaddr2(addr, plen, laddr): + dstAddrType = in6_getAddrType(addr) + + if dstAddrType == IPV6_ADDR_UNSPECIFIED: # Shouldn't happen as dst addr + return None + + if dstAddrType == IPV6_ADDR_LOOPBACK: + return None + + tmp = [[]] + map(lambda (x,y,z): (in6_getAddrType(x), x, y, z), laddr) + def filterSameScope(l, t): + if (t[0] & dstAddrType & IPV6_ADDR_SCOPE_MASK) == 0: + l.append(t) + return l + sameScope = reduce(filterSameScope, tmp) + + l = len(sameScope) + if l == 1: # Only one address for our scope + return sameScope[0][1] + + elif l > 1: # Muliple addresses for our scope + stfAddr = filter(lambda x: x[0] & IPV6_ADDR_6TO4, sameScope) + nativeAddr = filter(lambda x: not (x[0] & IPV6_ADDR_6TO4), sameScope) + + if not (dstAddrType & IPV6_ADDR_6TO4): # destination is not 6to4 + if len(nativeAddr) != 0: + return nativeAddr[0][1] + return stfAddr[0][1] + + else: # Destination is 6to4, try to use source 6to4 addr if any + if len(stfAddr) != 0: + return stfAddr[0][1] + return nativeAddr[0][1] + else: + return None + +# Think before modify it : for instance, FE::1 does exist and is unicast +# there are many others like that. +# TODO : integrate Unique Local Addresses +def in6_getAddrType(addr): + naddr = inet_pton(socket.AF_INET6, addr) + paddr = inet_ntop(socket.AF_INET6, naddr) # normalize + addrType = 0 + # _Assignable_ Global Unicast Address space + # is defined in RFC 3513 as those in 2000::/3 + if ((struct.unpack("B", naddr[0])[0] & 0xE0) == 0x20): + addrType = (IPV6_ADDR_UNICAST | IPV6_ADDR_GLOBAL) + if naddr[:2] == ' \x02': # Mark 6to4 @ + addrType |= IPV6_ADDR_6TO4 + elif naddr[0] == '\xff': # multicast + addrScope = paddr[3] + if addrScope == '2': + addrType = (IPV6_ADDR_LINKLOCAL | IPV6_ADDR_MULTICAST) + elif addrScope == 'e': + addrType = (IPV6_ADDR_GLOBAL | IPV6_ADDR_MULTICAST) + else: + addrType = (IPV6_ADDR_GLOBAL | IPV6_ADDR_MULTICAST) + elif ((naddr[0] == '\xfe') and ((int(paddr[2], 16) & 0xC) == 0x8)): + addrType = (IPV6_ADDR_UNICAST | IPV6_ADDR_LINKLOCAL) + elif paddr == "::1": + addrType = IPV6_ADDR_LOOPBACK + elif paddr == "::": + addrType = IPV6_ADDR_UNSPECIFIED + else: + # Everything else is global unicast (RFC 3513) + # Even old deprecated (RFC3879) Site-Local addresses + addrType = (IPV6_ADDR_GLOBAL | IPV6_ADDR_UNICAST) + + return addrType + +def in6_mactoifaceid(mac, ulbit=None): + """ + Compute the interface ID in modified EUI-64 format associated + to the Ethernet address provided as input. + value taken by U/L bit in the interface identifier is basically + the reversed value of that in given MAC address it can be forced + to a specific value by using optional 'ulbit' parameter. + """ + if len(mac) != 17: return None + m = "".join(mac.split(':')) + if len(m) != 12: return None + first = int(m[0:2], 16) + if ulbit is None or not (ulbit == 0 or ulbit == 1): + ulbit = [1,'-',0][first & 0x02] + ulbit *= 2 + first = "%.02x" % ((first & 0xFD) | ulbit) + eui64 = first + m[2:4] + ":" + m[4:6] + "FF:FE" + m[6:8] + ":" + m[8:12] + return eui64.upper() + +def in6_ifaceidtomac(ifaceid): # TODO: finish commenting function behavior + """ + Extract the mac address from provided iface ID. Iface ID is provided + in printable format ("XXXX:XXFF:FEXX:XXXX", eventually compressed). None + is returned on error. + """ + try: + ifaceid = inet_pton(socket.AF_INET6, "::"+ifaceid)[8:16] + except: + return None + if ifaceid[3:5] != '\xff\xfe': + return None + first = struct.unpack("B", ifaceid[:1])[0] + ulbit = 2*[1,'-',0][first & 0x02] + first = struct.pack("B", ((first & 0xFD) | ulbit)) + oui = first + ifaceid[1:3] + end = ifaceid[5:] + l = map(lambda x: "%.02x" % struct.unpack("B", x)[0], list(oui+end)) + return ":".join(l) + +def in6_addrtomac(addr): + """ + Extract the mac address from provided address. None is returned + on error. + """ + mask = inet_pton(socket.AF_INET6, "::ffff:ffff:ffff:ffff") + x = in6_and(mask, inet_pton(socket.AF_INET6, addr)) + ifaceid = inet_ntop(socket.AF_INET6, x)[2:] + return in6_ifaceidtomac(ifaceid) + +def in6_addrtovendor(addr): + """ + Extract the MAC address from a modified EUI-64 constructed IPv6 + address provided and use the IANA oui.txt file to get the vendor. + The database used for the conversion is the one loaded by Scapy, + based on Wireshark (/usr/share/wireshark/wireshark/manuf) None + is returned on error, "UNKNOWN" if the vendor is unknown. + """ + mac = in6_addrtomac(addr) + if mac is None: + return None + + res = conf.manufdb._get_manuf(mac) + if len(res) == 17 and res.count(':') != 5: # Mac address, i.e. unknown + res = "UNKNOWN" + + return res + +def in6_getLinkScopedMcastAddr(addr, grpid=None, scope=2): + """ + Generate a Link-Scoped Multicast Address as described in RFC 4489. + Returned value is in printable notation. + + 'addr' parameter specifies the link-local address to use for generating + Link-scoped multicast address IID. + + By default, the function returns a ::/96 prefix (aka last 32 bits of + returned address are null). If a group id is provided through 'grpid' + parameter, last 32 bits of the address are set to that value (accepted + formats : '\x12\x34\x56\x78' or '12345678' or 0x12345678 or 305419896). + + By default, generated address scope is Link-Local (2). That value can + be modified by passing a specific 'scope' value as an argument of the + function. RFC 4489 only authorizes scope values <= 2. Enforcement + is performed by the function (None will be returned). + + If no link-local address can be used to generate the Link-Scoped IPv6 + Multicast address, or if another error occurs, None is returned. + """ + if not scope in [0, 1, 2]: + return None + try: + if not in6_islladdr(addr): + return None + addr = inet_pton(socket.AF_INET6, addr) + except: + warning("in6_getLinkScopedMcastPrefix(): Invalid address provided") + return None + + iid = addr[8:] + + if grpid is None: + grpid = '\x00\x00\x00\x00' + else: + if type(grpid) is str: + if len(grpid) == 8: + try: + grpid = int(grpid, 16) & 0xffffffff + except: + warning("in6_getLinkScopedMcastPrefix(): Invalid group id provided") + return None + elif len(grpid) == 4: + try: + grpid = struct.unpack("!I", grpid)[0] + except: + warning("in6_getLinkScopedMcastPrefix(): Invalid group id provided") + return None + grpid = struct.pack("!I", grpid) + + flgscope = struct.pack("B", 0xff & ((0x3 << 4) | scope)) + plen = '\xff' + res = '\x00' + a = '\xff' + flgscope + res + plen + iid + grpid + + return inet_ntop(socket.AF_INET6, a) + +def in6_get6to4Prefix(addr): + """ + Returns the /48 6to4 prefix associated with provided IPv4 address + On error, None is returned. No check is performed on public/private + status of the address + """ + try: + addr = inet_pton(socket.AF_INET, addr) + addr = inet_ntop(socket.AF_INET6, '\x20\x02'+addr+'\x00'*10) + except: + return None + return addr + +def in6_6to4ExtractAddr(addr): + """ + Extract IPv4 address embbeded in 6to4 address. Passed address must be + a 6to4 addrees. None is returned on error. + """ + try: + addr = inet_pton(socket.AF_INET6, addr) + except: + return None + if addr[:2] != " \x02": + return None + return inet_ntop(socket.AF_INET, addr[2:6]) + + +def in6_getLocalUniquePrefix(): + """ + Returns a pseudo-randomly generated Local Unique prefix. Function + follows recommandation of Section 3.2.2 of RFC 4193 for prefix + generation. + """ + # Extracted from RFC 1305 (NTP) : + # NTP timestamps are represented as a 64-bit unsigned fixed-point number, + # in seconds relative to 0h on 1 January 1900. The integer part is in the + # first 32 bits and the fraction part in the last 32 bits. + + # epoch = (1900, 1, 1, 0, 0, 0, 5, 1, 0) + # x = time.time() + # from time import gmtime, strftime, gmtime, mktime + # delta = mktime(gmtime(0)) - mktime(self.epoch) + # x = x-delta + + tod = time.time() # time of day. Will bother with epoch later + i = int(tod) + j = int((tod - i)*(2**32)) + tod = struct.pack("!II", i,j) + # TODO: Add some check regarding system address gathering + rawmac = get_if_raw_hwaddr(conf.iface6)[1] + mac = ":".join(map(lambda x: "%.02x" % ord(x), list(rawmac))) + # construct modified EUI-64 ID + eui64 = inet_pton(socket.AF_INET6, '::' + in6_mactoifaceid(mac))[8:] + import sha + globalid = sha.new(tod+eui64).digest()[:5] + return inet_ntop(socket.AF_INET6, '\xfd' + globalid + '\x00'*10) + +def in6_getRandomizedIfaceId(ifaceid, previous=None): + """ + Implements the interface ID generation algorithm described in RFC 3041. + The function takes the Modified EUI-64 interface identifier generated + as described in RFC 4291 and an optional previous history value (the + first element of the output of this function). If no previous interface + identifier is provided, a random one is generated. The function returns + a tuple containing the randomized interface identifier and the history + value (for possible future use). Input and output values are provided in + a "printable" format as depicted below. + + ex: + + >>> in6_getRandomizedIfaceId('20b:93ff:feeb:2d3') + ('4c61:76ff:f46a:a5f3', 'd006:d540:db11:b092') + + >>> in6_getRandomizedIfaceId('20b:93ff:feeb:2d3', + previous='d006:d540:db11:b092') + ('fe97:46fe:9871:bd38', 'eeed:d79c:2e3f:62e') + """ + + s = "" + if previous is None: + d = "".join(map(chr, range(256))) + for i in range(8): + s += random.choice(d) + previous = s + s = inet_pton(socket.AF_INET6, "::"+ifaceid)[8:] + previous + import md5 + s = md5.new(s).digest() + s1,s2 = s[:8],s[8:] + s1 = chr(ord(s1[0]) | 0x04) + s1[1:] + s1 = inet_ntop(socket.AF_INET6, "\xff"*8 + s1)[20:] + s2 = inet_ntop(socket.AF_INET6, "\xff"*8 + s2)[20:] + return (s1, s2) + + +_rfc1924map = [ '0','1','2','3','4','5','6','7','8','9','A','B','C','D','E', + 'F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T', + 'U','V','W','X','Y','Z','a','b','c','d','e','f','g','h','i', + 'j','k','l','m','n','o','p','q','r','s','t','u','v','w','x', + 'y','z','!','#','$','%','&','(',')','*','+','-',';','<','=', + '>','?','@','^','_','`','{','|','}','~' ] + +def in6_ctop(addr): + """ + Convert an IPv6 address in Compact Representation Notation + (RFC 1924) to printable representation ;-) + Returns None on error. + """ + if len(addr) != 20 or not reduce(lambda x,y: x and y, + map(lambda x: x in _rfc1924map, addr)): + return None + i = 0 + for c in addr: + j = _rfc1924map.index(c) + i = 85*i + j + res = [] + for j in range(4): + res.append(struct.pack("!I", i%2**32)) + i = i/(2**32) + res.reverse() + return inet_ntop(socket.AF_INET6, "".join(res)) + +def in6_ptoc(addr): + """ + Converts an IPv6 address in printable representation to RFC + 1924 Compact Representation ;-) + Returns None on error. + """ + try: + d=struct.unpack("!IIII", inet_pton(socket.AF_INET6, addr)) + except: + return None + res = 0 + m = [2**96, 2**64, 2**32, 1] + for i in range(4): + res += d[i]*m[i] + rem = res + res = [] + while rem: + res.append(_rfc1924map[rem%85]) + rem = rem/85 + res.reverse() + return "".join(res) + + +def in6_isaddr6to4(x): + """ + Return True if provided address (in printable format) is a 6to4 + address (being in 2002::/16). + """ + x = inet_pton(socket.AF_INET6, x) + return x[:2] == ' \x02' + +conf.teredoPrefix = "2001::" # old one was 3ffe:831f (it is a /32) +conf.teredoServerPort = 3544 + +def in6_isaddrTeredo(x): + """ + Return True if provided address is a Teredo, meaning it is under + the /32 conf.teredoPrefix prefix value (by default, 2001::). + Otherwise, False is returned. Address must be passed in printable + format. + """ + our = inet_pton(socket.AF_INET6, x)[0:4] + teredoPrefix = inet_pton(socket.AF_INET6, conf.teredoPrefix)[0:4] + return teredoPrefix == our + +def teredoAddrExtractInfo(x): + """ + Extract information from a Teredo address. Return value is + a 4-tuple made of IPv4 address of Teredo server, flag value (int), + mapped address (non obfuscated) and mapped port (non obfuscated). + No specific checks are performed on passed address. + """ + addr = inet_pton(socket.AF_INET6, x) + server = inet_ntop(socket.AF_INET, addr[4:8]) + flag = struct.unpack("!H",addr[8:10])[0] + mappedport = struct.unpack("!H",strxor(addr[10:12],'\xff'*2))[0] + mappedaddr = inet_ntop(socket.AF_INET, strxor(addr[12:16],'\xff'*4)) + return server, flag, mappedaddr, mappedport + +def in6_iseui64(x): + """ + Return True if provided address has an interface identifier part + created in modified EUI-64 format (meaning it matches *::*:*ff:fe*:*). + Otherwise, False is returned. Address must be passed in printable + format. + """ + eui64 = inet_pton(socket.AF_INET6, '::ff:fe00:0') + x = in6_and(inet_pton(socket.AF_INET6, x), eui64) + return x == eui64 + +def in6_isanycast(x): # RFC 2526 + if in6_iseui64(x): + s = '::fdff:ffff:ffff:ff80' + x = in6_and(x, inet_pton(socket.AF_INET6, '::ffff:ffff:ffff:ff80')) + x = in6_and(x, inet_pton(socket.AF_INET6, s)) + return x == inet_pton(socket.AF_INET6, s) + else: + # not EUI-64 + #| n bits | 121-n bits | 7 bits | + #+---------------------------------+------------------+------------+ + #| subnet prefix | 1111111...111111 | anycast ID | + #+---------------------------------+------------------+------------+ + # | interface identifier field | + warning('in6_isanycast(): TODO not EUI-64') + return 0 + +def _in6_bitops(a1, a2, operator=0): + a1 = struct.unpack('4I', a1) + a2 = struct.unpack('4I', a2) + fop = [ lambda x,y: x | y, + lambda x,y: x & y, + lambda x,y: x ^ y + ] + ret = map(fop[operator%len(fop)], a1, a2) + t = ''.join(map(lambda x: struct.pack('I', x), ret)) + return t + +def in6_or(a1, a2): + """ + Provides a bit to bit OR of provided addresses. They must be + passed in network format. Return value is also an IPv6 address + in network format. + """ + return _in6_bitops(a1, a2, 0) + +def in6_and(a1, a2): + """ + Provides a bit to bit AND of provided addresses. They must be + passed in network format. Return value is also an IPv6 address + in network format. + """ + return _in6_bitops(a1, a2, 1) + +def in6_xor(a1, a2): + """ + Provides a bit to bit XOR of provided addresses. They must be + passed in network format. Return value is also an IPv6 address + in network format. + """ + return _in6_bitops(a1, a2, 2) + +def in6_cidr2mask(m): + """ + Return the mask (bitstring) associated with provided length + value. For instance if function is called on 48, return value is + '\xff\xff\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'. + + """ + if m > 128 or m < 0: + raise Scapy_Exception("value provided to in6_cidr2mask outside [0, 128] domain (%d)" % m) + + t = [] + for i in xrange(0, 4): + t.append(max(0, 2**32 - 2**(32-min(32, m)))) + m -= 32 + + return ''.join(map(lambda x: struct.pack('!I', x), t)) + +def in6_getnsma(a): + """ + Return link-local solicited-node multicast address for given + address. Passed address must be provided in network format. + Returned value is also in network format. + """ + + r = in6_and(a, inet_pton(socket.AF_INET6, '::ff:ffff')) + r = in6_or(inet_pton(socket.AF_INET6, 'ff02::1:ff00:0'), r) + return r + +def in6_getnsmac(a): # return multicast Ethernet address associated with multicast v6 destination + """ + Return the multicast mac address associated with provided + IPv6 address. Passed address must be in network format. + """ + + a = struct.unpack('16B', a)[-4:] + mac = '33:33:' + mac += ':'.join(map(lambda x: '%.2x' %x, a)) + return mac + +def in6_getha(prefix): + """ + Return the anycast address associated with all home agents on a given + subnet. + """ + r = in6_and(inet_pton(socket.AF_INET6, prefix), in6_cidr2mask(64)) + r = in6_or(r, inet_pton(socket.AF_INET6, '::fdff:ffff:ffff:fffe')) + return inet_ntop(socket.AF_INET6, r) + +def in6_ptop(str): + """ + Normalizes IPv6 addresses provided in printable format, returning the + same address in printable format. (2001:0db8:0:0::1 -> 2001:db8::1) + """ + return inet_ntop(socket.AF_INET6, inet_pton(socket.AF_INET6, str)) + +def in6_isincluded(addr, prefix, plen): + """ + Returns True when 'addr' belongs to prefix/plen. False otherwise. + """ + temp = inet_pton(socket.AF_INET6, addr) + pref = in6_cidr2mask(plen) + zero = inet_pton(socket.AF_INET6, prefix) + return zero == in6_and(temp, pref) + +def in6_isdocaddr(str): + """ + Returns True if provided address in printable format belongs to + 2001:db8::/32 address space reserved for documentation (as defined + in RFC 3849). + """ + return in6_isincluded(str, '2001:db8::', 32) + +def in6_islladdr(str): + """ + Returns True if provided address in printable format belongs to + _allocated_ link-local unicast address space (fe80::/10) + """ + return in6_isincluded(str, 'fe80::', 10) + +def in6_issladdr(str): + """ + Returns True if provided address in printable format belongs to + _allocated_ site-local address space (fec0::/10). This prefix has + been deprecated, address being now reserved by IANA. Function + will remain for historic reasons. + """ + return in6_isincluded(str, 'fec0::', 10) + +def in6_isuladdr(str): + """ + Returns True if provided address in printable format belongs to + Unique local address space (fc00::/7). + """ + return in6_isincluded(str, 'fc::', 7) + +# TODO : we should see the status of Unique Local addresses against +# global address space. +# Up-to-date information is available through RFC 3587. +# We should review function behavior based on its content. +def in6_isgladdr(str): + """ + Returns True if provided address in printable format belongs to + _allocated_ global address space (2000::/3). Please note that, + Unique Local addresses (FC00::/7) are not part of global address + space, and won't match. + """ + return in6_isincluded(str, '2000::', 3) + +def in6_ismaddr(str): + """ + Returns True if provided address in printable format belongs to + allocated Multicast address space (ff00::/8). + """ + return in6_isincluded(str, 'ff00::', 8) + +def in6_ismnladdr(str): + """ + Returns True if address belongs to node-local multicast address + space (ff01::/16) as defined in RFC + """ + return in6_isincluded(str, 'ff01::', 16) + +def in6_ismgladdr(str): + """ + Returns True if address belongs to global multicast address + space (ff0e::/16). + """ + return in6_isincluded(str, 'ff0e::', 16) + +def in6_ismlladdr(str): + """ + Returns True if address balongs to link-local multicast address + space (ff02::/16) + """ + return in6_isincluded(str, 'ff02::', 16) + +def in6_ismsladdr(str): + """ + Returns True if address belongs to site-local multicast address + space (ff05::/16). Site local address space has been deprecated. + Function remains for historic reasons. + """ + return in6_isincluded(str, 'ff05::', 16) + +def in6_isaddrllallnodes(str): + """ + Returns True if address is the link-local all-nodes multicast + address (ff02::1). + """ + return (inet_pton(socket.AF_INET6, "ff02::1") == + inet_pton(socket.AF_INET6, str)) + +def in6_isaddrllallservers(str): + """ + Returns True if address is the link-local all-servers multicast + address (ff02::2). + """ + return (inet_pton(socket.AF_INET6, "ff02::2") == + inet_pton(socket.AF_INET6, str)) + + +def in6_getscope(addr): + """ + Returns the scope of the address. + """ + if in6_isgladdr(addr): + scope = IPV6_ADDR_GLOBAL + elif in6_islladdr(addr): + scope = IPV6_ADDR_LINKLOCAL + elif in6_issladdr(addr): + scope = IPV6_ADDR_SITELOCAL + elif in6_ismaddr(addr): + scope = IPV6_ADDR_MULTICAST + elif addr == '::1': + scope = IPV6_ADDR_LOOPBACK + else: + scope = -1 + return scope