# This file is part of Scapy # See http://www.secdev.org/projects/scapy for more information # 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] """ from __future__ import print_function import socket import struct import time from scapy.ansmachine import AnsweringMachine from scapy.arch import get_if_raw_hwaddr, in6_getifaddr from scapy.config import conf from scapy.data import EPOCH, ETHER_ANY from scapy.compat import raw, orb, chb from scapy.error import warning from scapy.fields import BitField, ByteEnumField, ByteField, FieldLenField, \ FlagsField, IntEnumField, IntField, MACField, PacketField, \ PacketListField, ShortEnumField, ShortField, StrField, StrFixedLenField, \ StrLenField, UTCTimeField, X3BytesField, XIntField, XShortEnumField, \ PacketLenField, UUIDField, FieldListField from scapy.data import IANA_ENTERPRISE_NUMBERS from scapy.layers.inet import UDP from scapy.layers.inet6 import DomainNameListField, IP6Field, IP6ListField, \ IPv6 from scapy.packet import Packet, bind_bottom_up from scapy.pton_ntop import inet_pton from scapy.sendrecv import send from scapy.themes import Color from scapy.utils6 import in6_addrtovendor, in6_islladdr import scapy.modules.six as six ############################################################################# # Helpers ## ############################################################################# def get_cls(name, fallback_cls): return globals().get(name, fallback_cls) 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(orb(x[0]), "Raw"), conf.raw_layer) return cls(x, *args, **kargs) ############################################################################# ############################################################################# # 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 40: "OPTION_PANA_AGENT", # RFC5192 41: "OPTION_NEW_POSIX_TIMEZONE", # RFC4833 42: "OPTION_NEW_TZDB_TIMEZONE", # RFC4833 48: "OPTION_LQ_CLIENT_LINK", # RFC5007 59: "OPT_BOOTFILE_URL", # RFC5970 60: "OPT_BOOTFILE_PARAM", # RFC5970 61: "OPTION_CLIENT_ARCH_TYPE", # RFC5970 62: "OPTION_NII", # RFC5970 65: "OPTION_ERP_LOCAL_DOMAIN_NAME", # RFC6440 66: "OPTION_RELAY_SUPPLIED_OPTIONS", # RFC6422 68: "OPTION_VSS", # RFC6607 79: "OPTION_CLIENT_LINKLAYER_ADDR"} # RFC6939 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 # noqa: E501 37: "DHCP6OptRemoteID", # RFC4649 38: "DHCP6OptSubscriberID", # RFC4580 39: "DHCP6OptClientFQDN", # RFC4704 40: "DHCP6OptPanaAuthAgent", # RFC-ietf-dhc-paa-option-05.txt # noqa: E501 41: "DHCP6OptNewPOSIXTimeZone", # RFC4833 42: "DHCP6OptNewTZDBTimeZone", # RFC4833 43: "DHCP6OptRelayAgentERO", # RFC4994 # 44: "DHCP6OptLQQuery", #RFC5007 # 45: "DHCP6OptLQClientData", #RFC5007 # 46: "DHCP6OptLQClientTime", #RFC5007 # 47: "DHCP6OptLQRelayData", #RFC5007 48: "DHCP6OptLQClientLink", # RFC5007 59: "DHCP6OptBootFileUrl", # RFC5790 60: "DHCP6OptBootFileParam", # RFC5970 61: "DHCP6OptClientArchType", # RFC5970 62: "DHCP6OptClientNetworkInterId", # RFC5970 65: "DHCP6OptERPDomain", # RFC6440 66: "DHCP6OptRelaySuppliedOpt", # RFC6422 68: "DHCP6OptVSS", # RFC6607 79: "DHCP6OptClientLinkLayerAddr", # RFC6939 } # 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", 4: "UUID"} # 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(UTCTimeField): def __init__(self, *args, **kargs): epoch_2000 = (2000, 1, 1, 0, 0, 0, 5, 1, 0) # required Epoch UTCTimeField.__init__(self, epoch=epoch_2000, *args, **kargs) 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)] 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_NUMBERS), 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)] class DUID_UUID(Packet): # RFC 6355 name = "DUID - Based on UUID" fields_desc = [ShortEnumField("type", 4, duidtypes), UUIDField("uuid", None, uuid_fmt=UUIDField.FORMAT_BE)] duid_cls = {1: "DUID_LLT", 2: "DUID_EN", 3: "DUID_LL", 4: "DUID_UUID"} ##################################################################### # DHCPv6 Options classes # ##################################################################### class _DHCP6OptGuessPayload(Packet): @classmethod def _just_guess_payload_class(cls, payload): # try to guess what option is in the 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 def guess_payload_class(self, payload): # this method is used in case of all derived classes # from _DHCP6OptGuessPayload in this file cls = _DHCP6OptGuessPayload._just_guess_payload_class(payload) return cls @classmethod def dispatch_hook(cls, payload=None, *args, **kargs): # this classmethod is used in case of list of different suboptions # e.g. in ianaopts in DHCP6OptIA_NA cls_ = cls._just_guess_payload_class(payload) 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): __slots__ = ["length_from"] def __init__(self, name, default, length_from=None): StrField.__init__(self, name, default) self.length_from = length_from def i2m(self, pkt, i): return raw(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): tmp_len = self.length_from(pkt) return s[tmp_len:], self.m2i(pkt, s[:tmp_len]) 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), StrLenField("iaaddropts", "", length_from=lambda pkt: pkt.optlen - 24)] def guess_payload_class(self, payload): return conf.padding_layer 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), PacketListField("ianaopts", [], _DHCP6OptGuessPayload, length_from=lambda pkt: pkt.optlen - 12)] 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), PacketListField("iataopts", [], _DHCP6OptGuessPayload, 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 y in dhcp6opts: 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") # noqa: E501 return r r.append(struct.unpack("!H", x[:2])[0]) x = x[2:] return r def i2m(self, pkt, x): return b"".join(struct.pack('!H', y) for y in 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"), # noqa: E501 _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 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(len(raw(x)) for x in z) def getfield(self, pkt, s): tmp_len = self.length_from(pkt) lst = [] remain, payl = s[:tmp_len], s[tmp_len:] 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_NUMBERS), _VendorClassDataField("vcdata", [], VENDOR_CLASS_DATA, length_from=lambda pkt: pkt.optlen - 4)] # noqa: E501 # 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_NUMBERS), _VendorClassDataField("vso", [], VENDOR_SPECIFIC_OPTION, length_from=lambda pkt: pkt.optlen - 4)] # noqa: E501 # 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 Information-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)] 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 + 25), 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 # noqa: E501 StrLenField("iaprefopts", "", length_from=lambda pkt: pkt.optlen - 25)] 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", fmt="!H", adjust=lambda pkt, x: x + 12), XIntField("iaid", None), IntField("T1", None), IntField("T2", None), PacketListField("iapdopt", [], _DHCP6OptGuessPayload, 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): tmp_len = self.length_from(pkt) return s[tmp_len:], self.m2i(pkt, s[:tmp_len]) def i2len(self, pkt, x): return len(self.i2m(pkt, x)) def m2i(self, pkt, x): cur = [] while x: tmp_len = orb(x[0]) cur.append(x[1:1 + tmp_len]) x = x[tmp_len + 1:] return b".".join(cur) def i2m(self, pkt, x): if not x: return b"" return b"".join(chb(len(z)) + z for z in x.split(b'.')) 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 # noqa: E501 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_NUMBERS), 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 DHCP6OptPanaAuthAgent(_DHCP6OptGuessPayload): # RFC5192 name = "DHCP6 PANA Authentication Agent Option" fields_desc = [ShortEnumField("optcode", 40, dhcp6opts), FieldLenField("optlen", None, length_of="paaaddr"), IP6ListField("paaaddr", [], length_from=lambda pkt: pkt.optlen)] class DHCP6OptNewPOSIXTimeZone(_DHCP6OptGuessPayload): # RFC4833 name = "DHCP6 POSIX Timezone Option" fields_desc = [ShortEnumField("optcode", 41, dhcp6opts), FieldLenField("optlen", None, length_of="optdata"), StrLenField("optdata", "", length_from=lambda pkt: pkt.optlen)] class DHCP6OptNewTZDBTimeZone(_DHCP6OptGuessPayload): # RFC4833 name = "DHCP6 TZDB Timezone Option" fields_desc = [ShortEnumField("optcode", 42, dhcp6opts), FieldLenField("optlen", None, length_of="optdata"), StrLenField("optdata", "", length_from=lambda pkt: pkt.optlen)] 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)] class DHCP6OptLQClientLink(_DHCP6OptGuessPayload): # RFC5007 name = "DHCP6 Client Link Option" fields_desc = [ShortEnumField("optcode", 48, dhcp6opts), FieldLenField("optlen", None, length_of="linkaddress"), IP6ListField("linkaddress", [], length_from=lambda pkt: pkt.optlen)] class DHCP6OptBootFileUrl(_DHCP6OptGuessPayload): # RFC5970 name = "DHCP6 Boot File URL Option" fields_desc = [ShortEnumField("optcode", 59, dhcp6opts), FieldLenField("optlen", None, length_of="optdata"), StrLenField("optdata", "", length_from=lambda pkt: pkt.optlen)] class DHCP6OptClientArchType(_DHCP6OptGuessPayload): # RFC5970 name = "DHCP6 Client System Architecture Type Option" fields_desc = [ShortEnumField("optcode", 61, dhcp6opts), FieldLenField("optlen", None, length_of="archtypes", fmt="!H"), FieldListField("archtypes", [], ShortField("archtype", 0), length_from=lambda pkt: pkt.optlen)] class DHCP6OptClientNetworkInterId(_DHCP6OptGuessPayload): # RFC5970 name = "DHCP6 Client Network Interface Identifier Option" fields_desc = [ShortEnumField("optcode", 62, dhcp6opts), ShortField("optlen", 3), ByteField("iitype", 0), ByteField("iimajor", 0), ByteField("iiminor", 0)] class DHCP6OptERPDomain(_DHCP6OptGuessPayload): # RFC6440 name = "DHCP6 Option - ERP Domain Name List" fields_desc = [ShortEnumField("optcode", 65, dhcp6opts), FieldLenField("optlen", None, length_of="erpdomain"), DomainNameListField("erpdomain", [], length_from=lambda pkt: pkt.optlen)] class DHCP6OptRelaySuppliedOpt(_DHCP6OptGuessPayload): # RFC6422 name = "DHCP6 Relay-Supplied Options Option" fields_desc = [ShortEnumField("optcode", 66, dhcp6opts), FieldLenField("optlen", None, length_of="relaysupplied", fmt="!H"), PacketListField("relaysupplied", [], _DHCP6OptGuessPayload, length_from=lambda pkt: pkt.optlen)] # Virtual Subnet selection class DHCP6OptVSS(_DHCP6OptGuessPayload): # RFC6607 name = "DHCP6 Option - Virtual Subnet Selection" fields_desc = [ShortEnumField("optcode", 68, dhcp6opts), FieldLenField("optlen", None, length_of="data", adjust=lambda pkt, x: x + 1), ByteField("type", 255), # Default Global/default table StrLenField("data", "", length_from=lambda pkt: pkt.optlen)] # "Client link-layer address type. The link-layer type MUST be a valid hardware # noqa: E501 # type assigned by the IANA, as described in [RFC0826] class DHCP6OptClientLinkLayerAddr(_DHCP6OptGuessPayload): # RFC6939 name = "DHCP6 Option - Client Link Layer address" fields_desc = [ShortEnumField("optcode", 79, dhcp6opts), FieldLenField("optlen", None, length_of="clladdr", adjust=lambda pkt, x: x + 2), ShortField("lltype", 1), # ethernet _LLAddrField("clladdr", ETHER_ANY)] ##################################################################### # 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) ##################################################################### # 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] # 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), FieldLenField("optlen", None, fmt="!H", length_of="message"), PacketLenField("message", DHCP6(), _dhcp6_dispatcher, length_from=lambda p: p.optlen)] ##################################################################### # 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 # containing 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 client 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_Solicit, 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(_DHCP6OptGuessPayload, Packet): name = "DHCPv6 Relay Forward Message (Relay Agent/Server Message)" fields_desc = [ByteEnumField("msgtype", 12, dhcp6types), ByteField("hopcount", None), IP6Field("linkaddr", "::"), IP6Field("peeraddr", "::")] overload_fields = {UDP: {"sport": 547, "dport": 547}} 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 message 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) 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 = """ DHCPv6_am.parse_options( dns="2001:500::1035", domain="localdomain, local", duid=None, iface=conf.iface, advpref=255, sntpservers=None, sipdomains=None, sipservers=None, nisdomain=None, nisservers=None, nispdomain=None, nispservers=None, bcmcsdomains=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.iface. 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 isinstance(val, list): return val elif isinstance(val, str): tmp_len = val.split(',') return [x.strip() for x in tmp_len] else: print("Bad '%s' parameter provided." % param_name) self.usage() return -1 if iface is None: iface = conf.iface 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)), # noqa: E501 (sntpservers, "sntpservers", 31, lambda x: DHCP6OptSNTPServers(sntpservers=x)), # noqa: E501 (sipservers, "sipservers", 22, lambda x: DHCP6OptSIPServers(sipservers=x)), # noqa: E501 (sipdomains, "sipdomains", 21, lambda x: DHCP6OptSIPDomains(sipdomains=x)), # noqa: E501 (nisservers, "nisservers", 27, lambda x: DHCP6OptNISServers(nisservers=x)), # noqa: E501 (nisdomain, "nisdomain", 29, lambda x: DHCP6OptNISDomain(nisdomain=(x + [""])[0])), # noqa: E501 (nispservers, "nispservers", 28, lambda x: DHCP6OptNISPServers(nispservers=x)), # noqa: E501 (nispdomain, "nispdomain", 30, lambda x: DHCP6OptNISPDomain(nispdomain=(x + [""])[0])), # noqa: E501 (bcmcsservers, "bcmcsservers", 33, lambda x: DHCP6OptBCMCSServers(bcmcsservers=x)), # noqa: E501 (bcmcsdomains, "bcmcsdomains", 34, lambda x: DHCP6OptBCMCSDomains(bcmcsdomains=x))]: # noqa: E501 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 = sorted(self.dhcpv6_options) 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 epoch = (2000, 1, 1, 0, 0, 0, 5, 1, 0) delta = time.mktime(epoch) - EPOCH timeval = time.time() - delta # Mac Address rawmac = get_if_raw_hwaddr(iface)[1] mac = ":".join("%.02x" % orb(x) for x in 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 try: addr = next(x for x in in6_getifaddr() if x[2] == iface and in6_islladdr(x[0])) # noqa: E501 except (StopIteration, RuntimeError): warning("Unable to get a Link-Local address") return else: self.src_addr = addr[0] #### # Our leases self.leases = {} if self.debug: print("\n[+] Starting DHCPv6 service on %s:" % self.iface) def is_request(self, p): if IPv6 not in p: return False src = p[IPv6].src 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 ((DHCP6OptClientId not 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 ((DHCP6OptServerId not in p) or (DHCP6OptClientId not in p)): return False # provided server DUID must match ours duid = p[DHCP6OptServerId].duid if not isinstance(duid, type(self.duid)): return False if raw(duid) != raw(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: lst = [] if isinstance(it, DHCP6OptIA_NA): lst = it.ianaopts elif isinstance(it, DHCP6OptIA_TA): lst = it.iataopts addrs += [x.addr for x in lst if isinstance(x, DHCP6OptIAAddress)] # noqa: E501 it = it.payload addrs = [bo + x + n for x in addrs] if self.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 not isinstance(duid, type(self.duid)): return False if raw(duid) != raw(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)) # noqa: E501 def make_reply(self, req): p = req[IPv6] req_src = p.src p = p.payload.payload msgtype = p.msgtype trid = p.trid def _include_options(query, answer): """ Include options from the DHCPv6 query """ # See which options should be included reqopts = [] if query.haslayer(DHCP6OptOptReq): # add only asked ones reqopts = query[DHCP6OptOptReq].reqopts for o, opt in six.iteritems(self.dhcpv6_options): if o in reqopts: answer /= opt else: # advertise everything we have available # Should not happen has clients MUST include # and ORO in requests (sec 18.1.1) -- arno for o, opt in six.iteritems(self.dhcpv6_options): answer /= opt 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() _include_options(p, resp) 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) _include_options(p, resp) 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") # noqa: E501 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 for o, opt in six.iteritems(self.dhcpv6_options): resp /= opt return resp else: # what else ? pass # - We won't support reemission # - We won't support relay role, nor relay forwarded messages # at the beginning