## This file is part of Scapy
|
## See http://www.secdev.org/projects/scapy for more informations
|
## Copyright (C) Philippe Biondi <phil@secdev.org>
|
## This program is published under a GPLv2 license
|
|
## Copyright (C) 2005 Guillaume Valadon <guedou@hongo.wide.ad.jp>
|
## Arnaud Ebalard <arnaud.ebalard@eads.net>
|
|
"""
|
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 *
|
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
|
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
|
|
#############################################################################
|
# Helpers ##
|
#############################################################################
|
|
def get_cls(name, fallback_cls):
|
return globals().get(name, fallback_cls)
|
|
|
#############################################################################
|
#############################################################################
|
### DHCPv6 ###
|
#############################################################################
|
#############################################################################
|
|
All_DHCP_Relay_Agents_and_Servers = "ff02::1:2"
|
All_DHCP_Servers = "ff05::1:3" # Site-Local scope : deprecated by 3879
|
|
dhcp6opts = { 1: "CLIENTID",
|
2: "SERVERID",
|
3: "IA_NA",
|
4: "IA_TA",
|
5: "IAADDR",
|
6: "ORO",
|
7: "PREFERENCE",
|
8: "ELAPSED_TIME",
|
9: "RELAY_MSG",
|
11: "AUTH",
|
12: "UNICAST",
|
13: "STATUS_CODE",
|
14: "RAPID_COMMIT",
|
15: "USER_CLASS",
|
16: "VENDOR_CLASS",
|
17: "VENDOR_OPTS",
|
18: "INTERFACE_ID",
|
19: "RECONF_MSG",
|
20: "RECONF_ACCEPT",
|
21: "SIP Servers Domain Name List", #RFC3319
|
22: "SIP Servers IPv6 Address List", #RFC3319
|
23: "DNS Recursive Name Server Option", #RFC3646
|
24: "Domain Search List option", #RFC3646
|
25: "OPTION_IA_PD", #RFC3633
|
26: "OPTION_IAPREFIX", #RFC3633
|
27: "OPTION_NIS_SERVERS", #RFC3898
|
28: "OPTION_NISP_SERVERS", #RFC3898
|
29: "OPTION_NIS_DOMAIN_NAME", #RFC3898
|
30: "OPTION_NISP_DOMAIN_NAME", #RFC3898
|
31: "OPTION_SNTP_SERVERS", #RFC4075
|
32: "OPTION_INFORMATION_REFRESH_TIME", #RFC4242
|
33: "OPTION_BCMCS_SERVER_D", #RFC4280
|
34: "OPTION_BCMCS_SERVER_A", #RFC4280
|
36: "OPTION_GEOCONF_CIVIC", #RFC-ietf-geopriv-dhcp-civil-09.txt
|
37: "OPTION_REMOTE_ID", #RFC4649
|
38: "OPTION_SUBSCRIBER_ID", #RFC4580
|
39: "OPTION_CLIENT_FQDN", #RFC4704
|
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
|
37: "DHCP6OptRemoteID", #RFC4649
|
38: "DHCP6OptSubscriberID", #RFC4580
|
39: "DHCP6OptClientFQDN", #RFC4704
|
#40: "DHCP6OptPANAAgent", #RFC-ietf-dhc-paa-option-05.txt
|
#41: "DHCP6OptNewPOSIXTimeZone, #RFC4833
|
#42: "DHCP6OptNewTZDBTimeZone, #RFC4833
|
43: "DHCP6OptRelayAgentERO", #RFC4994
|
#44: "DHCP6OptLQQuery", #RFC5007
|
#45: "DHCP6OptLQClientData", #RFC5007
|
#46: "DHCP6OptLQClientTime", #RFC5007
|
#47: "DHCP6OptLQRelayData", #RFC5007
|
#48: "DHCP6OptLQClientLink", #RFC5007
|
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) ]
|
|
# In fact, IANA enterprise-numbers file available at
|
# http://www.iana.org/assignments/enterprise-numbers
|
# is simply huge (more than 2Mo and 600Ko in bz2). I'll
|
# add only most common vendors, and encountered values.
|
# -- arno
|
iana_enterprise_num = { 9: "ciscoSystems",
|
35: "Nortel Networks",
|
43: "3Com",
|
311: "Microsoft",
|
2636: "Juniper Networks, Inc.",
|
4526: "Netgear",
|
5771: "Cisco Systems, Inc.",
|
5842: "Cisco Systems",
|
16885: "Nortel Networks" }
|
|
class DUID_EN(Packet): # sect 9.3 RFC 3315
|
name = "DUID - Assigned by Vendor Based on Enterprise Number"
|
fields_desc = [ ShortEnumField("type", 2, duidtypes),
|
IntEnumField("enterprisenum", 311, iana_enterprise_num),
|
StrField("id","") ]
|
|
class DUID_LL(Packet): # sect 9.4 RFC 3315
|
name = "DUID - Based on Link-layer Address"
|
fields_desc = [ ShortEnumField("type", 3, duidtypes),
|
XShortEnumField("hwtype", 1, duidhwtypes),
|
_LLAddrField("lladdr", ETHER_ANY) ]
|
|
class DUID_UUID(Packet): # RFC 6355
|
name = "DUID - Based on UUID"
|
fields_desc = [ ShortEnumField("type", 4, duidtypes),
|
StrFixedLenField("uuid","", 16) ]
|
|
duid_cls = { 1: "DUID_LLT",
|
2: "DUID_EN",
|
3: "DUID_LL",
|
4: "DUID_UUID"}
|
|
#####################################################################
|
### DHCPv6 Options classes ###
|
#####################################################################
|
|
class _DHCP6OptGuessPayload(Packet):
|
def guess_payload_class(self, payload):
|
cls = conf.raw_layer
|
if len(payload) > 2 :
|
opt = struct.unpack("!H", payload[:2])[0]
|
cls = get_cls(dhcp6opts_by_code.get(opt, "DHCP6OptUnknown"), DHCP6OptUnknown)
|
return cls
|
|
class DHCP6OptUnknown(_DHCP6OptGuessPayload): # A generic DHCPv6 Option
|
name = "Unknown DHCPv6 Option"
|
fields_desc = [ ShortEnumField("optcode", 0, dhcp6opts),
|
FieldLenField("optlen", None, length_of="data", fmt="!H"),
|
StrLenField("data", "",
|
length_from = lambda pkt: pkt.optlen)]
|
|
class _DUIDField(PacketField):
|
__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):
|
l = self.length_from(pkt)
|
return s[l:], self.m2i(pkt,s[:l])
|
|
|
class DHCP6OptClientId(_DHCP6OptGuessPayload): # RFC sect 22.2
|
name = "DHCP6 Client Identifier Option"
|
fields_desc = [ ShortEnumField("optcode", 1, dhcp6opts),
|
FieldLenField("optlen", None, length_of="duid", fmt="!H"),
|
_DUIDField("duid", "",
|
length_from = lambda pkt: pkt.optlen) ]
|
|
|
class DHCP6OptServerId(DHCP6OptClientId): # RFC sect 22.3
|
name = "DHCP6 Server Identifier Option"
|
optcode = 2
|
|
# Should be encapsulated in the option field of IA_NA or IA_TA options
|
# Can only appear at that location.
|
# TODO : last field IAaddr-options is not defined in the reference document
|
class DHCP6OptIAAddress(_DHCP6OptGuessPayload): # RFC sect 22.6
|
name = "DHCP6 IA Address Option (IA_TA or IA_NA suboption)"
|
fields_desc = [ ShortEnumField("optcode", 5, dhcp6opts),
|
FieldLenField("optlen", None, length_of="iaaddropts",
|
fmt="!H", adjust = lambda pkt,x: x+24),
|
IP6Field("addr", "::"),
|
IntField("preflft", 0),
|
IntField("validlft", 0),
|
StrLenField("iaaddropts", "",
|
length_from = lambda pkt: pkt.optlen - 24) ]
|
def guess_payload_class(self, payload):
|
return conf.padding_layer
|
|
class _IANAOptField(PacketListField):
|
def i2len(self, pkt, z):
|
if z is None or z == []:
|
return 0
|
return sum(len(raw(x)) for x in z)
|
|
def getfield(self, pkt, s):
|
l = self.length_from(pkt)
|
lst = []
|
remain, payl = s[:l], s[l:]
|
while len(remain)>0:
|
p = self.m2i(pkt,remain)
|
if conf.padding_layer in p:
|
pad = p[conf.padding_layer]
|
remain = pad.load
|
del(pad.underlayer.payload)
|
else:
|
remain = ""
|
lst.append(p)
|
return payl,lst
|
|
class DHCP6OptIA_NA(_DHCP6OptGuessPayload): # RFC sect 22.4
|
name = "DHCP6 Identity Association for Non-temporary Addresses Option"
|
fields_desc = [ ShortEnumField("optcode", 3, dhcp6opts),
|
FieldLenField("optlen", None, length_of="ianaopts",
|
fmt="!H", adjust = lambda pkt,x: x+12),
|
XIntField("iaid", None),
|
IntField("T1", None),
|
IntField("T2", None),
|
_IANAOptField("ianaopts", [], DHCP6OptIAAddress,
|
length_from = lambda pkt: pkt.optlen-12) ]
|
|
class _IATAOptField(_IANAOptField):
|
pass
|
|
class DHCP6OptIA_TA(_DHCP6OptGuessPayload): # RFC sect 22.5
|
name = "DHCP6 Identity Association for Temporary Addresses Option"
|
fields_desc = [ ShortEnumField("optcode", 4, dhcp6opts),
|
FieldLenField("optlen", None, length_of="iataopts",
|
fmt="!H", adjust = lambda pkt,x: x+4),
|
XIntField("iaid", None),
|
_IATAOptField("iataopts", [], DHCP6OptIAAddress,
|
length_from = lambda pkt: pkt.optlen-4) ]
|
|
|
#### DHCPv6 Option Request Option ###################################
|
|
class _OptReqListField(StrLenField):
|
islist = 1
|
def i2h(self, pkt, x):
|
if x is None:
|
return []
|
return x
|
|
def i2len(self, pkt, x):
|
return 2*len(x)
|
|
def any2i(self, pkt, x):
|
return x
|
|
def i2repr(self, pkt, x):
|
s = []
|
for y in self.i2h(pkt, x):
|
if 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")
|
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"),
|
_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):
|
l = self.length_from(pkt)
|
lst = []
|
remain, payl = s[:l], s[l:]
|
while len(remain)>0:
|
p = self.m2i(pkt,remain)
|
if conf.padding_layer in p:
|
pad = p[conf.padding_layer]
|
remain = pad.load
|
del(pad.underlayer.payload)
|
else:
|
remain = ""
|
lst.append(p)
|
return payl,lst
|
|
|
class USER_CLASS_DATA(Packet):
|
name = "user class data"
|
fields_desc = [ FieldLenField("len", None, length_of="data"),
|
StrLenField("data", "",
|
length_from = lambda pkt: pkt.len) ]
|
def guess_payload_class(self, payload):
|
return conf.padding_layer
|
|
class DHCP6OptUserClass(_DHCP6OptGuessPayload):# RFC sect 22.15
|
name = "DHCP6 User Class Option"
|
fields_desc = [ ShortEnumField("optcode", 15, dhcp6opts),
|
FieldLenField("optlen", None, fmt="!H",
|
length_of="userclassdata"),
|
_UserClassDataField("userclassdata", [], USER_CLASS_DATA,
|
length_from = lambda pkt: pkt.optlen) ]
|
|
|
#### DHCPv6 Vendor Class Option #####################################
|
|
class _VendorClassDataField(_UserClassDataField):
|
pass
|
|
class VENDOR_CLASS_DATA(USER_CLASS_DATA):
|
name = "vendor class data"
|
|
class DHCP6OptVendorClass(_DHCP6OptGuessPayload):# RFC sect 22.16
|
name = "DHCP6 Vendor Class Option"
|
fields_desc = [ ShortEnumField("optcode", 16, dhcp6opts),
|
FieldLenField("optlen", None, length_of="vcdata", fmt="!H",
|
adjust = lambda pkt,x: x+4),
|
IntEnumField("enterprisenum",None , iana_enterprise_num ),
|
_VendorClassDataField("vcdata", [], VENDOR_CLASS_DATA,
|
length_from = lambda pkt: pkt.optlen-4) ]
|
|
#### DHCPv6 Vendor-Specific Information Option ######################
|
|
class VENDOR_SPECIFIC_OPTION(_DHCP6OptGuessPayload):
|
name = "vendor specific option data"
|
fields_desc = [ ShortField("optcode", None),
|
FieldLenField("optlen", None, length_of="optdata"),
|
StrLenField("optdata", "",
|
length_from = lambda pkt: pkt.optlen) ]
|
def guess_payload_class(self, payload):
|
return conf.padding_layer
|
|
# The third one that will be used for nothing interesting
|
class DHCP6OptVendorSpecificInfo(_DHCP6OptGuessPayload):# RFC sect 22.17
|
name = "DHCP6 Vendor-specific Information Option"
|
fields_desc = [ ShortEnumField("optcode", 17, dhcp6opts),
|
FieldLenField("optlen", None, length_of="vso", fmt="!H",
|
adjust = lambda pkt,x: x+4),
|
IntEnumField("enterprisenum",None , iana_enterprise_num),
|
_VendorClassDataField("vso", [], VENDOR_SPECIFIC_OPTION,
|
length_from = lambda pkt: pkt.optlen-4) ]
|
|
#### DHCPv6 Interface-ID Option #####################################
|
|
# Repasser sur cette option a la fin. Elle a pas l'air d'etre des
|
# masses critique.
|
class DHCP6OptIfaceId(_DHCP6OptGuessPayload):# RFC sect 22.18
|
name = "DHCP6 Interface-Id Option"
|
fields_desc = [ ShortEnumField("optcode", 18, dhcp6opts),
|
FieldLenField("optlen", None, fmt="!H",
|
length_of="ifaceid"),
|
StrLenField("ifaceid", "",
|
length_from = lambda pkt: pkt.optlen) ]
|
|
|
#### DHCPv6 Reconfigure Message Option ##############################
|
|
# A server includes a Reconfigure Message option in a Reconfigure
|
# message to indicate to the client whether the client responds with a
|
# renew message or an 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
|
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",
|
adjust = lambda pkt,x: x+12),
|
IntField("iaid", 0),
|
IntField("T1", 0),
|
IntField("T2", 0),
|
PacketListField("iapdopt", [], DHCP6OptIAPrefix,
|
length_from = lambda pkt: pkt.optlen-12) ]
|
|
class DHCP6OptNISServers(_DHCP6OptGuessPayload): #RFC3898
|
name = "DHCP6 Option - NIS Servers"
|
fields_desc = [ ShortEnumField("optcode", 27, dhcp6opts),
|
FieldLenField("optlen", None, length_of="nisservers"),
|
IP6ListField("nisservers", [],
|
length_from = lambda pkt: pkt.optlen) ]
|
|
class DHCP6OptNISPServers(_DHCP6OptGuessPayload): #RFC3898
|
name = "DHCP6 Option - NIS+ Servers"
|
fields_desc = [ ShortEnumField("optcode", 28, dhcp6opts),
|
FieldLenField("optlen", None, length_of="nispservers"),
|
IP6ListField("nispservers", [],
|
length_from = lambda pkt: pkt.optlen) ]
|
|
class DomainNameField(StrLenField):
|
def getfield(self, pkt, s):
|
l = self.length_from(pkt)
|
return s[l:], self.m2i(pkt,s[:l])
|
|
def i2len(self, pkt, x):
|
return len(self.i2m(pkt, x))
|
|
def m2i(self, pkt, x):
|
cur = []
|
while x:
|
l = orb(x[0])
|
cur.append(x[1:1+l])
|
x = x[l+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
|
name = ""
|
fields_desc = [ ShortEnumField("optcode", 36, dhcp6opts),
|
FieldLenField("optlen", None, length_of="optdata"),
|
StrLenField("optdata", "",
|
length_from = lambda pkt: pkt.optlen) ]
|
|
# TODO: see if we encounter opaque values from vendor devices
|
class DHCP6OptRemoteID(_DHCP6OptGuessPayload): #RFC4649
|
name = "DHCP6 Option - Relay Agent Remote-ID"
|
fields_desc = [ ShortEnumField("optcode", 37, dhcp6opts),
|
FieldLenField("optlen", None, length_of="remoteid",
|
adjust = lambda pkt,x: x+4),
|
IntEnumField("enterprisenum", None, iana_enterprise_num),
|
StrLenField("remoteid", "",
|
length_from = lambda pkt: pkt.optlen-4) ]
|
|
# TODO : 'subscriberid' default value should be at least 1 byte long
|
class DHCP6OptSubscriberID(_DHCP6OptGuessPayload): #RFC4580
|
name = "DHCP6 Option - Subscriber ID"
|
fields_desc = [ ShortEnumField("optcode", 38, dhcp6opts),
|
FieldLenField("optlen", None, length_of="subscriberid"),
|
StrLenField("subscriberid", "",
|
length_from = lambda pkt: pkt.optlen) ]
|
|
# TODO : "The data in the Domain Name field MUST be encoded
|
# as described in Section 8 of [5]"
|
class DHCP6OptClientFQDN(_DHCP6OptGuessPayload): #RFC4704
|
name = "DHCP6 Option - Client FQDN"
|
fields_desc = [ ShortEnumField("optcode", 39, dhcp6opts),
|
FieldLenField("optlen", None, length_of="fqdn",
|
adjust = lambda pkt,x: x+1),
|
BitField("res", 0, 5),
|
FlagsField("flags", 0, 3, "SON" ),
|
DomainNameField("fqdn", "",
|
length_from = lambda pkt: pkt.optlen-1) ]
|
|
class DHCP6OptRelayAgentERO(_DHCP6OptGuessPayload): # RFC4994
|
name = "DHCP6 Option - RelayRequest Option"
|
fields_desc = [ ShortEnumField("optcode", 43, dhcp6opts),
|
FieldLenField("optlen", None, length_of="reqopts", fmt="!H"),
|
_OptReqListField("reqopts", [23, 24],
|
length_from = lambda pkt: pkt.optlen) ]
|
|
# "Client link-layer address type. The link-layer type MUST be a valid hardware
|
# 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+1),
|
ShortField("lltype", 1), # ethernet
|
_LLAddrField("clladdr", ETHER_ANY) ]
|
|
# 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) ]
|
|
|
#####################################################################
|
### 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,
|
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_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 )
|
|
|
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)
|
|
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.iface6, 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.iface6.
|
|
advpref : Value in [0,255] given to Advertise preference field.
|
By default, 255 is used. Be aware that this specific
|
value makes clients stops waiting for further Advertise
|
messages from other servers.
|
|
dns : list of recursive DNS servers addresses (as a string or list).
|
By default, it is set empty and the associated DHCP6OptDNSServers
|
option is inactive. See RFC 3646 for details.
|
domain : a list of DNS search domain (as a string or list). By default,
|
it is empty and the associated DHCP6OptDomains option is inactive.
|
See RFC 3646 for details.
|
|
sntpservers : a list of SNTP servers IPv6 addresses. By default,
|
it is empty and the associated DHCP6OptSNTPServers option
|
is inactive.
|
|
sipdomains : a list of SIP domains. By default, it is empty and the
|
associated DHCP6OptSIPDomains option is inactive. See RFC 3319
|
for details.
|
sipservers : a list of SIP servers IPv6 addresses. By default, it is
|
empty and the associated DHCP6OptSIPDomains option is inactive.
|
See RFC 3319 for details.
|
|
nisdomain : a list of NIS domains. By default, it is empty and the
|
associated DHCP6OptNISDomains option is inactive. See RFC 3898
|
for details. See RFC 3646 for details.
|
nisservers : a list of NIS servers IPv6 addresses. By default, it is
|
empty and the associated DHCP6OptNISServers option is inactive.
|
See RFC 3646 for details.
|
|
nispdomain : a list of NIS+ domains. By default, it is empty and the
|
associated DHCP6OptNISPDomains option is inactive. See RFC 3898
|
for details.
|
nispservers : a list of NIS+ servers IPv6 addresses. By default, it is
|
empty and the associated DHCP6OptNISServers option is inactive.
|
See RFC 3898 for details.
|
|
bcmcsdomain : a list of BCMCS domains. By default, it is empty and the
|
associated DHCP6OptBCMCSDomains option is inactive. See RFC 4280
|
for details.
|
bcmcsservers : a list of BCMCS servers IPv6 addresses. By default, it is
|
empty and the associated DHCP6OptBCMCSServers option is inactive.
|
See RFC 4280 for details.
|
|
If you have a need for others, just ask ... or provide a patch."""
|
print(msg)
|
|
def parse_options(self, dns="2001:500::1035", domain="localdomain, local",
|
startip="2001:db8::1", endip="2001:db8::20", duid=None,
|
sntpservers=None, sipdomains=None, sipservers=None,
|
nisdomain=None, nisservers=None, nispdomain=None,
|
nispservers=None, bcmcsservers=None, bcmcsdomains=None,
|
iface=None, debug=0, advpref=255):
|
def norm_list(val, param_name):
|
if val is None:
|
return None
|
if isinstance(val, list):
|
return val
|
elif isinstance(val, str):
|
l = val.split(',')
|
return [x.strip() for x in l]
|
else:
|
print("Bad '%s' parameter provided." % param_name)
|
self.usage()
|
return -1
|
|
if iface is None:
|
iface = conf.iface6
|
|
self.debug = debug
|
|
# Dictionary of provided DHCPv6 options, keyed by option type
|
self.dhcpv6_options={}
|
|
for o in [(dns, "dns", 23, lambda x: DHCP6OptDNSServers(dnsservers=x)),
|
(domain, "domain", 24, lambda x: DHCP6OptDNSDomains(dnsdomains=x)),
|
(sntpservers, "sntpservers", 31, lambda x: DHCP6OptSNTPServers(sntpservers=x)),
|
(sipservers, "sipservers", 22, lambda x: DHCP6OptSIPServers(sipservers=x)),
|
(sipdomains, "sipdomains", 21, lambda x: DHCP6OptSIPDomains(sipdomains=x)),
|
(nisservers, "nisservers", 27, lambda x: DHCP6OptNISServers(nisservers=x)),
|
(nisdomain, "nisdomain", 29, lambda x: DHCP6OptNISDomain(nisdomain=(x+[""])[0])),
|
(nispservers, "nispservers", 28, lambda x: DHCP6OptNISPServers(nispservers=x)),
|
(nispdomain, "nispdomain", 30, lambda x: DHCP6OptNISPDomain(nispdomain=(x+[""])[0])),
|
(bcmcsservers, "bcmcsservers", 33, lambda x: DHCP6OptBCMCSServers(bcmcsservers=x)),
|
(bcmcsdomains, "bcmcsdomains", 34, lambda x: DHCP6OptBCMCSDomains(bcmcsdomains=x))]:
|
|
opt = norm_list(o[0], o[1])
|
if opt == -1: # Usage() was triggered
|
return False
|
elif opt is None: # We won't return that option
|
pass
|
else:
|
self.dhcpv6_options[o[2]] = o[3](opt)
|
|
if self.debug:
|
print("\n[+] List of active DHCPv6 options:")
|
opts = 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]))
|
except StopIteration:
|
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 not IPv6 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 ((not DHCP6OptClientId in p) or
|
DHCP6OptServerId in p):
|
return False
|
|
if (p.msgtype == 6 or # Rebind
|
p.msgtype == 4): # Confirm
|
# XXX We do not reply to Confirm or Rebind as we
|
# XXX do not support address assignment
|
return False
|
|
elif (p.msgtype == 3 or # Request
|
p.msgtype == 5 or # Renew
|
p.msgtype == 8): # Release
|
|
# Both options must be present
|
if ((not DHCP6OptServerId in p) or
|
(not DHCP6OptClientId in p)):
|
return False
|
# provided server DUID must match ours
|
duid = p[DHCP6OptServerId].duid
|
if 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:
|
l = []
|
if isinstance(it, DHCP6OptIA_NA):
|
l = it.ianaopts
|
elif isinstance(it, DHCP6OptIA_TA):
|
l = it.iataopts
|
|
addrs += [x.addr for x in l if isinstance(x, DHCP6OptIAAddress)]
|
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))
|
|
def make_reply(self, req):
|
p = req[IPv6]
|
req_src = p.src
|
|
p = p.payload.payload
|
|
msgtype = p.msgtype
|
trid = p.trid
|
|
if msgtype == 1: # SOLICIT (See Sect 17.1 and 17.2 of RFC 3315)
|
|
# XXX We don't support address or prefix assignment
|
# XXX We also do not support relay function --arno
|
|
client_duid = p[DHCP6OptClientId].duid
|
resp = IPv6(src=self.src_addr, dst=req_src)
|
resp /= UDP(sport=547, dport=546)
|
|
if p.haslayer(DHCP6OptRapidCommit):
|
# construct a Reply packet
|
resp /= DHCP6_Reply(trid=trid)
|
resp /= DHCP6OptRapidCommit() # See 17.1.2
|
resp /= DHCP6OptServerId(duid = self.duid)
|
resp /= DHCP6OptClientId(duid = client_duid)
|
|
else: # No Rapid Commit in the packet. Reply with an Advertise
|
|
if (p.haslayer(DHCP6OptIA_NA) or
|
p.haslayer(DHCP6OptIA_TA)):
|
# XXX We don't assign addresses at the moment
|
msg = "Scapy6 dhcp6d does not support address assignment"
|
resp /= DHCP6_Advertise(trid = trid)
|
resp /= DHCP6OptStatusCode(statuscode=2, statusmsg=msg)
|
resp /= DHCP6OptServerId(duid = self.duid)
|
resp /= DHCP6OptClientId(duid = client_duid)
|
|
elif p.haslayer(DHCP6OptIA_PD):
|
# XXX We don't assign prefixes at the moment
|
msg = "Scapy6 dhcp6d does not support prefix assignment"
|
resp /= DHCP6_Advertise(trid = trid)
|
resp /= DHCP6OptStatusCode(statuscode=6, statusmsg=msg)
|
resp /= DHCP6OptServerId(duid = self.duid)
|
resp /= DHCP6OptClientId(duid = client_duid)
|
|
else: # Usual case, no request for prefixes or addresse
|
resp /= DHCP6_Advertise(trid = trid)
|
resp /= DHCP6OptPref(prefval = self.advpref)
|
resp /= DHCP6OptServerId(duid = self.duid)
|
resp /= DHCP6OptClientId(duid = client_duid)
|
resp /= DHCP6OptReconfAccept()
|
|
# See which options should be included
|
reqopts = []
|
if p.haslayer(DHCP6OptOptReq): # add only asked ones
|
reqopts = p[DHCP6OptOptReq].reqopts
|
for o, opt in six.iteritems(self.dhcpv6_options):
|
if o in reqopts:
|
resp /= opt
|
else: # advertise everything we have available
|
for o, opt in six.iteritems(self.dhcpv6_options):
|
resp /= opt
|
|
return resp
|
|
elif msgtype == 3: #REQUEST (INFO-REQUEST is further below)
|
client_duid = p[DHCP6OptClientId].duid
|
resp = IPv6(src=self.src_addr, dst=req_src)
|
resp /= UDP(sport=547, dport=546)
|
resp /= DHCP6_Solicit(trid=trid)
|
resp /= DHCP6OptServerId(duid = self.duid)
|
resp /= DHCP6OptClientId(duid = client_duid)
|
|
# See which options should be included
|
reqopts = []
|
if p.haslayer(DHCP6OptOptReq): # add only asked ones
|
reqopts = p[DHCP6OptOptReq].reqopts
|
for o, opt in six.iteritems(self.dhcpv6_options):
|
if o in reqopts:
|
resp /= 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):
|
resp /= opt
|
|
return resp
|
|
elif msgtype == 4: # CONFIRM
|
# see Sect 18.1.2
|
|
# Client want to check if addresses it was assigned
|
# are still appropriate
|
|
# Server must discard any Confirm messages that
|
# do not include a Client Identifier option OR
|
# THAT DO INCLUDE a Server Identifier Option
|
|
# XXX we must discard the SOLICIT if it is received with
|
# a unicast destination address
|
|
pass
|
|
elif msgtype == 5: # RENEW
|
# see Sect 18.1.3
|
|
# Clients want to extend lifetime of assigned addresses
|
# and update configuration parameters. This message is sent
|
# specifically to the server that provided her the info
|
|
# - Received message must include a Server Identifier
|
# option.
|
# - the content of server identifier option must match
|
# the server's identifier.
|
# - the message must include a Client identifier option
|
|
pass
|
|
elif msgtype == 6: # REBIND
|
# see Sect 18.1.4
|
|
# Same purpose as the Renew message but sent to any
|
# available server after he received no response
|
# to its previous Renew message.
|
|
|
# - Message must include a Client Identifier Option
|
# - Message can't include a Server identifier option
|
|
# XXX we must discard the SOLICIT if it is received with
|
# a unicast destination address
|
|
pass
|
|
elif msgtype == 8: # RELEASE
|
# See section 18.1.6
|
|
# Message is sent to the server to indicate that
|
# she will no longer use the addresses that was assigned
|
# We should parse the message and verify our dictionary
|
# to log that fact.
|
|
|
# - The message must include a server identifier option
|
# - The content of the Server Identifier option must
|
# match the server's identifier
|
# - the message must include a Client Identifier option
|
|
pass
|
|
elif msgtype == 9: # DECLINE
|
# See section 18.1.7
|
pass
|
|
elif msgtype == 11: # INFO-REQUEST
|
client_duid = None
|
if not p.haslayer(DHCP6OptClientId):
|
if self.debug:
|
warning("Received Info Request message without Client Id option")
|
else:
|
client_duid = p[DHCP6OptClientId].duid
|
|
resp = IPv6(src=self.src_addr, dst=req_src)
|
resp /= UDP(sport=547, dport=546)
|
resp /= DHCP6_Reply(trid=trid)
|
resp /= DHCP6OptServerId(duid = self.duid)
|
|
if client_duid:
|
resp /= DHCP6OptClientId(duid = client_duid)
|
|
# Stack requested options if available
|
reqopts = []
|
if p.haslayer(DHCP6OptOptReq):
|
reqopts = p[DHCP6OptOptReq].reqopts
|
for o, 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
|