## This file is part of Scapy ## See http://www.secdev.org/projects/scapy for more informations ## Copyright (C) Philippe Biondi ## This program is published under a GPLv2 license ## Copyright (C) 2005 Guillaume Valadon ## Arnaud Ebalard """ DHCPv6: Dynamic Host Configuration Protocol for IPv6. [RFC 3315] """ import socket from scapy.packet import * from scapy.fields import * from scapy.utils6 import * from scapy.layers.inet6 import * from scapy.ansmachine import AnsweringMachine ############################################################################# # Helpers ## ############################################################################# def get_cls(name, fallback_cls): return globals().get(name, fallback_cls) ############################################################################# ############################################################################# ### DHCPv6 ### ############################################################################# ############################################################################# All_DHCP_Relay_Agents_and_Servers = "ff02::1:2" All_DHCP_Servers = "ff05::1:3" # Site-Local scope : deprecated by 3879 dhcp6opts = { 1: "CLIENTID", 2: "SERVERID", 3: "IA_NA", 4: "IA_TA", 5: "IAADDR", 6: "ORO", 7: "PREFERENCE", 8: "ELAPSED_TIME", 9: "RELAY_MSG", 11: "AUTH", 12: "UNICAST", 13: "STATUS_CODE", 14: "RAPID_COMMIT", 15: "USER_CLASS", 16: "VENDOR_CLASS", 17: "VENDOR_OPTS", 18: "INTERFACE_ID", 19: "RECONF_MSG", 20: "RECONF_ACCEPT", 21: "SIP Servers Domain Name List", #RFC3319 22: "SIP Servers IPv6 Address List", #RFC3319 23: "DNS Recursive Name Server Option", #RFC3646 24: "Domain Search List option", #RFC3646 25: "OPTION_IA_PD", #RFC3633 26: "OPTION_IAPREFIX", #RFC3633 27: "OPTION_NIS_SERVERS", #RFC3898 28: "OPTION_NISP_SERVERS", #RFC3898 29: "OPTION_NIS_DOMAIN_NAME", #RFC3898 30: "OPTION_NISP_DOMAIN_NAME", #RFC3898 31: "OPTION_SNTP_SERVERS", #RFC4075 32: "OPTION_INFORMATION_REFRESH_TIME", #RFC4242 33: "OPTION_BCMCS_SERVER_D", #RFC4280 34: "OPTION_BCMCS_SERVER_A", #RFC4280 36: "OPTION_GEOCONF_CIVIC", #RFC-ietf-geopriv-dhcp-civil-09.txt 37: "OPTION_REMOTE_ID", #RFC4649 38: "OPTION_SUBSCRIBER_ID", #RFC4580 39: "OPTION_CLIENT_FQDN" } #RFC4704 dhcp6opts_by_code = { 1: "DHCP6OptClientId", 2: "DHCP6OptServerId", 3: "DHCP6OptIA_NA", 4: "DHCP6OptIA_TA", 5: "DHCP6OptIAAddress", 6: "DHCP6OptOptReq", 7: "DHCP6OptPref", 8: "DHCP6OptElapsedTime", 9: "DHCP6OptRelayMsg", 11: "DHCP6OptAuth", 12: "DHCP6OptServerUnicast", 13: "DHCP6OptStatusCode", 14: "DHCP6OptRapidCommit", 15: "DHCP6OptUserClass", 16: "DHCP6OptVendorClass", 17: "DHCP6OptVendorSpecificInfo", 18: "DHCP6OptIfaceId", 19: "DHCP6OptReconfMsg", 20: "DHCP6OptReconfAccept", 21: "DHCP6OptSIPDomains", #RFC3319 22: "DHCP6OptSIPServers", #RFC3319 23: "DHCP6OptDNSServers", #RFC3646 24: "DHCP6OptDNSDomains", #RFC3646 25: "DHCP6OptIA_PD", #RFC3633 26: "DHCP6OptIAPrefix", #RFC3633 27: "DHCP6OptNISServers", #RFC3898 28: "DHCP6OptNISPServers", #RFC3898 29: "DHCP6OptNISDomain", #RFC3898 30: "DHCP6OptNISPDomain", #RFC3898 31: "DHCP6OptSNTPServers", #RFC4075 32: "DHCP6OptInfoRefreshTime", #RFC4242 33: "DHCP6OptBCMCSDomains", #RFC4280 34: "DHCP6OptBCMCSServers", #RFC4280 #36: "DHCP6OptGeoConf", #RFC-ietf-geopriv-dhcp-civil-09.txt 37: "DHCP6OptRemoteID", #RFC4649 38: "DHCP6OptSubscriberID", #RFC4580 39: "DHCP6OptClientFQDN", #RFC4704 #40: "DHCP6OptPANAAgent", #RFC-ietf-dhc-paa-option-05.txt #41: "DHCP6OptNewPOSIXTimeZone, #RFC4833 #42: "DHCP6OptNewTZDBTimeZone, #RFC4833 43: "DHCP6OptRelayAgentERO" #RFC4994 #44: "DHCP6OptLQQuery", #RFC5007 #45: "DHCP6OptLQClientData", #RFC5007 #46: "DHCP6OptLQClientTime", #RFC5007 #47: "DHCP6OptLQRelayData", #RFC5007 #48: "DHCP6OptLQClientLink", #RFC5007 } # sect 5.3 RFC 3315 : DHCP6 Messages types dhcp6types = { 1:"SOLICIT", 2:"ADVERTISE", 3:"REQUEST", 4:"CONFIRM", 5:"RENEW", 6:"REBIND", 7:"REPLY", 8:"RELEASE", 9:"DECLINE", 10:"RECONFIGURE", 11:"INFORMATION-REQUEST", 12:"RELAY-FORW", 13:"RELAY-REPL" } ##################################################################### ### DHCPv6 DUID related stuff ### ##################################################################### duidtypes = { 1: "Link-layer address plus time", 2: "Vendor-assigned unique ID based on Enterprise Number", 3: "Link-layer Address" } # DUID hardware types - RFC 826 - Extracted from # http://www.iana.org/assignments/arp-parameters on 31/10/06 # We should add the length of every kind of address. duidhwtypes = { 0: "NET/ROM pseudo", # Not referenced by IANA 1: "Ethernet (10Mb)", 2: "Experimental Ethernet (3Mb)", 3: "Amateur Radio AX.25", 4: "Proteon ProNET Token Ring", 5: "Chaos", 6: "IEEE 802 Networks", 7: "ARCNET", 8: "Hyperchannel", 9: "Lanstar", 10: "Autonet Short Address", 11: "LocalTalk", 12: "LocalNet (IBM PCNet or SYTEK LocalNET)", 13: "Ultra link", 14: "SMDS", 15: "Frame Relay", 16: "Asynchronous Transmission Mode (ATM)", 17: "HDLC", 18: "Fibre Channel", 19: "Asynchronous Transmission Mode (ATM)", 20: "Serial Line", 21: "Asynchronous Transmission Mode (ATM)", 22: "MIL-STD-188-220", 23: "Metricom", 24: "IEEE 1394.1995", 25: "MAPOS", 26: "Twinaxial", 27: "EUI-64", 28: "HIPARP", 29: "IP and ARP over ISO 7816-3", 30: "ARPSec", 31: "IPsec tunnel", 32: "InfiniBand (TM)", 33: "TIA-102 Project 25 Common Air Interface (CAI)" } class UTCTimeField(IntField): epoch = (2000, 1, 1, 0, 0, 0, 5, 1, 0) # required Epoch def i2repr(self, pkt, x): x = self.i2h(pkt, x) from time import gmtime, strftime, mktime delta = mktime(self.epoch) - mktime(gmtime(0)) x = x + delta t = strftime("%a, %d %b %Y %H:%M:%S +0000", gmtime(x)) return "%s (%d)" % (t, x) class _LLAddrField(MACField): pass # XXX We only support Ethernet addresses at the moment. _LLAddrField # will be modified when needed. Ask us. --arno class DUID_LLT(Packet): # sect 9.2 RFC 3315 name = "DUID - Link-layer address plus time" fields_desc = [ ShortEnumField("type", 1, duidtypes), XShortEnumField("hwtype", 1, duidhwtypes), UTCTimeField("timeval", 0), # i.e. 01 Jan 2000 _LLAddrField("lladdr", ETHER_ANY) ] # In fact, IANA enterprise-numbers file available at # http//www.iana.org/asignments/enterprise-numbers) # is simply huge (more than 2Mo and 600Ko in bz2). I'll # add only most common vendors, and encountered values. # -- arno iana_enterprise_num = { 9: "ciscoSystems", 35: "Nortel Networks", 43: "3Com", 311: "Microsoft", 2636: "Juniper Networks, Inc.", 4526: "Netgear", 5771: "Cisco Systems, Inc.", 5842: "Cisco Systems", 16885: "Nortel Networks" } class DUID_EN(Packet): # sect 9.3 RFC 3315 name = "DUID - Assigned by Vendor Based on Enterprise Number" fields_desc = [ ShortEnumField("type", 2, duidtypes), IntEnumField("enterprisenum", 311, iana_enterprise_num), StrField("id","") ] class DUID_LL(Packet): # sect 9.4 RFC 3315 name = "DUID - Based on Link-layer Address" fields_desc = [ ShortEnumField("type", 3, duidtypes), XShortEnumField("hwtype", 1, duidhwtypes), _LLAddrField("lladdr", ETHER_ANY) ] duid_cls = { 1: "DUID_LLT", 2: "DUID_EN", 3: "DUID_LL"} ##################################################################### ### DHCPv6 Options classes ### ##################################################################### class _DHCP6OptGuessPayload(Packet): def guess_payload_class(self, payload): cls = conf.raw_layer if len(payload) > 2 : opt = struct.unpack("!H", payload[:2])[0] cls = get_cls(dhcp6opts_by_code.get(opt, "DHCP6OptUnknown"), DHCP6OptUnknown) return cls class DHCP6OptUnknown(_DHCP6OptGuessPayload): # A generic DHCPv6 Option name = "Unknown DHCPv6 OPtion" fields_desc = [ ShortEnumField("optcode", 0, dhcp6opts), FieldLenField("optlen", None, length_of="data", fmt="!H"), StrLenField("data", "", length_from = lambda pkt: pkt.optlen)] class _DUIDField(PacketField): holds_packets=1 def __init__(self, name, default, length_from=None): StrField.__init__(self, name, default) self.length_from = length_from def i2m(self, pkt, i): return str(i) def m2i(self, pkt, x): cls = conf.raw_layer if len(x) > 4: o = struct.unpack("!H", x[:2])[0] cls = get_cls(duid_cls.get(o, conf.raw_layer), conf.raw_layer) return cls(x) def getfield(self, pkt, s): l = self.length_from(pkt) return s[l:], self.m2i(pkt,s[:l]) class DHCP6OptClientId(_DHCP6OptGuessPayload): # RFC sect 22.2 name = "DHCP6 Client Identifier Option" fields_desc = [ ShortEnumField("optcode", 1, dhcp6opts), FieldLenField("optlen", None, length_of="duid", fmt="!H"), _DUIDField("duid", "", length_from = lambda pkt: pkt.optlen) ] class DHCP6OptServerId(DHCP6OptClientId): # RFC sect 22.3 name = "DHCP6 Server Identifier Option" optcode = 2 # Should be encapsulated in the option field of IA_NA or IA_TA options # Can only appear at that location. # TODO : last field IAaddr-options is not defined in the reference document class DHCP6OptIAAddress(_DHCP6OptGuessPayload): # RFC sect 22.6 name = "DHCP6 IA Address Option (IA_TA or IA_NA suboption)" fields_desc = [ ShortEnumField("optcode", 5, dhcp6opts), FieldLenField("optlen", None, length_of="iaaddropts", fmt="!H", adjust = lambda pkt,x: x+24), IP6Field("addr", "::"), IntField("preflft", 0), IntField("validlft", 0), XIntField("iaid", None), StrLenField("iaaddropts", "", length_from = lambda pkt: pkt.optlen - 24) ] def guess_payload_class(self, payload): return conf.padding_layer class _IANAOptField(PacketListField): def i2len(self, pkt, z): if z is None or z == []: return 0 return sum(map(lambda x: len(str(x)) ,z)) def getfield(self, pkt, s): l = self.length_from(pkt) lst = [] remain, payl = s[:l], s[l:] while len(remain)>0: p = self.m2i(pkt,remain) if conf.padding_layer in p: pad = p[conf.padding_layer] remain = pad.load del(pad.underlayer.payload) else: remain = "" lst.append(p) return payl,lst class DHCP6OptIA_NA(_DHCP6OptGuessPayload): # RFC sect 22.4 name = "DHCP6 Identity Association for Non-temporary Addresses Option" fields_desc = [ ShortEnumField("optcode", 3, dhcp6opts), FieldLenField("optlen", None, length_of="ianaopts", fmt="!H", adjust = lambda pkt,x: x+12), XIntField("iaid", None), IntField("T1", None), IntField("T2", None), _IANAOptField("ianaopts", [], DHCP6OptIAAddress, length_from = lambda pkt: pkt.optlen-12) ] class _IATAOptField(_IANAOptField): pass class DHCP6OptIA_TA(_DHCP6OptGuessPayload): # RFC sect 22.5 name = "DHCP6 Identity Association for Temporary Addresses Option" fields_desc = [ ShortEnumField("optcode", 4, dhcp6opts), FieldLenField("optlen", None, length_of="iataopts", fmt="!H", adjust = lambda pkt,x: x+4), XIntField("iaid", None), _IATAOptField("iataopts", [], DHCP6OptIAAddress, length_from = lambda pkt: pkt.optlen-4) ] #### DHCPv6 Option Request Option ################################### class _OptReqListField(StrLenField): islist = 1 def i2h(self, pkt, x): if x is None: return [] return x def i2len(self, pkt, x): return 2*len(x) def any2i(self, pkt, x): return x def i2repr(self, pkt, x): s = [] for y in self.i2h(pkt, x): if dhcp6opts.has_key(y): s.append(dhcp6opts[y]) else: s.append("%d" % y) return "[%s]" % ", ".join(s) def m2i(self, pkt, x): r = [] while len(x) != 0: if len(x)<2: warning("Odd length for requested option field. Rejecting last byte") return r r.append(struct.unpack("!H", x[:2])[0]) x = x[2:] return r def i2m(self, pkt, x): return "".join(map(lambda y: struct.pack("!H", y), x)) # A client may include an ORO in a solicit, Request, Renew, Rebind, # Confirm or Information-request class DHCP6OptOptReq(_DHCP6OptGuessPayload): # RFC sect 22.7 name = "DHCP6 Option Request Option" fields_desc = [ ShortEnumField("optcode", 6, dhcp6opts), FieldLenField("optlen", None, length_of="reqopts", fmt="!H"), _OptReqListField("reqopts", [23, 24], length_from = lambda pkt: pkt.optlen) ] #### DHCPv6 Preference Option ####################################### # emise par un serveur pour affecter le choix fait par le client. Dans # les messages Advertise, a priori class DHCP6OptPref(_DHCP6OptGuessPayload): # RFC sect 22.8 name = "DHCP6 Preference Option" fields_desc = [ ShortEnumField("optcode", 7, dhcp6opts), ShortField("optlen", 1 ), ByteField("prefval",255) ] #### DHCPv6 Elapsed Time Option ##################################### class _ElapsedTimeField(ShortField): def i2repr(self, pkt, x): if x == 0xffff: return "infinity (0xffff)" return "%.2f sec" % (self.i2h(pkt, x)/100.) class DHCP6OptElapsedTime(_DHCP6OptGuessPayload):# RFC sect 22.9 name = "DHCP6 Elapsed Time Option" fields_desc = [ ShortEnumField("optcode", 8, dhcp6opts), ShortField("optlen", 2), _ElapsedTimeField("elapsedtime", 0) ] #### DHCPv6 Relay Message Option #################################### # Relayed message is seen as a payload. class DHCP6OptRelayMsg(_DHCP6OptGuessPayload):# RFC sect 22.10 name = "DHCP6 Relay Message Option" fields_desc = [ ShortEnumField("optcode", 9, dhcp6opts), ShortField("optlen", None ) ] def post_build(self, p, pay): if self.optlen is None: l = len(pay) p = p[:2]+struct.pack("!H", l) return p + pay #### DHCPv6 Authentication Option ################################### # The following fields are set in an Authentication option for the # Reconfigure Key Authentication Protocol: # # protocol 3 # # algorithm 1 # # RDM 0 # # The format of the Authentication information for the Reconfigure Key # Authentication Protocol is: # # 0 1 2 3 # 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ # | Type | Value (128 bits) | # +-+-+-+-+-+-+-+-+ | # . . # . . # . +-+-+-+-+-+-+-+-+ # | | # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ # # Type Type of data in Value field carried in this option: # # 1 Reconfigure Key value (used in Reply message). # # 2 HMAC-MD5 digest of the message (used in Reconfigure # message). # # Value Data as defined by field. # TODO : Decoding only at the moment class DHCP6OptAuth(_DHCP6OptGuessPayload): # RFC sect 22.11 name = "DHCP6 Option - Authentication" fields_desc = [ ShortEnumField("optcode", 11, dhcp6opts), FieldLenField("optlen", None, length_of="authinfo", adjust = lambda pkt,x: x+11), ByteField("proto", 3), # TODO : XXX ByteField("alg", 1), # TODO : XXX ByteField("rdm", 0), # TODO : XXX StrFixedLenField("replay", "A"*8, 8), # TODO: XXX StrLenField("authinfo", "", length_from = lambda pkt: pkt.optlen - 11) ] #### DHCPv6 Server Unicast Option ################################### class _SrvAddrField(IP6Field): def i2h(self, pkt, x): if x is None: return "::" return x def i2m(self, pkt, x): return inet_pton(socket.AF_INET6, self.i2h(pkt,x)) class DHCP6OptServerUnicast(_DHCP6OptGuessPayload):# RFC sect 22.12 name = "DHCP6 Server Unicast Option" fields_desc = [ ShortEnumField("optcode", 12, dhcp6opts), ShortField("optlen", 16 ), _SrvAddrField("srvaddr",None) ] #### DHCPv6 Status Code Option ###################################### dhcp6statuscodes = { 0:"Success", # sect 24.4 1:"UnspecFail", 2:"NoAddrsAvail", 3:"NoBinding", 4:"NotOnLink", 5:"UseMulticast", 6:"NoPrefixAvail"} # From RFC3633 class DHCP6OptStatusCode(_DHCP6OptGuessPayload):# RFC sect 22.13 name = "DHCP6 Status Code Option" fields_desc = [ ShortEnumField("optcode", 13, dhcp6opts), FieldLenField("optlen", None, length_of="statusmsg", fmt="!H", adjust = lambda pkt,x:x+2), ShortEnumField("statuscode",None,dhcp6statuscodes), StrLenField("statusmsg", "", length_from = lambda pkt: pkt.optlen-2) ] #### DHCPv6 Rapid Commit Option ##################################### class DHCP6OptRapidCommit(_DHCP6OptGuessPayload): # RFC sect 22.14 name = "DHCP6 Rapid Commit Option" fields_desc = [ ShortEnumField("optcode", 14, dhcp6opts), ShortField("optlen", 0)] #### DHCPv6 User Class Option ####################################### class _UserClassDataField(PacketListField): def i2len(self, pkt, z): if z is None or z == []: return 0 return sum(map(lambda x: len(str(x)) ,z)) def getfield(self, pkt, s): l = self.length_from(pkt) lst = [] remain, payl = s[:l], s[l:] while len(remain)>0: p = self.m2i(pkt,remain) if conf.padding_layer in p: pad = p[conf.padding_layer] remain = pad.load del(pad.underlayer.payload) else: remain = "" lst.append(p) return payl,lst class USER_CLASS_DATA(Packet): name = "user class data" fields_desc = [ FieldLenField("len", None, length_of="data"), StrLenField("data", "", length_from = lambda pkt: pkt.len) ] def guess_payload_class(self, payload): return conf.padding_layer class DHCP6OptUserClass(_DHCP6OptGuessPayload):# RFC sect 22.15 name = "DHCP6 User Class Option" fields_desc = [ ShortEnumField("optcode", 15, dhcp6opts), FieldLenField("optlen", None, fmt="!H", length_of="userclassdata"), _UserClassDataField("userclassdata", [], USER_CLASS_DATA, length_from = lambda pkt: pkt.optlen) ] #### DHCPv6 Vendor Class Option ##################################### class _VendorClassDataField(_UserClassDataField): pass class VENDOR_CLASS_DATA(USER_CLASS_DATA): name = "vendor class data" class DHCP6OptVendorClass(_DHCP6OptGuessPayload):# RFC sect 22.16 name = "DHCP6 Vendor Class Option" fields_desc = [ ShortEnumField("optcode", 16, dhcp6opts), FieldLenField("optlen", None, length_of="vcdata", fmt="!H", adjust = lambda pkt,x: x+4), IntEnumField("enterprisenum",None , iana_enterprise_num ), _VendorClassDataField("vcdata", [], VENDOR_CLASS_DATA, length_from = lambda pkt: pkt.optlen-4) ] #### DHCPv6 Vendor-Specific Information Option ###################### class VENDOR_SPECIFIC_OPTION(_DHCP6OptGuessPayload): name = "vendor specific option data" fields_desc = [ ShortField("optcode", None), FieldLenField("optlen", None, length_of="optdata"), StrLenField("optdata", "", length_from = lambda pkt: pkt.optlen) ] def guess_payload_class(self, payload): return conf.padding_layer # The third one that will be used for nothing interesting class DHCP6OptVendorSpecificInfo(_DHCP6OptGuessPayload):# RFC sect 22.17 name = "DHCP6 Vendor-specific Information Option" fields_desc = [ ShortEnumField("optcode", 17, dhcp6opts), FieldLenField("optlen", None, length_of="vso", fmt="!H", adjust = lambda pkt,x: x+4), IntEnumField("enterprisenum",None , iana_enterprise_num), _VendorClassDataField("vso", [], VENDOR_SPECIFIC_OPTION, length_from = lambda pkt: pkt.optlen-4) ] #### DHCPv6 Interface-ID Option ##################################### # Repasser sur cette option a la fin. Elle a pas l'air d'etre des # masses critique. class DHCP6OptIfaceId(_DHCP6OptGuessPayload):# RFC sect 22.18 name = "DHCP6 Interface-Id Option" fields_desc = [ ShortEnumField("optcode", 18, dhcp6opts), FieldLenField("optlen", None, fmt="!H", length_of="ifaceid"), StrLenField("ifaceid", "", length_from = lambda pkt: pkt.optlen) ] #### DHCPv6 Reconfigure Message Option ############################## # A server includes a Reconfigure Message option in a Reconfigure # message to indicate to the client whether the client responds with a # renew message or an Informatiion-request message. class DHCP6OptReconfMsg(_DHCP6OptGuessPayload): # RFC sect 22.19 name = "DHCP6 Reconfigure Message Option" fields_desc = [ ShortEnumField("optcode", 19, dhcp6opts), ShortField("optlen", 1 ), ByteEnumField("msgtype", 11, { 5:"Renew Message", 11:"Information Request"}) ] #### DHCPv6 Reconfigure Accept Option ############################### # A client uses the Reconfigure Accept option to announce to the # server whether the client is willing to accept Recoonfigure # messages, and a server uses this option to tell the client whether # or not to accept Reconfigure messages. The default behavior in the # absence of this option, means unwillingness to accept reconfigure # messages, or instruction not to accept Reconfigure messages, for the # client and server messages, respectively. class DHCP6OptReconfAccept(_DHCP6OptGuessPayload): # RFC sect 22.20 name = "DHCP6 Reconfigure Accept Option" fields_desc = [ ShortEnumField("optcode", 20, dhcp6opts), ShortField("optlen", 0)] # As required in Sect 8. of RFC 3315, Domain Names must be encoded as # described in section 3.1 of RFC 1035 # XXX Label should be at most 63 octets in length : we do not enforce it # Total length of domain should be 255 : we do not enforce it either class DomainNameListField(StrLenField): islist = 1 def i2len(self, pkt, x): return len(self.i2m(pkt, x)) def m2i(self, pkt, x): res = [] while x: cur = [] while x and x[0] != '\x00': l = ord(x[0]) cur.append(x[1:l+1]) x = x[l+1:] res.append(".".join(cur)) if x and x[0] == '\x00': x = x[1:] return res def i2m(self, pkt, x): def conditionalTrailingDot(z): if z and z[-1] == '\x00': return z return z+'\x00' res = "" tmp = map(lambda y: map((lambda z: chr(len(z))+z), y.split('.')), x) return "".join(map(lambda x: conditionalTrailingDot("".join(x)), tmp)) class DHCP6OptSIPDomains(_DHCP6OptGuessPayload): #RFC3319 name = "DHCP6 Option - SIP Servers Domain Name List" fields_desc = [ ShortEnumField("optcode", 21, dhcp6opts), FieldLenField("optlen", None, length_of="sipdomains"), DomainNameListField("sipdomains", [], length_from = lambda pkt: pkt.optlen) ] class DHCP6OptSIPServers(_DHCP6OptGuessPayload): #RFC3319 name = "DHCP6 Option - SIP Servers IPv6 Address List" fields_desc = [ ShortEnumField("optcode", 22, dhcp6opts), FieldLenField("optlen", None, length_of="sipservers"), IP6ListField("sipservers", [], length_from = lambda pkt: pkt.optlen) ] class DHCP6OptDNSServers(_DHCP6OptGuessPayload): #RFC3646 name = "DHCP6 Option - DNS Recursive Name Server" fields_desc = [ ShortEnumField("optcode", 23, dhcp6opts), FieldLenField("optlen", None, length_of="dnsservers"), IP6ListField("dnsservers", [], length_from = lambda pkt: pkt.optlen) ] class DHCP6OptDNSDomains(_DHCP6OptGuessPayload): #RFC3646 name = "DHCP6 Option - Domain Search List option" fields_desc = [ ShortEnumField("optcode", 24, dhcp6opts), FieldLenField("optlen", None, length_of="dnsdomains"), DomainNameListField("dnsdomains", [], length_from = lambda pkt: pkt.optlen) ] # TODO: Implement iaprefopts correctly when provided with more # information about it. class DHCP6OptIAPrefix(_DHCP6OptGuessPayload): #RFC3633 name = "DHCP6 Option - IA_PD Prefix option" fields_desc = [ ShortEnumField("optcode", 26, dhcp6opts), FieldLenField("optlen", None, length_of="iaprefopts", adjust = lambda pkt,x: x+26), IntField("preflft", 0), IntField("validlft", 0), ByteField("plen", 48), # TODO: Challenge that default value IP6Field("prefix", "2001:db8::"), # At least, global and won't hurt StrLenField("iaprefopts", "", length_from = lambda pkt: pkt.optlen-26) ] class DHCP6OptIA_PD(_DHCP6OptGuessPayload): #RFC3633 name = "DHCP6 Option - Identity Association for Prefix Delegation" fields_desc = [ ShortEnumField("optcode", 25, dhcp6opts), FieldLenField("optlen", None, length_of="iapdopt", adjust = lambda pkt,x: x+12), IntField("iaid", 0), IntField("T1", 0), IntField("T2", 0), PacketListField("iapdopt", [], DHCP6OptIAPrefix, length_from = lambda pkt: pkt.optlen-12) ] class DHCP6OptNISServers(_DHCP6OptGuessPayload): #RFC3898 name = "DHCP6 Option - NIS Servers" fields_desc = [ ShortEnumField("optcode", 27, dhcp6opts), FieldLenField("optlen", None, length_of="nisservers"), IP6ListField("nisservers", [], length_from = lambda pkt: pkt.optlen) ] class DHCP6OptNISPServers(_DHCP6OptGuessPayload): #RFC3898 name = "DHCP6 Option - NIS+ Servers" fields_desc = [ ShortEnumField("optcode", 28, dhcp6opts), FieldLenField("optlen", None, length_of="nispservers"), IP6ListField("nispservers", [], length_from = lambda pkt: pkt.optlen) ] class DomainNameField(StrLenField): def getfield(self, pkt, s): l = self.length_from(pkt) return s[l:], self.m2i(pkt,s[:l]) def i2len(self, pkt, x): return len(self.i2m(pkt, x)) def m2i(self, pkt, x): cur = [] while x: l = ord(x[0]) cur.append(x[1:1+l]) x = x[l+1:] ret_str = ".".join(cur) return ret_str def i2m(self, pkt, x): if not x: return "" tmp = "".join(map(lambda z: chr(len(z))+z, x.split('.'))) return tmp class DHCP6OptNISDomain(_DHCP6OptGuessPayload): #RFC3898 name = "DHCP6 Option - NIS Domain Name" fields_desc = [ ShortEnumField("optcode", 29, dhcp6opts), FieldLenField("optlen", None, length_of="nisdomain"), DomainNameField("nisdomain", "", length_from = lambda pkt: pkt.optlen) ] class DHCP6OptNISPDomain(_DHCP6OptGuessPayload): #RFC3898 name = "DHCP6 Option - NIS+ Domain Name" fields_desc = [ ShortEnumField("optcode", 30, dhcp6opts), FieldLenField("optlen", None, length_of="nispdomain"), DomainNameField("nispdomain", "", length_from= lambda pkt: pkt.optlen) ] class DHCP6OptSNTPServers(_DHCP6OptGuessPayload): #RFC4075 name = "DHCP6 option - SNTP Servers" fields_desc = [ ShortEnumField("optcode", 31, dhcp6opts), FieldLenField("optlen", None, length_of="sntpservers"), IP6ListField("sntpservers", [], length_from = lambda pkt: pkt.optlen) ] IRT_DEFAULT=86400 IRT_MINIMUM=600 class DHCP6OptInfoRefreshTime(_DHCP6OptGuessPayload): #RFC4242 name = "DHCP6 Option - Information Refresh Time" fields_desc = [ ShortEnumField("optcode", 32, dhcp6opts), ShortField("optlen", 4), IntField("reftime", IRT_DEFAULT)] # One day class DHCP6OptBCMCSDomains(_DHCP6OptGuessPayload): #RFC4280 name = "DHCP6 Option - BCMCS Domain Name List" fields_desc = [ ShortEnumField("optcode", 33, dhcp6opts), FieldLenField("optlen", None, length_of="bcmcsdomains"), DomainNameListField("bcmcsdomains", [], length_from = lambda pkt: pkt.optlen) ] class DHCP6OptBCMCSServers(_DHCP6OptGuessPayload): #RFC4280 name = "DHCP6 Option - BCMCS Addresses List" fields_desc = [ ShortEnumField("optcode", 34, dhcp6opts), FieldLenField("optlen", None, length_of="bcmcsservers"), IP6ListField("bcmcsservers", [], length_from= lambda pkt: pkt.optlen) ] # TODO : Does Nothing at the moment class DHCP6OptGeoConf(_DHCP6OptGuessPayload): #RFC-ietf-geopriv-dhcp-civil-09.txt name = "" fields_desc = [ ShortEnumField("optcode", 36, dhcp6opts), FieldLenField("optlen", None, length_of="optdata"), StrLenField("optdata", "", length_from = lambda pkt: pkt.optlen) ] # TODO: see if we encounter opaque values from vendor devices class DHCP6OptRemoteID(_DHCP6OptGuessPayload): #RFC4649 name = "DHCP6 Option - Relay Agent Remote-ID" fields_desc = [ ShortEnumField("optcode", 37, dhcp6opts), FieldLenField("optlen", None, length_of="remoteid", adjust = lambda pkt,x: x+4), IntEnumField("enterprisenum", None, iana_enterprise_num), StrLenField("remoteid", "", length_from = lambda pkt: pkt.optlen-4) ] # TODO : 'subscriberid' default value should be at least 1 byte long class DHCP6OptSubscriberID(_DHCP6OptGuessPayload): #RFC4580 name = "DHCP6 Option - Subscriber ID" fields_desc = [ ShortEnumField("optcode", 38, dhcp6opts), FieldLenField("optlen", None, length_of="subscriberid"), StrLenField("subscriberid", "", length_from = lambda pkt: pkt.optlen) ] # TODO : "The data in the Domain Name field MUST be encoded # as described in Section 8 of [5]" class DHCP6OptClientFQDN(_DHCP6OptGuessPayload): #RFC4704 name = "DHCP6 Option - Client FQDN" fields_desc = [ ShortEnumField("optcode", 39, dhcp6opts), FieldLenField("optlen", None, length_of="fqdn", adjust = lambda pkt,x: x+1), BitField("res", 0, 5), FlagsField("flags", 0, 3, "SON" ), DomainNameField("fqdn", "", length_from = lambda pkt: pkt.optlen-1) ] class DHCP6OptRelayAgentERO(_DHCP6OptGuessPayload): # RFC4994 name = "DHCP6 Option - RelayRequest Option" fields_desc = [ ShortEnumField("optcode", 43, dhcp6opts), FieldLenField("optlen", None, length_of="reqopts", fmt="!H"), _OptReqListField("reqopts", [23, 24], length_from = lambda pkt: pkt.optlen) ] ##################################################################### ### DHCPv6 messages ### ##################################################################### # Some state parameters of the protocols that should probably be # useful to have in the configuration (and keep up-to-date) DHCP6RelayAgentUnicastAddr="" DHCP6RelayHopCount="" DHCP6ServerUnicastAddr="" DHCP6ClientUnicastAddr="" DHCP6ClientIA_TA="" DHCP6ClientIA_NA="" DHCP6ClientIAID="" T1="" # Voir 2462 T2="" # Voir 2462 DHCP6ServerDUID="" DHCP6CurrentTransactionID="" # devrait etre utilise pour matcher une # reponse et mis a jour en mode client par une valeur aleatoire pour # laquelle on attend un retour de la part d'un serveur. DHCP6PrefVal="" # la valeur de preference a utiliser dans # les options preference # Emitted by : # - server : ADVERTISE, REPLY, RECONFIGURE, RELAY-REPL (vers relay) # - client : SOLICIT, REQUEST, CONFIRM, RENEW, REBIND, RELEASE, DECLINE, # INFORMATION REQUEST # - relay : RELAY-FORW (toward server) class _DHCP6GuessPayload(Packet): def guess_payload_class(self, payload): if len(payload) > 1 : print ord(payload[0]) return get_cls(dhcp6opts.get(ord(payload[0]),"DHCP6OptUnknown"), conf.raw_layer) return conf.raw_layer ##################################################################### ## DHCPv6 messages sent between Clients and Servers (types 1 to 11) # Comme specifie en section 15.1 de la RFC 3315, les valeurs de # transaction id sont selectionnees de maniere aleatoire par le client # a chaque emission et doivent matcher dans les reponses faites par # les clients class DHCP6(_DHCP6OptGuessPayload): name = "DHCPv6 Generic Message)" fields_desc = [ ByteEnumField("msgtype",None,dhcp6types), X3BytesField("trid",0x000000) ] overload_fields = { UDP: {"sport": 546, "dport": 547} } def hashret(self): return struct.pack("!I", self.trid)[1:4] ##################################################################### # Solicit Message : sect 17.1.1 RFC3315 # - sent by client # - must include a client identifier option # - the client may include IA options for any IAs to which it wants the # server to assign address # - The client use IA_NA options to request the assignment of # non-temporary addresses and uses IA_TA options to request the # assignment of temporary addresses # - The client should include an Option Request option to indicate the # options the client is interested in receiving (eventually # including hints) # - The client includes a Reconfigure Accept option if is willing to # accept Reconfigure messages from the server. # Le cas du send and reply est assez particulier car suivant la # presence d'une option rapid commit dans le solicit, l'attente # s'arrete au premier message de reponse recu ou alors apres un # timeout. De la meme maniere, si un message Advertise arrive avec une # valeur de preference de 255, il arrete l'attente et envoie une # Request. # - The client announces its intention to use DHCP authentication by # including an Authentication option in its solicit message. The # server selects a key for the client based on the client's DUID. The # client and server use that key to authenticate all DHCP messages # exchanged during the session class DHCP6_Solicit(DHCP6): name = "DHCPv6 Solicit Message" msgtype = 1 overload_fields = { UDP: {"sport": 546, "dport": 547} } ##################################################################### # Advertise Message # - sent by server # - Includes a server identifier option # - Includes a client identifier option # - the client identifier option must match the client's DUID # - transaction ID must match class DHCP6_Advertise(DHCP6): name = "DHCPv6 Advertise Message" msgtype = 2 overload_fields = { UDP: {"sport": 547, "dport": 546} } def answers(self, other): return (isinstance(other,DHCP6_Solicit) and other.msgtype == 1 and self.trid == other.trid) ##################################################################### # Request Message # - sent by clients # - includes a server identifier option # - the content of Server Identifier option must match server's DUID # - includes a client identifier option # - must include an ORO Option (even with hints) p40 # - can includes a reconfigure Accept option indicating whether or # not the client is willing to accept Reconfigure messages from # the server (p40) # - When the server receives a Request message via unicast from a # client to which the server has not sent a unicast option, the server # discards the Request message and responds with a Reply message # containinig Status Code option with the value UseMulticast, a Server # Identifier Option containing the server's DUID, the client # Identifier option from the client message and no other option. class DHCP6_Request(DHCP6): name = "DHCPv6 Request Message" msgtype = 3 ##################################################################### # Confirm Message # - sent by clients # - must include a clien identifier option # - When the server receives a Confirm Message, the server determines # whether the addresses in the Confirm message are appropriate for the # link to which the client is attached. cf p50 class DHCP6_Confirm(DHCP6): name = "DHCPv6 Confirm Message" msgtype = 4 ##################################################################### # Renew Message # - sent by clients # - must include a server identifier option # - content of server identifier option must match the server's identifier # - must include a client identifier option # - the clients includes any IA assigned to the interface that may # have moved to a new link, along with the addresses associated with # those IAs in its confirm messages # - When the server receives a Renew message that contains an IA # option from a client, it locates the client's binding and verifies # that the information in the IA from the client matches the # information for that client. If the server cannot find a client # entry for the IA the server returns the IA containing no addresses # with a status code option est to NoBinding in the Reply message. cf # p51 pour le reste. class DHCP6_Renew(DHCP6): name = "DHCPv6 Renew Message" msgtype = 5 ##################################################################### # Rebind Message # - sent by clients # - must include a client identifier option # cf p52 class DHCP6_Rebind(DHCP6): name = "DHCPv6 Rebind Message" msgtype = 6 ##################################################################### # Reply Message # - sent by servers # - the message must include a server identifier option # - transaction-id field must match the value of original message # The server includes a Rapid Commit option in the Reply message to # indicate that the reply is in response to a solicit message # - if the client receives a reply message with a Status code option # with the value UseMulticast, the client records the receipt of the # message and sends subsequent messages to the server through the # interface on which the message was received using multicast. The # client resends the original message using multicast # - When the client receives a NotOnLink status from the server in # response to a Confirm message, the client performs DHCP server # solicitation as described in section 17 and client-initiated # configuration as descrribed in section 18 (RFC 3315) # - when the client receives a NotOnLink status from the server in # response to a Request, the client can either re-issue the Request # without specifying any addresses or restart the DHCP server # discovery process. # - the server must include a server identifier option containing the # server's DUID in the Reply message class DHCP6_Reply(DHCP6): name = "DHCPv6 Reply Message" msgtype = 7 overload_fields = { UDP: {"sport": 547, "dport": 546} } def answers(self, other): types = (DHCP6_InfoRequest, DHCP6_Confirm, DHCP6_Rebind, DHCP6_Decline, DHCP6_Request, DHCP6_Release, DHCP6_Renew) return (isinstance(other, types) and self.trid == other.trid) ##################################################################### # Release Message # - sent by clients # - must include a server identifier option # cf p53 class DHCP6_Release(DHCP6): name = "DHCPv6 Release Message" msgtype = 8 ##################################################################### # Decline Message # - sent by clients # - must include a client identifier option # - Server identifier option must match server identifier # - The addresses to be declined must be included in the IAs. Any # addresses for the IAs the client wishes to continue to use should # not be in added to the IAs. # - cf p54 class DHCP6_Decline(DHCP6): name = "DHCPv6 Decline Message" msgtype = 9 ##################################################################### # Reconfigure Message # - sent by servers # - must be unicast to the client # - must include a server identifier option # - must include a client identifier option that contains the client DUID # - must contain a Reconfigure Message Option and the message type # must be a valid value # - the server sets the transaction-id to 0 # - The server must use DHCP Authentication in the Reconfigure # message. Autant dire que ca va pas etre le type de message qu'on va # voir le plus souvent. class DHCP6_Reconf(DHCP6): name = "DHCPv6 Reconfigure Message" msgtype = 10 overload_fields = { UDP: { "sport": 547, "dport": 546 } } ##################################################################### # Information-Request Message # - sent by clients when needs configuration information but no # addresses. # - client should include a client identifier option to identify # itself. If it doesn't the server is not able to return client # specific options or the server can choose to not respond to the # message at all. The client must include a client identifier option # if the message will be authenticated. # - client must include an ORO of option she's interested in receiving # (can include hints) class DHCP6_InfoRequest(DHCP6): name = "DHCPv6 Information Request Message" msgtype = 11 ##################################################################### # sent between Relay Agents and Servers # # Normalement, doit inclure une option "Relay Message Option" # peut en inclure d'autres. # voir section 7.1 de la 3315 # Relay-Forward Message # - sent by relay agents to servers # If the relay agent relays messages to the All_DHCP_Servers multicast # address or other multicast addresses, it sets the Hop Limit field to # 32. class DHCP6_RelayForward(_DHCP6GuessPayload,Packet): name = "DHCPv6 Relay Forward Message (Relay Agent/Server Message)" fields_desc = [ ByteEnumField("msgtype", 12, dhcp6types), ByteField("hopcount", None), IP6Field("linkaddr", "::"), IP6Field("peeraddr", "::") ] def hashret(self): # we filter on peer address field return inet_pton(socket.AF_INET6, self.peeraddr) ##################################################################### # sent between Relay Agents and Servers # Normalement, doit inclure une option "Relay Message Option" # peut en inclure d'autres. # Les valeurs des champs hop-count, link-addr et peer-addr # sont copiees du messsage Forward associe. POur le suivi de session. # Pour le moment, comme decrit dans le commentaire, le hashret # se limite au contenu du champ peer address. # Voir section 7.2 de la 3315. # Relay-Reply Message # - sent by servers to relay agents # - if the solicit message was received in a Relay-Forward message, # the server constructs a relay-reply message with the Advertise # message in the payload of a relay-message. cf page 37/101. Envoie de # ce message en unicast au relay-agent. utilisation de l'adresse ip # presente en ip source du paquet recu class DHCP6_RelayReply(DHCP6_RelayForward): name = "DHCPv6 Relay Reply Message (Relay Agent/Server Message)" msgtype = 13 def hashret(self): # We filter on peer address field. return inet_pton(socket.AF_INET6, self.peeraddr) def answers(self, other): return (isinstance(other, DHCP6_RelayForward) and self.hopcount == other.hopcount and self.linkaddr == other.linkaddr and self.peeraddr == other.peeraddr ) dhcp6_cls_by_type = { 1: "DHCP6_Solicit", 2: "DHCP6_Advertise", 3: "DHCP6_Request", 4: "DHCP6_Confirm", 5: "DHCP6_Renew", 6: "DHCP6_Rebind", 7: "DHCP6_Reply", 8: "DHCP6_Release", 9: "DHCP6_Decline", 10: "DHCP6_Reconf", 11: "DHCP6_InfoRequest", 12: "DHCP6_RelayForward", 13: "DHCP6_RelayReply" } def _dhcp6_dispatcher(x, *args, **kargs): cls = conf.raw_layer if len(x) >= 2: cls = get_cls(dhcp6_cls_by_type.get(ord(x[0]), "Raw"), conf.raw_layer) return cls(x, *args, **kargs) bind_bottom_up(UDP, _dhcp6_dispatcher, { "dport": 547 } ) bind_bottom_up(UDP, _dhcp6_dispatcher, { "dport": 546 } ) class DHCPv6_am(AnsweringMachine): function_name = "dhcp6d" filter = "udp and port 546 and port 547" send_function = staticmethod(send) def usage(self): msg = """ dhcp6d( dns="2001:500::1035", domain="localdomain, local", duid=None) iface=conf.iface6, advpref=255, sntpservers=None, sipdomains=None, sipservers=None, nisdomain=None, nisservers=None, nispdomain=None, nispservers=None, bcmcsdomain=None, bcmcsservers=None) debug : When set, additional debugging information is printed. duid : some DUID class (DUID_LLT, DUID_LL or DUID_EN). If none is provided a DUID_LLT is constructed based on the MAC address of the sending interface and launch time of dhcp6d answering machine. iface : the interface to listen/reply on if you do not want to use conf.iface6. advpref : Value in [0,255] given to Advertise preference field. By default, 255 is used. Be aware that this specific value makes clients stops waiting for further Advertise messages from other servers. dns : list of recursive DNS servers addresses (as a string or list). By default, it is set empty and the associated DHCP6OptDNSServers option is inactive. See RFC 3646 for details. domain : a list of DNS search domain (as a string or list). By default, it is empty and the associated DHCP6OptDomains option is inactive. See RFC 3646 for details. sntpservers : a list of SNTP servers IPv6 addresses. By default, it is empty and the associated DHCP6OptSNTPServers option is inactive. sipdomains : a list of SIP domains. By default, it is empty and the associated DHCP6OptSIPDomains option is inactive. See RFC 3319 for details. sipservers : a list of SIP servers IPv6 addresses. By default, it is empty and the associated DHCP6OptSIPDomains option is inactive. See RFC 3319 for details. nisdomain : a list of NIS domains. By default, it is empty and the associated DHCP6OptNISDomains option is inactive. See RFC 3898 for details. See RFC 3646 for details. nisservers : a list of NIS servers IPv6 addresses. By default, it is empty and the associated DHCP6OptNISServers option is inactive. See RFC 3646 for details. nispdomain : a list of NIS+ domains. By default, it is empty and the associated DHCP6OptNISPDomains option is inactive. See RFC 3898 for details. nispservers : a list of NIS+ servers IPv6 addresses. By default, it is empty and the associated DHCP6OptNISServers option is inactive. See RFC 3898 for details. bcmcsdomain : a list of BCMCS domains. By default, it is empty and the associated DHCP6OptBCMCSDomains option is inactive. See RFC 4280 for details. bcmcsservers : a list of BCMCS servers IPv6 addresses. By default, it is empty and the associated DHCP6OptBCMCSServers option is inactive. See RFC 4280 for details. If you have a need for others, just ask ... or provide a patch.""" print msg def parse_options(self, dns="2001:500::1035", domain="localdomain, local", startip="2001:db8::1", endip="2001:db8::20", duid=None, sntpservers=None, sipdomains=None, sipservers=None, nisdomain=None, nisservers=None, nispdomain=None, nispservers=None, bcmcsservers=None, bcmcsdomains=None, iface=None, debug=0, advpref=255): def norm_list(val, param_name): if val is None: return None if type(val) is list: return val elif type(val) is str: l = val.split(',') return map(lambda x: x.strip(), l) else: print "Bad '%s' parameter provided." % param_name self.usage() return -1 if iface is None: iface = conf.iface6 self.debug = debug # Dictionary of provided DHCPv6 options, keyed by option type self.dhcpv6_options={} for o in [(dns, "dns", 23, lambda x: DHCP6OptDNSServers(dnsservers=x)), (domain, "domain", 24, lambda x: DHCP6OptDNSDomains(dnsdomains=x)), (sntpservers, "sntpservers", 31, lambda x: DHCP6OptSNTPServers(sntpservers=x)), (sipservers, "sipservers", 22, lambda x: DHCP6OptSIPServers(sipservers=x)), (sipdomains, "sipdomains", 21, lambda x: DHCP6OptSIPDomains(sipdomains=x)), (nisservers, "nisservers", 27, lambda x: DHCP6OptNISServers(nisservers=x)), (nisdomain, "nisdomain", 29, lambda x: DHCP6OptNISDomain(nisdomain=(x+[""])[0])), (nispservers, "nispservers", 28, lambda x: DHCP6OptNISPServers(nispservers=x)), (nispdomain, "nispdomain", 30, lambda x: DHCP6OptNISPDomain(nispdomain=(x+[""])[0])), (bcmcsservers, "bcmcsservers", 33, lambda x: DHCP6OptBCMCSServers(bcmcsservers=x)), (bcmcsdomains, "bcmcsdomains", 34, lambda x: DHCP6OptBCMCSDomains(bcmcsdomains=x))]: opt = norm_list(o[0], o[1]) if opt == -1: # Usage() was triggered return False elif opt is None: # We won't return that option pass else: self.dhcpv6_options[o[2]] = o[3](opt) if self.debug: print "\n[+] List of active DHCPv6 options:" opts = self.dhcpv6_options.keys() opts.sort() for i in opts: print " %d: %s" % (i, repr(self.dhcpv6_options[i])) # Preference value used in Advertise. self.advpref = advpref # IP Pool self.startip = startip self.endip = endip # XXX TODO Check IPs are in same subnet #### # The interface we are listening/replying on self.iface = iface #### # Generate a server DUID if duid is not None: self.duid = duid else: # Timeval from time import gmtime, strftime, mktime epoch = (2000, 1, 1, 0, 0, 0, 5, 1, 0) delta = mktime(epoch) - mktime(gmtime(0)) timeval = time.time() - delta # Mac Address rawmac = get_if_raw_hwaddr(iface)[1] mac = ":".join(map(lambda x: "%.02x" % ord(x), list(rawmac))) self.duid = DUID_LLT(timeval = timeval, lladdr = mac) if self.debug: print "\n[+] Our server DUID:" self.duid.show(label_lvl=" "*4) #### # Find the source address we will use l = filter(lambda x: x[2] == iface and in6_islladdr(x[0]), in6_getifaddr()) if not l: warning("Unable to get a Link-Local address") return self.src_addr = l[0][0] #### # Our leases self.leases = {} if self.debug: print "\n[+] Starting DHCPv6 service on %s:" % self.iface def is_request(self, p): if not IPv6 in p: return False src = p[IPv6].src dst = p[IPv6].dst p = p[IPv6].payload if not isinstance(p, UDP) or p.sport != 546 or p.dport != 547 : return False p = p.payload if not isinstance(p, DHCP6): return False # Message we considered client messages : # Solicit (1), Request (3), Confirm (4), Renew (5), Rebind (6) # Decline (9), Release (8), Information-request (11), if not (p.msgtype in [1, 3, 4, 5, 6, 8, 9, 11]): return False # Message validation following section 15 of RFC 3315 if ((p.msgtype == 1) or # Solicit (p.msgtype == 6) or # Rebind (p.msgtype == 4)): # Confirm if ((not DHCP6OptClientId in p) or DHCP6OptServerId in p): return False if (p.msgtype == 6 or # Rebind p.msgtype == 4): # Confirm # XXX We do not reply to Confirm or Rebind as we # XXX do not support address assignment return False elif (p.msgtype == 3 or # Request p.msgtype == 5 or # Renew p.msgtype == 8): # Release # Both options must be present if ((not DHCP6OptServerId in p) or (not DHCP6OptClientId in p)): return False # provided server DUID must match ours duid = p[DHCP6OptServerId].duid if (type(duid) != type(self.duid)): return False if str(duid) != str(self.duid): return False if (p.msgtype == 5 or # Renew p.msgtype == 8): # Release # XXX We do not reply to Renew or Release as we # XXX do not support address assignment return False elif p.msgtype == 9: # Decline # XXX We should check if we are tracking that client if not self.debug: return False bo = Color.bold g = Color.green + bo b = Color.blue + bo n = Color.normal r = Color.red vendor = in6_addrtovendor(src) if (vendor and vendor != "UNKNOWN"): vendor = " [" + b + vendor + n + "]" else: vendor = "" src = bo + src + n it = p addrs = [] while it: l = [] if isinstance(it, DHCP6OptIA_NA): l = it.ianaopts elif isinstance(it, DHCP6OptIA_TA): l = it.iataopts opsaddr = filter(lambda x: isinstance(x, DHCP6OptIAAddress),l) a=map(lambda x: x.addr, opsaddr) addrs += a it = it.payload addrs = map(lambda x: bo + x + n, addrs) if debug: msg = r + "[DEBUG]" + n + " Received " + g + "Decline" + n msg += " from " + bo + src + vendor + " for " msg += ", ".join(addrs)+ n print msg # See sect 18.1.7 # Sent by a client to warn us she has determined # one or more addresses assigned to her is already # used on the link. # We should simply log that fact. No messaged should # be sent in return. # - Message must include a Server identifier option # - the content of the Server identifier option must # match the server's identifier # - the message must include a Client Identifier option return False elif p.msgtype == 11: # Information-Request if DHCP6OptServerId in p: duid = p[DHCP6OptServerId].duid if (type(duid) != type(self.duid)): return False if str(duid) != str(self.duid): return False if ((DHCP6OptIA_NA in p) or (DHCP6OptIA_TA in p) or (DHCP6OptIA_PD in p)): return False else: return False return True def print_reply(self, req, reply): def norm(s): if s.startswith("DHCPv6 "): s = s[7:] if s.endswith(" Message"): s = s[:-8] return s if reply is None: return bo = Color.bold g = Color.green + bo b = Color.blue + bo n = Color.normal reqtype = g + norm(req.getlayer(UDP).payload.name) + n reqsrc = req.getlayer(IPv6).src vendor = in6_addrtovendor(reqsrc) if (vendor and vendor != "UNKNOWN"): vendor = " [" + b + vendor + n + "]" else: vendor = "" reqsrc = bo + reqsrc + n reptype = g + norm(reply.getlayer(UDP).payload.name) + n print "Sent %s answering to %s from %s%s" % (reptype, reqtype, reqsrc, vendor) def make_reply(self, req): req_mac_src = req.src req_mac_dst = req.dst p = req[IPv6] req_src = p.src req_dst = p.dst p = p.payload.payload msgtype = p.msgtype trid = p.trid if msgtype == 1: # SOLICIT (See Sect 17.1 and 17.2 of RFC 3315) # XXX We don't support address or prefix assignment # XXX We also do not support relay function --arno client_duid = p[DHCP6OptClientId].duid resp = IPv6(src=self.src_addr, dst=req_src) resp /= UDP(sport=547, dport=546) if p.haslayer(DHCP6OptRapidCommit): # construct a Reply packet resp /= DHCP6_Reply(trid=trid) resp /= DHCP6OptRapidCommit() # See 17.1.2 resp /= DHCP6OptServerId(duid = self.duid) resp /= DHCP6OptClientId(duid = client_duid) else: # No Rapid Commit in the packet. Reply with an Advertise if (p.haslayer(DHCP6OptIA_NA) or p.haslayer(DHCP6OptIA_TA)): # XXX We don't assign addresses at the moment msg = "Scapy6 dhcp6d does not support address assignment" resp /= DHCP6_Advertise(trid = trid) resp /= DHCP6OptStatusCode(statuscode=2, statusmsg=msg) resp /= DHCP6OptServerId(duid = self.duid) resp /= DHCP6OptClientId(duid = client_duid) elif p.haslayer(DHCP6OptIA_PD): # XXX We don't assign prefixes at the moment msg = "Scapy6 dhcp6d does not support prefix assignment" resp /= DHCP6_Advertise(trid = trid) resp /= DHCP6OptStatusCode(statuscode=6, statusmsg=msg) resp /= DHCP6OptServerId(duid = self.duid) resp /= DHCP6OptClientId(duid = client_duid) else: # Usual case, no request for prefixes or addresse resp /= DHCP6_Advertise(trid = trid) resp /= DHCP6OptPref(prefval = self.advpref) resp /= DHCP6OptServerId(duid = self.duid) resp /= DHCP6OptClientId(duid = client_duid) resp /= DHCP6OptReconfAccept() # See which options should be included reqopts = [] if p.haslayer(DHCP6OptOptReq): # add only asked ones reqopts = p[DHCP6OptOptReq].reqopts for o in self.dhcpv6_options.keys(): if o in reqopts: resp /= self.dhcpv6_options[o] else: # advertise everything we have available for o in self.dhcpv6_options.keys(): resp /= self.dhcpv6_options[o] return resp elif msgtype == 3: #REQUEST (INFO-REQUEST is further below) client_duid = p[DHCP6OptClientId].duid resp = IPv6(src=self.src_addr, dst=req_src) resp /= UDP(sport=547, dport=546) resp /= DHCP6_Solicit(trid=trid) resp /= DHCP6OptServerId(duid = self.duid) resp /= DHCP6OptClientId(duid = client_duid) # See which options should be included reqopts = [] if p.haslayer(DHCP6OptOptReq): # add only asked ones reqopts = p[DHCP6OptOptReq].reqopts for o in self.dhcpv6_options.keys(): if o in reqopts: resp /= self.dhcpv6_options[o] else: # advertise everything we have available. # Should not happen has clients MUST include # and ORO in requests (sec 18.1.1) -- arno for o in self.dhcpv6_options.keys(): resp /= self.dhcpv6_options[o] return resp elif msgtype == 4: # CONFIRM # see Sect 18.1.2 # Client want to check if addresses it was assigned # are still appropriate # Server must discard any Confirm messages that # do not include a Client Identifier option OR # THAT DO INCLUDE a Server Identifier Option # XXX we must discard the SOLICIT if it is received with # a unicast destination address pass elif msgtype == 5: # RENEW # see Sect 18.1.3 # Clients want to extend lifetime of assigned addresses # and update configuration parameters. This message is sent # specifically to the server that provided her the info # - Received message must include a Server Identifier # option. # - the content of server identifier option must match # the server's identifier. # - the message must include a Client identifier option pass elif msgtype == 6: # REBIND # see Sect 18.1.4 # Same purpose as the Renew message but sent to any # available server after he received no response # to its previous Renew message. # - Message must include a Client Identifier Option # - Message can't include a Server identifier option # XXX we must discard the SOLICIT if it is received with # a unicast destination address pass elif msgtype == 8: # RELEASE # See section 18.1.6 # Message is sent to the server to indicate that # she will no longer use the addresses that was assigned # We should parse the message and verify our dictionary # to log that fact. # - The message must include a server identifier option # - The content of the Server Identifier option must # match the server's identifier # - the message must include a Client Identifier option pass elif msgtype == 9: # DECLINE # See section 18.1.7 pass elif msgtype == 11: # INFO-REQUEST client_duid = None if not p.haslayer(DHCP6OptClientId): if self.debug: warning("Received Info Request message without Client Id option") else: client_duid = p[DHCP6OptClientId].duid resp = IPv6(src=self.src_addr, dst=req_src) resp /= UDP(sport=547, dport=546) resp /= DHCP6_Reply(trid=trid) resp /= DHCP6OptServerId(duid = self.duid) if client_duid: resp /= DHCP6OptClientId(duid = client_duid) # Stack requested options if available reqopts = [] if p.haslayer(DHCP6OptOptReq): reqopts = p[DHCP6OptOptReq].reqopts for o in self.dhcpv6_options.keys(): resp /= self.dhcpv6_options[o] return resp else: # what else ? pass # - We won't support reemission # - We won't support relay role, nor relay forwarded messages # at the beginning