summaryrefslogtreecommitdiffhomepage
path: root/dhcpv6
diff options
context:
space:
mode:
authorChris Koch <chrisko@google.com>2023-02-18 23:55:40 -0800
committerChris K <c@chrisko.ch>2023-02-19 13:39:52 -0800
commit93dbaf95ae931da311e1671fd0f470f2aa5f6980 (patch)
tree9e316e50ebcece3b96f0c4261c63d32af8fa2c32 /dhcpv6
parentf51b4d4530334a45ccb40368ada7930d269ef44a (diff)
dhcpv6: proper DUID types
Signed-off-by: Chris Koch <chrisko@google.com>
Diffstat (limited to 'dhcpv6')
-rw-r--r--dhcpv6/dhcpv6_test.go27
-rw-r--r--dhcpv6/dhcpv6message.go13
-rw-r--r--dhcpv6/dhcpv6relay_test.go2
-rw-r--r--dhcpv6/duid.go343
-rw-r--r--dhcpv6/duid_test.go222
-rw-r--r--dhcpv6/iputils.go13
-rw-r--r--dhcpv6/iputils_test.go23
-rw-r--r--dhcpv6/modifiers.go4
-rw-r--r--dhcpv6/modifiers_test.go14
-rw-r--r--dhcpv6/option_clientid.go10
-rw-r--r--dhcpv6/option_clientid_test.go22
-rw-r--r--dhcpv6/option_serverid.go10
-rw-r--r--dhcpv6/option_serverid_test.go22
-rw-r--r--dhcpv6/prettyprint_test.go2
-rw-r--r--dhcpv6/ztpv6/parse_vendor_options.go6
-rw-r--r--dhcpv6/ztpv6/parse_vendor_options_test.go7
16 files changed, 417 insertions, 323 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 {