blob: fa58ccc899138633c40560a112811f9d6062c5db [file] [log] [blame]
## 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