86890704fd
todo: add documentation & wireshark dissector
1859 lines
74 KiB
Python
Executable file
1859 lines
74 KiB
Python
Executable file
# This file is part of Scapy
|
|
# See http://www.secdev.org/projects/scapy for more information
|
|
# Copyright (C) Philippe Biondi <phil@secdev.org>
|
|
# This program is published under a GPLv2 license
|
|
|
|
# Copyright (C) 2005 Guillaume Valadon <guedou@hongo.wide.ad.jp>
|
|
# Arnaud Ebalard <arnaud.ebalard@eads.net>
|
|
|
|
"""
|
|
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
|