diff options
author | Chris Koch <chrisko@google.com> | 2023-02-18 23:55:40 -0800 |
---|---|---|
committer | Chris K <c@chrisko.ch> | 2023-02-19 13:39:52 -0800 |
commit | 93dbaf95ae931da311e1671fd0f470f2aa5f6980 (patch) | |
tree | 9e316e50ebcece3b96f0c4261c63d32af8fa2c32 | |
parent | f51b4d4530334a45ccb40368ada7930d269ef44a (diff) |
dhcpv6: proper DUID types
Signed-off-by: Chris Koch <chrisko@google.com>
-rw-r--r-- | dhcpv6/dhcpv6_test.go | 27 | ||||
-rw-r--r-- | dhcpv6/dhcpv6message.go | 13 | ||||
-rw-r--r-- | dhcpv6/dhcpv6relay_test.go | 2 | ||||
-rw-r--r-- | dhcpv6/duid.go | 343 | ||||
-rw-r--r-- | dhcpv6/duid_test.go | 222 | ||||
-rw-r--r-- | dhcpv6/iputils.go | 13 | ||||
-rw-r--r-- | dhcpv6/iputils_test.go | 23 | ||||
-rw-r--r-- | dhcpv6/modifiers.go | 4 | ||||
-rw-r--r-- | dhcpv6/modifiers_test.go | 14 | ||||
-rw-r--r-- | dhcpv6/option_clientid.go | 10 | ||||
-rw-r--r-- | dhcpv6/option_clientid_test.go | 22 | ||||
-rw-r--r-- | dhcpv6/option_serverid.go | 10 | ||||
-rw-r--r-- | dhcpv6/option_serverid_test.go | 22 | ||||
-rw-r--r-- | dhcpv6/prettyprint_test.go | 2 | ||||
-rw-r--r-- | dhcpv6/ztpv6/parse_vendor_options.go | 6 | ||||
-rw-r--r-- | dhcpv6/ztpv6/parse_vendor_options_test.go | 7 | ||||
-rw-r--r-- | examples/packetcrafting6/main.go | 5 |
17 files changed, 419 insertions, 326 deletions
diff --git a/dhcpv6/dhcpv6_test.go b/dhcpv6/dhcpv6_test.go index b1f952d..210f19d 100644 --- a/dhcpv6/dhcpv6_test.go +++ b/dhcpv6/dhcpv6_test.go @@ -165,9 +165,9 @@ func TestNewAdvertiseFromSolicit(t *testing.T) { MessageType: MessageTypeSolicit, TransactionID: TransactionID{0xa, 0xb, 0xc}, } - s.AddOption(OptClientID(Duid{})) + s.AddOption(OptClientID(&DUIDLLT{})) - a, err := NewAdvertiseFromSolicit(&s, WithServerID(Duid{})) + a, err := NewAdvertiseFromSolicit(&s, WithServerID(&DUIDLLT{})) require.NoError(t, err) require.Equal(t, a.TransactionID, s.TransactionID) require.Equal(t, a.Type(), MessageTypeAdvertise) @@ -178,35 +178,35 @@ func TestNewReplyFromMessage(t *testing.T) { TransactionID: TransactionID{0xa, 0xb, 0xc}, MessageType: MessageTypeConfirm, } - var duid Duid - msg.AddOption(OptClientID(duid)) - msg.AddOption(OptServerID(duid)) + var duid DUIDLLT + msg.AddOption(OptClientID(&duid)) + msg.AddOption(OptServerID(&duid)) - rep, err := NewReplyFromMessage(&msg, WithServerID(duid)) + rep, err := NewReplyFromMessage(&msg, WithServerID(&duid)) require.NoError(t, err) require.Equal(t, rep.TransactionID, msg.TransactionID) require.Equal(t, rep.Type(), MessageTypeReply) msg.MessageType = MessageTypeRenew - rep, err = NewReplyFromMessage(&msg, WithServerID(duid)) + rep, err = NewReplyFromMessage(&msg, WithServerID(&duid)) require.NoError(t, err) require.Equal(t, rep.TransactionID, msg.TransactionID) require.Equal(t, rep.Type(), MessageTypeReply) msg.MessageType = MessageTypeRebind - rep, err = NewReplyFromMessage(&msg, WithServerID(duid)) + rep, err = NewReplyFromMessage(&msg, WithServerID(&duid)) require.NoError(t, err) require.Equal(t, rep.TransactionID, msg.TransactionID) require.Equal(t, rep.Type(), MessageTypeReply) msg.MessageType = MessageTypeRelease - rep, err = NewReplyFromMessage(&msg, WithServerID(duid)) + rep, err = NewReplyFromMessage(&msg, WithServerID(&duid)) require.NoError(t, err) require.Equal(t, rep.TransactionID, msg.TransactionID) require.Equal(t, rep.Type(), MessageTypeReply) msg.MessageType = MessageTypeInformationRequest - rep, err = NewReplyFromMessage(&msg, WithServerID(duid)) + rep, err = NewReplyFromMessage(&msg, WithServerID(&duid)) require.NoError(t, err) require.Equal(t, rep.TransactionID, msg.TransactionID) require.Equal(t, rep.Type(), MessageTypeReply) @@ -226,9 +226,8 @@ func TestNewMessageTypeSolicit(t *testing.T) { hwAddr, err := net.ParseMAC("24:0A:9E:9F:EB:2B") require.NoError(t, err) - duid := Duid{ - Type: DUID_LL, - HwType: iana.HWTypeEthernet, + duid := &DUIDLL{ + HWType: iana.HWTypeEthernet, LinkLayerAddr: hwAddr, } @@ -239,7 +238,7 @@ func TestNewMessageTypeSolicit(t *testing.T) { // Check CID cduid := s.Options.ClientID() require.NotNil(t, cduid) - require.Equal(t, cduid, &duid) + require.Equal(t, cduid, duid) // Check ORO oro := s.Options.RequestedOptions() diff --git a/dhcpv6/dhcpv6message.go b/dhcpv6/dhcpv6message.go index 17f371a..8886c6c 100644 --- a/dhcpv6/dhcpv6message.go +++ b/dhcpv6/dhcpv6message.go @@ -32,21 +32,21 @@ func (mo MessageOptions) ArchTypes() iana.Archs { } // ClientID returns the client identifier option. -func (mo MessageOptions) ClientID() *Duid { +func (mo MessageOptions) ClientID() DUID { opt := mo.GetOne(OptionClientID) if opt == nil { return nil } - return &opt.(*optClientID).Duid + return opt.(*optClientID).DUID } // ServerID returns the server identifier option. -func (mo MessageOptions) ServerID() *Duid { +func (mo MessageOptions) ServerID() DUID { opt := mo.GetOne(OptionServerID) if opt == nil { return nil } - return &opt.(*optServerID).Duid + return opt.(*optServerID).DUID } // IANA returns all Identity Association for Non-temporary Address options. @@ -347,9 +347,8 @@ func GetTime() uint32 { // NewSolicit creates a new SOLICIT message, using the given hardware address to // derive the IAID in the IA_NA option. func NewSolicit(hwaddr net.HardwareAddr, modifiers ...Modifier) (*Message, error) { - duid := Duid{ - Type: DUID_LLT, - HwType: iana.HWTypeEthernet, + duid := &DUIDLLT{ + HWType: iana.HWTypeEthernet, Time: GetTime(), LinkLayerAddr: hwaddr, } diff --git a/dhcpv6/dhcpv6relay_test.go b/dhcpv6/dhcpv6relay_test.go index 113842c..1d38855 100644 --- a/dhcpv6/dhcpv6relay_test.go +++ b/dhcpv6/dhcpv6relay_test.go @@ -96,7 +96,7 @@ func TestNewRelayRepFromRelayForw(t *testing.T) { // create the inner message s, err := NewMessage() require.NoError(t, err) - s.AddOption(OptClientID(Duid{})) + s.AddOption(OptClientID(&DUIDLL{})) rf.AddOption(OptRelayMessage(s)) a, err := NewAdvertiseFromSolicit(s) diff --git a/dhcpv6/duid.go b/dhcpv6/duid.go index 2ae8e60..fcd785d 100644 --- a/dhcpv6/duid.go +++ b/dhcpv6/duid.go @@ -1,157 +1,242 @@ package dhcpv6 import ( - "bytes" - "encoding/binary" "fmt" "net" "github.com/insomniacslk/dhcp/iana" + "github.com/u-root/uio/uio" ) -// DuidType is the DUID type as defined in rfc3315. -type DuidType uint16 +// DUID is the interface that all DUIDs adhere to. +type DUID interface { + fmt.Stringer + + ToBytes() []byte + FromBytes(p []byte) error + DUIDType() DUIDType +} + +// DUIDLLT is a DUID based on link-layer address plus time (RFC 8415 Section 11.2). +type DUIDLLT struct { + HWType iana.HWType + Time uint32 + LinkLayerAddr net.HardwareAddr +} + +// String pretty-prints DUIDLLT information. +func (d DUIDLLT) String() string { + return fmt.Sprintf("DUID-LLT{HWType=%s HWAddr=%s Time=%d}", d.HWType, d.LinkLayerAddr, d.Time) +} + +// DUIDType returns the DUID_LLT type. +func (d DUIDLLT) DUIDType() DUIDType { + return DUID_LLT +} + +// ToBytes serializes the option out to bytes. +func (d DUIDLLT) ToBytes() []byte { + buf := uio.NewBigEndianBuffer(nil) + buf.Write16(uint16(d.DUIDType())) + buf.Write16(uint16(d.HWType)) + buf.Write32(d.Time) + buf.WriteBytes(d.LinkLayerAddr) + return buf.Data() +} + +// FromBytes reads the option. +func (d *DUIDLLT) FromBytes(p []byte) error { + buf := uio.NewBigEndianBuffer(p) + d.HWType = iana.HWType(buf.Read16()) + d.Time = buf.Read32() + d.LinkLayerAddr = buf.ReadAll() + return buf.FinError() +} + +// DUIDLL is a DUID based on link-layer (RFC 8415 Section 11.4). +type DUIDLL struct { + HWType iana.HWType + LinkLayerAddr net.HardwareAddr +} + +// String pretty-prints DUIDLL information. +func (d DUIDLL) String() string { + return fmt.Sprintf("DUID-LL{HWType=%s HWAddr=%s}", d.HWType, d.LinkLayerAddr) +} + +// DUIDType returns the DUID_LL type. +func (d DUIDLL) DUIDType() DUIDType { + return DUID_LL +} + +// ToBytes serializes the option out to bytes. +func (d DUIDLL) ToBytes() []byte { + buf := uio.NewBigEndianBuffer(nil) + buf.Write16(uint16(d.DUIDType())) + buf.Write16(uint16(d.HWType)) + buf.WriteBytes(d.LinkLayerAddr) + return buf.Data() +} + +// FromBytes reads the option. +func (d *DUIDLL) FromBytes(p []byte) error { + buf := uio.NewBigEndianBuffer(p) + d.HWType = iana.HWType(buf.Read16()) + d.LinkLayerAddr = buf.ReadAll() + return buf.FinError() +} + +// DUIDEN is a DUID based on enterprise number (RFC 8415 Section 11.3). +type DUIDEN struct { + EnterpriseNumber uint32 + EnterpriseIdentifier []byte +} + +// String pretty-prints DUIDEN information. +func (d DUIDEN) String() string { + return fmt.Sprintf("DUID-EN{EnterpriseNumber=%d EnterpriseIdentifier=%s}", d.EnterpriseNumber, d.EnterpriseIdentifier) +} + +// DUIDType returns the DUID_EN type. +func (d DUIDEN) DUIDType() DUIDType { + return DUID_EN +} + +// ToBytes serializes the option out to bytes. +func (d DUIDEN) ToBytes() []byte { + buf := uio.NewBigEndianBuffer(nil) + buf.Write16(uint16(d.DUIDType())) + buf.Write32(d.EnterpriseNumber) + buf.WriteBytes(d.EnterpriseIdentifier) + return buf.Data() +} + +// FromBytes reads the option. +func (d *DUIDEN) FromBytes(p []byte) error { + buf := uio.NewBigEndianBuffer(p) + d.EnterpriseNumber = buf.Read32() + d.EnterpriseIdentifier = buf.ReadAll() + return buf.FinError() +} + +// DUIDUUID is a DUID based on UUID (RFC 8415 Section 11.5). +type DUIDUUID struct { + // Defined by RFC 6355. + UUID [16]byte +} + +// String pretty-prints DUIDUUID information. +func (d DUIDUUID) String() string { + return fmt.Sprintf("DUID-UUID{%#x}", d.UUID[:]) +} + +// DUIDType returns the DUID_UUID type. +func (d DUIDUUID) DUIDType() DUIDType { + return DUID_UUID +} + +// ToBytes serializes the option out to bytes. +func (d DUIDUUID) ToBytes() []byte { + buf := uio.NewBigEndianBuffer(nil) + buf.Write16(uint16(d.DUIDType())) + buf.WriteData(d.UUID[:]) + return buf.Data() +} + +// FromBytes reads the option. +func (d *DUIDUUID) FromBytes(p []byte) error { + if len(p) != 16 { + return fmt.Errorf("buffer is length %d, DUID-UUID must be exactly 16 bytes", len(p)) + } + copy(d.UUID[:], p) + return nil +} + +// Equal returns true if e is a DUID-UUID with the same values as d. +func (d *DUIDUUID) Equal(e DUID) bool { + euuid, ok := e.(*DUIDUUID) + if !ok { + return false + } + return d.UUID == euuid.UUID +} + +// DUIDOpaque is a DUID of unknown type. +type DUIDOpaque struct { + Type DUIDType + Data []byte +} + +// String pretty-prints opaque DUID information. +func (d DUIDOpaque) String() string { + return fmt.Sprintf("DUID-Opaque{Type=%d Data=%#x}", d.Type, d.Data) +} + +// DUIDType returns the opaque DUID type. +func (d DUIDOpaque) DUIDType() DUIDType { + return d.Type +} + +// ToBytes serializes the option out to bytes. +func (d DUIDOpaque) ToBytes() []byte { + buf := uio.NewBigEndianBuffer(nil) + buf.Write16(uint16(d.Type)) + buf.WriteData(d.Data) + return buf.Data() +} + +// FromBytes reads the option. +func (d *DUIDOpaque) FromBytes(p []byte) error { + d.Data = append([]byte(nil), p...) + return nil +} + +// DUIDType is the DUID type as defined in RFC 3315. +type DUIDType uint16 // DUID types const ( - DUID_LLT DuidType = 1 - DUID_EN DuidType = 2 - DUID_LL DuidType = 3 - DUID_UUID DuidType = 4 + DUID_LLT DUIDType = 1 + DUID_EN DUIDType = 2 + DUID_LL DUIDType = 3 + DUID_UUID DUIDType = 4 ) -// DuidTypeToString maps a DuidType to a name. -var DuidTypeToString = map[DuidType]string{ +// duidTypeToString maps a DUIDType to a name. +var duidTypeToString = map[DUIDType]string{ DUID_LL: "DUID-LL", DUID_LLT: "DUID-LLT", DUID_EN: "DUID-EN", DUID_UUID: "DUID-UUID", } -func (d DuidType) String() string { - if dtype, ok := DuidTypeToString[d]; ok { +func (d DUIDType) String() string { + if dtype, ok := duidTypeToString[d]; ok { return dtype } - return "Unknown" -} - -// Duid is a DHCP Unique Identifier. -type Duid struct { - Type DuidType - HwType iana.HWType // for DUID-LLT and DUID-LL. Ignored otherwise. RFC 826 - Time uint32 // for DUID-LLT. Ignored otherwise - LinkLayerAddr net.HardwareAddr - EnterpriseNumber uint32 // for DUID-EN. Ignored otherwise - EnterpriseIdentifier []byte // for DUID-EN. Ignored otherwise - Uuid []byte // for DUID-UUID. Ignored otherwise - Opaque []byte // for unknown DUIDs -} - -// Length returns the DUID length in bytes. -func (d *Duid) Length() int { - if d.Type == DUID_LLT { - return 8 + len(d.LinkLayerAddr) - } else if d.Type == DUID_LL { - return 4 + len(d.LinkLayerAddr) - } else if d.Type == DUID_EN { - return 6 + len(d.EnterpriseIdentifier) - } else if d.Type == DUID_UUID { - return 18 - } else { - return 2 + len(d.Opaque) - } -} - -// Equal compares two Duid objects. -func (d Duid) Equal(o Duid) bool { - if d.Type != o.Type || - d.HwType != o.HwType || - d.Time != o.Time || - !bytes.Equal(d.LinkLayerAddr, o.LinkLayerAddr) || - d.EnterpriseNumber != o.EnterpriseNumber || - !bytes.Equal(d.EnterpriseIdentifier, o.EnterpriseIdentifier) || - !bytes.Equal(d.Uuid, o.Uuid) || - !bytes.Equal(d.Opaque, o.Opaque) { - return false - } - return true -} - -// ToBytes serializes a Duid object. -func (d *Duid) ToBytes() []byte { - if d.Type == DUID_LLT { - buf := make([]byte, 8) - binary.BigEndian.PutUint16(buf[0:2], uint16(d.Type)) - binary.BigEndian.PutUint16(buf[2:4], uint16(d.HwType)) - binary.BigEndian.PutUint32(buf[4:8], d.Time) - return append(buf, d.LinkLayerAddr...) - } else if d.Type == DUID_LL { - buf := make([]byte, 4) - binary.BigEndian.PutUint16(buf[0:2], uint16(d.Type)) - binary.BigEndian.PutUint16(buf[2:4], uint16(d.HwType)) - return append(buf, d.LinkLayerAddr...) - } else if d.Type == DUID_EN { - buf := make([]byte, 6) - binary.BigEndian.PutUint16(buf[0:2], uint16(d.Type)) - binary.BigEndian.PutUint32(buf[2:6], d.EnterpriseNumber) - return append(buf, d.EnterpriseIdentifier...) - } else if d.Type == DUID_UUID { - buf := make([]byte, 2) - binary.BigEndian.PutUint16(buf[0:2], uint16(d.Type)) - return append(buf, d.Uuid...) - } else { - buf := make([]byte, 2) - binary.BigEndian.PutUint16(buf[0:2], uint16(d.Type)) - return append(buf, d.Opaque...) - } + return "unknown" } -func (d *Duid) String() string { - var hwaddr string - if d.HwType == iana.HWTypeEthernet { - for _, b := range d.LinkLayerAddr { - hwaddr += fmt.Sprintf("%02x:", b) - } - if len(hwaddr) > 0 && hwaddr[len(hwaddr)-1] == ':' { - hwaddr = hwaddr[:len(hwaddr)-1] - } +// DUIDFromBytes parses a DUID from a byte slice. +func DUIDFromBytes(data []byte) (DUID, error) { + buf := uio.NewBigEndianBuffer(data) + if !buf.Has(2) { + return nil, fmt.Errorf("buffer too short: have %d bytes, want 2 bytes", buf.Len()) } - return fmt.Sprintf("DUID{type=%v hwtype=%v hwaddr=%v}", d.Type.String(), d.HwType.String(), hwaddr) -} -// DuidFromBytes parses a Duid from a byte slice. -func DuidFromBytes(data []byte) (*Duid, error) { - if len(data) < 2 { - return nil, fmt.Errorf("Invalid DUID: shorter than 2 bytes") - } - d := Duid{} - d.Type = DuidType(binary.BigEndian.Uint16(data[0:2])) - if d.Type == DUID_LLT { - if len(data) < 8 { - return nil, fmt.Errorf("Invalid DUID-LLT: shorter than 8 bytes") - } - d.HwType = iana.HWType(binary.BigEndian.Uint16(data[2:4])) - d.Time = binary.BigEndian.Uint32(data[4:8]) - d.LinkLayerAddr = data[8:] - } else if d.Type == DUID_LL { - if len(data) < 4 { - return nil, fmt.Errorf("Invalid DUID-LL: shorter than 4 bytes") - } - d.HwType = iana.HWType(binary.BigEndian.Uint16(data[2:4])) - d.LinkLayerAddr = data[4:] - } else if d.Type == DUID_EN { - if len(data) < 6 { - return nil, fmt.Errorf("Invalid DUID-EN: shorter than 6 bytes") - } - d.EnterpriseNumber = binary.BigEndian.Uint32(data[2:6]) - d.EnterpriseIdentifier = data[6:] - } else if d.Type == DUID_UUID { - if len(data) != 18 { - return nil, fmt.Errorf("Invalid DUID-UUID length. Expected 18, got %v", len(data)) - } - d.Uuid = data[2:18] - } else { - d.Opaque = data[2:] + typ := DUIDType(buf.Read16()) + var d DUID + switch typ { + case DUID_LLT: + d = &DUIDLLT{} + case DUID_LL: + d = &DUIDLL{} + case DUID_EN: + d = &DUIDEN{} + case DUID_UUID: + d = &DUIDUUID{} + default: + d = &DUIDOpaque{Type: typ} } - return &d, nil + return d, d.FromBytes(buf.Data()) } diff --git a/dhcpv6/duid_test.go b/dhcpv6/duid_test.go index 5efa0e1..d035474 100644 --- a/dhcpv6/duid_test.go +++ b/dhcpv6/duid_test.go @@ -3,6 +3,7 @@ package dhcpv6 import ( "bytes" "net" + "reflect" "testing" "github.com/insomniacslk/dhcp/iana" @@ -11,138 +12,127 @@ import ( func TestDuidInvalidTooShort(t *testing.T) { // too short DUID at all (must be at least 2 bytes) - _, err := DuidFromBytes([]byte{0}) + _, err := DUIDFromBytes([]byte{0}) require.Error(t, err) // too short DUID_LL (must be at least 4 bytes) - _, err = DuidFromBytes([]byte{0, 3, 0xa}) + _, err = DUIDFromBytes([]byte{0, 3, 0xa}) require.Error(t, err) // too short DUID_EN (must be at least 6 bytes) - _, err = DuidFromBytes([]byte{0, 2, 0xa, 0xb, 0xc}) + _, err = DUIDFromBytes([]byte{0, 2, 0xa, 0xb, 0xc}) require.Error(t, err) // too short DUID_LLT (must be at least 8 bytes) - _, err = DuidFromBytes([]byte{0, 1, 0xa, 0xb, 0xc, 0xd, 0xe}) + _, err = DUIDFromBytes([]byte{0, 1, 0xa, 0xb, 0xc, 0xd, 0xe}) require.Error(t, err) // too short DUID_UUID (must be at least 18 bytes) - _, err = DuidFromBytes([]byte{0, 4, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf}) + _, err = DUIDFromBytes([]byte{0, 4, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf}) require.Error(t, err) } -func TestDuidLLTFromBytes(t *testing.T) { - buf := []byte{ - 0, 1, // DUID_LLT - 0, 1, // HwTypeEthernet - 0x01, 0x02, 0x03, 0x04, // time - 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, // link-layer addr - } - duid, err := DuidFromBytes(buf) - require.NoError(t, err) - require.Equal(t, 14, duid.Length()) - require.Equal(t, DUID_LLT, duid.Type) - require.Equal(t, uint32(0x01020304), duid.Time) - require.Equal(t, iana.HWTypeEthernet, duid.HwType) - require.Equal(t, net.HardwareAddr{0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff}, duid.LinkLayerAddr) -} +func TestFromBytes(t *testing.T) { + for _, tt := range []struct { + name string + buf []byte + want DUID + stringer string + }{ + { + name: "DUID-LLT", + buf: []byte{ + 0, 1, // DUID_LLT + 0, 1, // HwTypeEthernet + 0x01, 0x02, 0x03, 0x04, // time + 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, // link-layer addr + }, + want: &DUIDLLT{ + Time: 0x01020304, + HWType: iana.HWTypeEthernet, + LinkLayerAddr: net.HardwareAddr{0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff}, + }, + stringer: "DUID-LLT{HWType=Ethernet HWAddr=aa:bb:cc:dd:ee:ff Time=16909060}", + }, + { + name: "DUID-LL", + buf: []byte{ + 0, 3, // DUID_LL + 0, 1, // HwTypeEthernet + 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, // link-layer addr + }, + want: &DUIDLL{ + HWType: iana.HWTypeEthernet, + LinkLayerAddr: net.HardwareAddr{0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff}, + }, + stringer: "DUID-LL{HWType=Ethernet HWAddr=aa:bb:cc:dd:ee:ff}", + }, + { + name: "DUID-EN", + buf: []byte{ + 0, 2, // DUID_EN + 0, 0, 0, 1, // EnterpriseNumber + 0x66, 0x6f, 0x6f, // "foo" + }, + want: &DUIDEN{ + EnterpriseNumber: 0x1, + EnterpriseIdentifier: []byte("foo"), + }, + stringer: "DUID-EN{EnterpriseNumber=1 EnterpriseIdentifier=foo}", + }, + { + name: "DUID-UUID", + buf: []byte{ + 0x00, 0x04, // DUID_UUID + 0x01, 0x02, 0x03, 0x04, // UUID + 0x01, 0x02, 0x03, 0x04, // UUID + 0x01, 0x02, 0x03, 0x04, // UUID + 0x01, 0x02, 0x03, 0x04, // UUID + }, + want: &DUIDUUID{ + UUID: [16]byte{ + 0x01, 0x02, 0x03, 0x04, + 0x01, 0x02, 0x03, 0x04, + 0x01, 0x02, 0x03, 0x04, + 0x01, 0x02, 0x03, 0x04, + }, + }, + stringer: "DUID-UUID{0x01020304010203040102030401020304}", + }, + { + name: "DUIDOpaque", + buf: []byte{ + 0x00, 0x05, // unknown DUID + 0x01, 0x02, 0x03, // Opaque + }, + want: &DUIDOpaque{ + Type: 0x5, + Data: []byte{0x01, 0x02, 0x03}, + }, + stringer: "DUID-Opaque{Type=5 Data=0x010203}", + }, + } { + t.Run(tt.name, func(t *testing.T) { + // FromBytes + got, err := DUIDFromBytes(tt.buf) + if err != nil { + t.Errorf("DUIDFromBytes = %v", err) + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("DUIDFromBytes = %v, want %v", got, tt.want) + } -func TestDuidLLFromBytes(t *testing.T) { - buf := []byte{ - 0, 3, // DUID_LL - 0, 1, // HwTypeEthernet - 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, // link-layer addr - } - duid, err := DuidFromBytes(buf) - require.NoError(t, err) - require.Equal(t, 10, duid.Length()) - require.Equal(t, DUID_LL, duid.Type) - require.Equal(t, iana.HWTypeEthernet, duid.HwType) - require.Equal(t, net.HardwareAddr{0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff}, duid.LinkLayerAddr) -} - -func TestDuidUuidFromBytes(t *testing.T) { - buf := []byte{ - 0x00, 0x04, // DUID_UUID - } - uuid := []byte{0x00, 0x01, 0x00, 0x02, 0x00, 0x03, 0x00, 0x04, 0x00, 0x05, 0x00, 0x06, 0x00, 0x07, 0x00, 0x08} - buf = append(buf, uuid...) - duid, err := DuidFromBytes(buf) - require.NoError(t, err) - require.Equal(t, 18, duid.Length()) - require.Equal(t, DUID_UUID, duid.Type) - require.Equal(t, uuid, duid.Uuid) -} + // ToBytes + buf := tt.want.ToBytes() + if !bytes.Equal(buf, tt.buf) { + t.Errorf("ToBytes() = %#x, want %#x", buf, tt.buf) + } -func TestDuidLLTToBytes(t *testing.T) { - expected := []byte{ - 0, 1, // DUID_LLT - 0, 1, // HwTypeEthernet - 0x01, 0x02, 0x03, 0x04, // time - 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, // link-layer addr - } - duid := Duid{ - Type: DUID_LLT, - HwType: iana.HWTypeEthernet, - Time: uint32(0x01020304), - LinkLayerAddr: []byte{0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff}, - } - toBytes := duid.ToBytes() - require.Equal(t, expected, toBytes) -} - -func TestDuidUuidToBytes(t *testing.T) { - uuid := []byte{0x00, 0x02, 0x00, 0x03, 0x00, 0x04, 0x00, 0x05, 0x00, 0x06, 0x00, 0x07, 0x00, 0x08, 0x00, 0x09} - expected := []byte{00, 04} - expected = append(expected, uuid...) - duid := Duid{ - Type: DUID_UUID, - Uuid: uuid, - } - toBytes := duid.ToBytes() - if !bytes.Equal(toBytes, expected) { - t.Fatalf("Invalid ToBytes result. Expected %v, got %v", expected, toBytes) - } -} - -func TestOpaqueDuid(t *testing.T) { - duid := []byte("\x00\x0a\x00\x03\x00\x01\x4c\x5e\x0c\x43\xbf\x39") - d, err := DuidFromBytes(duid) - if err != nil { - t.Fatalf("DuidFromBytes: unexpected error: %v", err) - } - if got, want := d.Length(), len(duid); got != want { - t.Errorf("Length: unexpected result: got %d, want %d", got, want) - } - if got, want := d.ToBytes(), duid; !bytes.Equal(got, want) { - t.Fatalf("ToBytes: unexpected result: got %x, want %x", got, want) - } -} - -func TestDuidEqual(t *testing.T) { - d := Duid{ - Type: DUID_LL, - HwType: iana.HWTypeEthernet, - LinkLayerAddr: net.HardwareAddr{0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff}, - } - o := Duid{ - Type: DUID_LL, - HwType: iana.HWTypeEthernet, - LinkLayerAddr: net.HardwareAddr{0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff}, - } - require.True(t, d.Equal(o)) -} - -func TestDuidEqualNotEqual(t *testing.T) { - d := Duid{ - Type: DUID_LL, - HwType: iana.HWTypeEthernet, - LinkLayerAddr: net.HardwareAddr{0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff}, - } - o := Duid{ - Type: DUID_LL, - HwType: iana.HWTypeEthernet, - LinkLayerAddr: net.HardwareAddr{0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0x00}, + // Stringer + s := tt.want.String() + if s != tt.stringer { + t.Errorf("String() = %s, want %s", s, tt.stringer) + } + }) } - require.False(t, d.Equal(o)) } diff --git a/dhcpv6/iputils.go b/dhcpv6/iputils.go index d2cac47..c4926e1 100644 --- a/dhcpv6/iputils.go +++ b/dhcpv6/iputils.go @@ -90,8 +90,15 @@ func ExtractMAC(packet DHCPv6) (net.HardwareAddr, error) { if duid == nil { return nil, fmt.Errorf("client ID not found in packet") } - if duid.LinkLayerAddr == nil { - return nil, fmt.Errorf("failed to extract MAC") + switch d := duid.(type) { + case *DUIDLL: + if d.LinkLayerAddr != nil { + return d.LinkLayerAddr, nil + } + case *DUIDLLT: + if d.LinkLayerAddr != nil { + return d.LinkLayerAddr, nil + } } - return duid.LinkLayerAddr, nil + return nil, fmt.Errorf("failed to extract MAC") } diff --git a/dhcpv6/iputils_test.go b/dhcpv6/iputils_test.go index e206aa1..601e96e 100644 --- a/dhcpv6/iputils_test.go +++ b/dhcpv6/iputils_test.go @@ -131,10 +131,9 @@ func Test_ExtractMAC(t *testing.T) { require.NoError(t, err) require.Equal(t, mac.String(), "24:8a:07:56:dc:a4") - // MAC extracted from DUID - duid := Duid{ - Type: DUID_LL, - HwType: iana.HWTypeEthernet, + // MAC extracted from DUID-LL + duid := &DUIDLL{ + HWType: iana.HWTypeEthernet, LinkLayerAddr: []byte{0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa}, } solicit, err := NewMessage(WithClientID(duid)) @@ -145,6 +144,18 @@ func Test_ExtractMAC(t *testing.T) { require.NoError(t, err) require.Equal(t, mac.String(), "aa:aa:aa:aa:aa:aa") + // MAC extracted from DUID-LLT + solicit, err = NewMessage(WithClientID(&DUIDLLT{ + HWType: iana.HWTypeEthernet, + LinkLayerAddr: []byte{0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa}, + })) + require.NoError(t, err) + relay, err = EncapsulateRelay(solicit, MessageTypeRelayForward, net.IPv6zero, net.IPv6zero) + require.NoError(t, err) + mac, err = ExtractMAC(relay) + require.NoError(t, err) + require.Equal(t, mac.String(), "aa:aa:aa:aa:aa:aa") + // no client ID solicit, err = NewMessage() require.NoError(t, err) @@ -152,8 +163,8 @@ func Test_ExtractMAC(t *testing.T) { require.Error(t, err) // DUID is not DuidLL or DuidLLT - duid = Duid{} - solicit, err = NewMessage(WithClientID(duid)) + duiden := &DUIDEN{} + solicit, err = NewMessage(WithClientID(duiden)) require.NoError(t, err) _, err = ExtractMAC(solicit) require.Error(t, err) diff --git a/dhcpv6/modifiers.go b/dhcpv6/modifiers.go index 1d8c49f..b0d22c5 100644 --- a/dhcpv6/modifiers.go +++ b/dhcpv6/modifiers.go @@ -16,12 +16,12 @@ func WithOption(o Option) Modifier { } // WithClientID adds a client ID option to a DHCPv6 packet -func WithClientID(duid Duid) Modifier { +func WithClientID(duid DUID) Modifier { return WithOption(OptClientID(duid)) } // WithServerID adds a client ID option to a DHCPv6 packet -func WithServerID(duid Duid) Modifier { +func WithServerID(duid DUID) Modifier { return WithOption(OptServerID(duid)) } diff --git a/dhcpv6/modifiers_test.go b/dhcpv6/modifiers_test.go index c0b8139..322d4e3 100644 --- a/dhcpv6/modifiers_test.go +++ b/dhcpv6/modifiers_test.go @@ -10,27 +10,25 @@ import ( ) func TestWithClientID(t *testing.T) { - duid := Duid{ - Type: DUID_LL, - HwType: iana.HWTypeEthernet, + duid := &DUIDLL{ + HWType: iana.HWTypeEthernet, LinkLayerAddr: net.HardwareAddr([]byte{0xfa, 0xce, 0xb0, 0x00, 0x00, 0x0c}), } m, err := NewMessage(WithClientID(duid)) require.NoError(t, err) cid := m.Options.ClientID() - require.Equal(t, cid, &duid) + require.Equal(t, cid, duid) } func TestWithServerID(t *testing.T) { - duid := Duid{ - Type: DUID_LL, - HwType: iana.HWTypeEthernet, + duid := &DUIDLL{ + HWType: iana.HWTypeEthernet, LinkLayerAddr: net.HardwareAddr([]byte{0xfa, 0xce, 0xb0, 0x00, 0x00, 0x0c}), } m, err := NewMessage(WithServerID(duid)) require.NoError(t, err) sid := m.Options.ServerID() - require.Equal(t, sid, &duid) + require.Equal(t, sid, duid) } func TestWithRequestedOptions(t *testing.T) { diff --git a/dhcpv6/option_clientid.go b/dhcpv6/option_clientid.go index d08f6d3..d07c4d5 100644 --- a/dhcpv6/option_clientid.go +++ b/dhcpv6/option_clientid.go @@ -6,12 +6,12 @@ import ( // OptClientID represents a Client Identifier option as defined by RFC 3315 // Section 22.2. -func OptClientID(d Duid) Option { +func OptClientID(d DUID) Option { return &optClientID{d} } type optClientID struct { - Duid + DUID } func (*optClientID) Code() OptionCode { @@ -19,16 +19,16 @@ func (*optClientID) Code() OptionCode { } func (op *optClientID) String() string { - return fmt.Sprintf("%s: %s", op.Code(), op.Duid.String()) + return fmt.Sprintf("%s: %s", op.Code(), op.DUID) } // parseOptClientID builds an OptClientId structure from a sequence // of bytes. The input data does not include option code and length // bytes. func parseOptClientID(data []byte) (*optClientID, error) { - cid, err := DuidFromBytes(data) + cid, err := DUIDFromBytes(data) if err != nil { return nil, err } - return &optClientID{*cid}, nil + return &optClientID{cid}, nil } diff --git a/dhcpv6/option_clientid_test.go b/dhcpv6/option_clientid_test.go index f60356e..0bac410 100644 --- a/dhcpv6/option_clientid_test.go +++ b/dhcpv6/option_clientid_test.go @@ -16,16 +16,19 @@ func TestParseOptClientID(t *testing.T) { } opt, err := parseOptClientID(data) require.NoError(t, err) - require.Equal(t, DUID_LL, opt.Type) - require.Equal(t, iana.HWTypeEthernet, opt.HwType) - require.Equal(t, net.HardwareAddr([]byte{0, 1, 2, 3, 4, 5}), opt.LinkLayerAddr) + want := OptClientID( + &DUIDLL{ + HWType: iana.HWTypeEthernet, + LinkLayerAddr: net.HardwareAddr([]byte{0, 1, 2, 3, 4, 5}), + }, + ) + require.Equal(t, want, opt) } func TestOptClientIdToBytes(t *testing.T) { opt := OptClientID( - Duid{ - Type: DUID_LL, - HwType: iana.HWTypeEthernet, + &DUIDLL{ + HWType: iana.HWTypeEthernet, LinkLayerAddr: net.HardwareAddr([]byte{5, 4, 3, 2, 1, 0}), }, ) @@ -50,9 +53,8 @@ func TestOptClientIdDecodeEncode(t *testing.T) { func TestOptionClientId(t *testing.T) { opt := OptClientID( - Duid{ - Type: DUID_LL, - HwType: iana.HWTypeEthernet, + &DUIDLL{ + HWType: iana.HWTypeEthernet, LinkLayerAddr: net.HardwareAddr([]byte{0xde, 0xad, 0, 0, 0xbe, 0xef}), }, ) @@ -60,7 +62,7 @@ func TestOptionClientId(t *testing.T) { require.Contains( t, opt.String(), - "Client ID: DUID{type=DUID-LL hwtype=Ethernet hwaddr=de:ad:00:00:be:ef}", + "Client ID: DUID-LL{HWType=Ethernet HWAddr=de:ad:00:00:be:ef}", "String() should contain the correct cid output", ) } diff --git a/dhcpv6/option_serverid.go b/dhcpv6/option_serverid.go index b461e8d..bdfc290 100644 --- a/dhcpv6/option_serverid.go +++ b/dhcpv6/option_serverid.go @@ -6,12 +6,12 @@ import ( // OptServerID represents a Server Identifier option as defined by RFC 3315 // Section 22.1. -func OptServerID(d Duid) Option { +func OptServerID(d DUID) Option { return &optServerID{d} } type optServerID struct { - Duid + DUID } func (*optServerID) Code() OptionCode { @@ -19,15 +19,15 @@ func (*optServerID) Code() OptionCode { } func (op *optServerID) String() string { - return fmt.Sprintf("%s: %v", op.Code(), op.Duid.String()) + return fmt.Sprintf("%s: %v", op.Code(), op.DUID) } // parseOptServerID builds an optServerID structure from a sequence of bytes. // The input data does not include option code and length bytes. func parseOptServerID(data []byte) (*optServerID, error) { - sid, err := DuidFromBytes(data) + sid, err := DUIDFromBytes(data) if err != nil { return nil, err } - return &optServerID{*sid}, nil + return &optServerID{sid}, nil } diff --git a/dhcpv6/option_serverid_test.go b/dhcpv6/option_serverid_test.go index 0e5cb2f..556f515 100644 --- a/dhcpv6/option_serverid_test.go +++ b/dhcpv6/option_serverid_test.go @@ -16,16 +16,19 @@ func TestParseOptServerID(t *testing.T) { } opt, err := parseOptServerID(data) require.NoError(t, err) - require.Equal(t, DUID_LL, opt.Type) - require.Equal(t, iana.HWTypeEthernet, opt.HwType) - require.Equal(t, net.HardwareAddr{0, 1, 2, 3, 4, 5}, opt.LinkLayerAddr) + want := OptServerID( + &DUIDLL{ + HWType: iana.HWTypeEthernet, + LinkLayerAddr: net.HardwareAddr{0, 1, 2, 3, 4, 5}, + }, + ) + require.Equal(t, opt, want) } func TestOptServerIdToBytes(t *testing.T) { opt := OptServerID( - Duid{ - Type: DUID_LL, - HwType: iana.HWTypeEthernet, + &DUIDLL{ + HWType: iana.HWTypeEthernet, LinkLayerAddr: net.HardwareAddr{5, 4, 3, 2, 1, 0}, }, ) @@ -50,9 +53,8 @@ func TestOptServerIdDecodeEncode(t *testing.T) { func TestOptionServerId(t *testing.T) { opt := OptServerID( - Duid{ - Type: DUID_LL, - HwType: iana.HWTypeEthernet, + &DUIDLL{ + HWType: iana.HWTypeEthernet, LinkLayerAddr: net.HardwareAddr{0xde, 0xad, 0, 0, 0xbe, 0xef}, }, ) @@ -60,7 +62,7 @@ func TestOptionServerId(t *testing.T) { require.Contains( t, opt.String(), - "Server ID: DUID{type=DUID-LL hwtype=Ethernet hwaddr=de:ad:00:00:be:ef}", + "Server ID: DUID-LL{HWType=Ethernet HWAddr=de:ad:00:00:be:ef}", "String() should contain the correct sid output", ) } diff --git a/dhcpv6/prettyprint_test.go b/dhcpv6/prettyprint_test.go index 7b839ef..def212c 100644 --- a/dhcpv6/prettyprint_test.go +++ b/dhcpv6/prettyprint_test.go @@ -73,7 +73,7 @@ func TestPrint(t *testing.T) { WithOption(OptRelayPort(1026)), WithOption(&OptRemoteID{EnterpriseNumber: 300, RemoteID: []byte{0xde, 0xad, 0xbe, 0xed}}), WithOption(OptRequestedOption(OptionBootfileURL, OptionBootfileParam)), - WithOption(OptServerID(Duid{Type: DUID_LL, HwType: iana.HWTypeEthernet, LinkLayerAddr: net.HardwareAddr{0x1, 0x2, 0x3, 0x4, 0x5, 0x6}})), + WithOption(OptServerID(&DUIDLL{HWType: iana.HWTypeEthernet, LinkLayerAddr: net.HardwareAddr{0x1, 0x2, 0x3, 0x4, 0x5, 0x6}})), WithOption(&OptUserClass{[][]byte{[]byte("foo"), []byte("bar")}}), WithOption(oneiana), WithOption(oneiata), diff --git a/dhcpv6/ztpv6/parse_vendor_options.go b/dhcpv6/ztpv6/parse_vendor_options.go index b3ce55b..f1508fd 100644 --- a/dhcpv6/ztpv6/parse_vendor_options.go +++ b/dhcpv6/ztpv6/parse_vendor_options.go @@ -81,10 +81,12 @@ func ParseVendorData(packet dhcpv6.DHCPv6) (*VendorData, error) { if len(v) < 3 { return nil, errVendorOptionMalformed } - duid := packet.(*dhcpv6.Message).Options.ClientID() vd.VendorName = iana.EnterpriseIDCienaCorporation.String() vd.Model = v[1] + "-" + v[2] - vd.Serial = string(duid.EnterpriseIdentifier) + duid := packet.(*dhcpv6.Message).Options.ClientID() + if enterpriseDUID, ok := duid.(*dhcpv6.DUIDEN); ok { + vd.Serial = string(enterpriseDUID.EnterpriseIdentifier) + } return &vd, nil } } diff --git a/dhcpv6/ztpv6/parse_vendor_options_test.go b/dhcpv6/ztpv6/parse_vendor_options_test.go index 03ff8cf..cfbfdde 100644 --- a/dhcpv6/ztpv6/parse_vendor_options_test.go +++ b/dhcpv6/ztpv6/parse_vendor_options_test.go @@ -64,7 +64,7 @@ func TestParseVendorDataWithVendorClass(t *testing.T) { tt := []struct { name string vc string - clientId *dhcpv6.Duid + clientId dhcpv6.DUID want *VendorData fail bool }{ @@ -84,8 +84,7 @@ func TestParseVendorDataWithVendorClass(t *testing.T) { { name: "Ciena", vc: "1271-23422Z11-123", - clientId: &dhcpv6.Duid{ - Type: dhcpv6.DUID_EN, + clientId: &dhcpv6.DUIDEN{ EnterpriseIdentifier: []byte("001234567"), }, want: &VendorData{VendorName: iana.EnterpriseIDCienaCorporation.String(), Model: "23422Z11-123", Serial: "001234567"}, @@ -102,7 +101,7 @@ func TestParseVendorDataWithVendorClass(t *testing.T) { packet.AddOption(&dhcpv6.OptVendorClass{ EnterpriseNumber: 0000, Data: [][]byte{[]byte(tc.vc)}}) if tc.clientId != nil { - packet.AddOption(dhcpv6.OptClientID(*tc.clientId)) + packet.AddOption(dhcpv6.OptClientID(tc.clientId)) } vd, err := ParseVendorData(packet) if err != nil && !tc.fail { diff --git a/examples/packetcrafting6/main.go b/examples/packetcrafting6/main.go index f2c1855..13e1c22 100644 --- a/examples/packetcrafting6/main.go +++ b/examples/packetcrafting6/main.go @@ -41,9 +41,8 @@ func main() { if err != nil { log.Fatal(err) } - duid := dhcpv6.Duid{ - Type: dhcpv6.DUID_LLT, - HwType: iana.HWTypeEthernet, + duid := &dhcpv6.DUIDLLT{ + HWType: iana.HWTypeEthernet, Time: dhcpv6.GetTime(), LinkLayerAddr: mac, } |