diff options
Diffstat (limited to 'dhcpv6')
-rw-r--r-- | dhcpv6/dhcpv6.go | 58 | ||||
-rw-r--r-- | dhcpv6/dhcpv6_test.go | 47 | ||||
-rw-r--r-- | dhcpv6/dhcpv6message.go | 27 | ||||
-rw-r--r-- | dhcpv6/dhcpv6message_test.go | 34 | ||||
-rw-r--r-- | dhcpv6/modifiers.go | 6 | ||||
-rw-r--r-- | dhcpv6/option_archtype.go | 62 | ||||
-rw-r--r-- | dhcpv6/option_archtype_test.go | 5 | ||||
-rw-r--r-- | dhcpv6/utils.go | 77 | ||||
-rw-r--r-- | dhcpv6/utils_test.go | 69 |
9 files changed, 194 insertions, 191 deletions
diff --git a/dhcpv6/dhcpv6.go b/dhcpv6/dhcpv6.go index 0dabca5..9382334 100644 --- a/dhcpv6/dhcpv6.go +++ b/dhcpv6/dhcpv6.go @@ -1,8 +1,12 @@ package dhcpv6 import ( + "errors" "fmt" "net" + "strings" + + "github.com/insomniacslk/dhcp/iana" ) type DHCPv6 interface { @@ -199,3 +203,57 @@ func EncapsulateRelay(d DHCPv6, mType MessageType, linkAddr, peerAddr net.IP) (D outer.AddOption(&orm) return &outer, nil } + +// IsUsingUEFI function takes a DHCPv6 message and returns true if +// the machine trying to netboot is using UEFI of false if it is not. +func IsUsingUEFI(msg DHCPv6) bool { + // RFC 4578 says: + // As of the writing of this document, the following pre-boot + // architecture types have been requested. + // Type Architecture Name + // ---- ----------------- + // 0 Intel x86PC + // 1 NEC/PC98 + // 2 EFI Itanium + // 3 DEC Alpha + // 4 Arc x86 + // 5 Intel Lean Client + // 6 EFI IA32 + // 7 EFI BC + // 8 EFI Xscale + // 9 EFI x86-64 + if opt := msg.GetOneOption(OptionClientArchType); opt != nil { + optat := opt.(*OptClientArchType) + for _, at := range optat.ArchTypes { + // TODO investigate if other types are appropriate + if at == iana.EFI_BC || at == iana.EFI_X86_64 { + return true + } + } + } + if opt := msg.GetOneOption(OptionUserClass); opt != nil { + optuc := opt.(*OptUserClass) + for _, uc := range optuc.UserClasses { + if strings.Contains(string(uc), "EFI") { + return true + } + } + } + return false +} + +// GetTransactionID returns a transactionID of a message or its inner message +// in case of relay +func GetTransactionID(packet DHCPv6) (uint32, error) { + if message, ok := packet.(*DHCPv6Message); ok { + return message.TransactionID(), nil + } + if relay, ok := packet.(*DHCPv6Relay); ok { + message, err := relay.GetInnerMessage() + if err != nil { + return 0, err + } + return GetTransactionID(message) + } + return 0, errors.New("Invalid DHCPv6 packet") +} diff --git a/dhcpv6/dhcpv6_test.go b/dhcpv6/dhcpv6_test.go index 4a44e0e..6840252 100644 --- a/dhcpv6/dhcpv6_test.go +++ b/dhcpv6/dhcpv6_test.go @@ -215,5 +215,52 @@ func TestNewMessageTypeSolicitWithCID(t *testing.T) { require.Equal(t, len(opts), 2) } + +func TestIsUsingUEFIArchTypeTrue(t *testing.T) { + msg := DHCPv6Message{} + opt := OptClientArchType{ArchTypes: []iana.ArchType{iana.EFI_BC}} + msg.AddOption(&opt) + require.True(t, IsUsingUEFI(&msg)) +} + +func TestIsUsingUEFIArchTypeFalse(t *testing.T) { + msg := DHCPv6Message{} + opt := OptClientArchType{ArchTypes: []iana.ArchType{iana.INTEL_X86PC}} + msg.AddOption(&opt) + require.False(t, IsUsingUEFI(&msg)) +} + +func TestIsUsingUEFIUserClassTrue(t *testing.T) { + msg := DHCPv6Message{} + opt := OptUserClass{UserClasses: [][]byte{[]byte("ipxeUEFI")}} + msg.AddOption(&opt) + require.True(t, IsUsingUEFI(&msg)) +} + +func TestIsUsingUEFIUserClassFalse(t *testing.T) { + msg := DHCPv6Message{} + opt := OptUserClass{UserClasses: [][]byte{[]byte("ipxeLegacy")}} + msg.AddOption(&opt) + require.False(t, IsUsingUEFI(&msg)) +} + +func TestGetTransactionIDMessage(t *testing.T) { + message, err := NewMessage() + require.NoError(t, err) + transactionID, err := GetTransactionID(message) + require.NoError(t, err) + require.Equal(t, transactionID, message.(*DHCPv6Message).TransactionID()) +} + +func TestGetTransactionIDRelay(t *testing.T) { + message, err := NewMessage() + require.NoError(t, err) + relay, err := EncapsulateRelay(message, MessageTypeRelayForward, nil, nil) + require.NoError(t, err) + transactionID, err := GetTransactionID(relay) + require.NoError(t, err) + require.Equal(t, transactionID, message.(*DHCPv6Message).TransactionID()) +} + // TODO test NewMessageTypeSolicit // test String and Summary diff --git a/dhcpv6/dhcpv6message.go b/dhcpv6/dhcpv6message.go index e601932..7ee00ad 100644 --- a/dhcpv6/dhcpv6message.go +++ b/dhcpv6/dhcpv6message.go @@ -286,6 +286,33 @@ func (d *DHCPv6Message) UpdateOption(option Option) { d.AddOption(option) } +// IsNetboot returns true if the machine is trying to netboot. It checks if +// "boot file" is one of the requested options, which is useful for +// SOLICIT/REQUEST packet types, it also checks if the "boot file" option is +// included in the packet, which is useful for ADVERTISE/REPLY packet. +func (d *DHCPv6Message) IsNetboot() bool { + if d.IsOptionRequested(OptionBootfileURL) { + return true + } + if optbf := d.GetOneOption(OptionBootfileURL); optbf != nil { + return true + } + return false +} + +// IsOptionRequested takes an OptionCode and returns true if that option is +// within the requested options of the DHCPv6 message. +func (d *DHCPv6Message) IsOptionRequested(requested OptionCode) bool { + for _, optoro := range d.GetOption(OptionORO) { + for _, o := range optoro.(*OptRequestedOption).RequestedOptions() { + if o == requested { + return true + } + } + } + return false +} + func (d *DHCPv6Message) String() string { return fmt.Sprintf("DHCPv6Message(messageType=%v transactionID=0x%06x, %d options)", d.MessageTypeToString(), d.TransactionID(), len(d.options), diff --git a/dhcpv6/dhcpv6message_test.go b/dhcpv6/dhcpv6message_test.go new file mode 100644 index 0000000..5c92a7b --- /dev/null +++ b/dhcpv6/dhcpv6message_test.go @@ -0,0 +1,34 @@ +package dhcpv6 + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestIsNetboot(t *testing.T) { + msg1 := DHCPv6Message{} + require.False(t, msg1.IsNetboot()) + + msg2 := DHCPv6Message{} + optro := OptRequestedOption{} + optro.AddRequestedOption(OptionBootfileURL) + msg2.AddOption(&optro) + require.True(t, msg2.IsNetboot()) + + msg3 := DHCPv6Message{} + optbf := OptBootFileURL{} + msg3.AddOption(&optbf) + require.True(t, msg3.IsNetboot()) +} + +func TestIsOptionRequested(t *testing.T) { + msg1 := DHCPv6Message{} + require.False(t, msg1.IsOptionRequested(OptionDNSRecursiveNameServer)) + + msg2 := DHCPv6Message{} + optro := OptRequestedOption{} + optro.AddRequestedOption(OptionDNSRecursiveNameServer) + msg2.AddOption(&optro) + require.True(t, msg2.IsOptionRequested(OptionDNSRecursiveNameServer)) +} diff --git a/dhcpv6/modifiers.go b/dhcpv6/modifiers.go index 32d11ac..6cd66db 100644 --- a/dhcpv6/modifiers.go +++ b/dhcpv6/modifiers.go @@ -2,6 +2,8 @@ package dhcpv6 import ( "log" + + "github.com/insomniacslk/dhcp/iana" ) // WithClientID adds a client ID option to a DHCPv6 packet @@ -53,9 +55,9 @@ func WithUserClass(uc []byte) Modifier { } // WithArchType adds an arch type option to the packet -func WithArchType(at ArchType) Modifier { +func WithArchType(at iana.ArchType) Modifier { return func(d DHCPv6) DHCPv6 { - ao := OptClientArchType{ArchType: at} + ao := OptClientArchType{ArchTypes: []iana.ArchType{at}} d.AddOption(&ao) return d } diff --git a/dhcpv6/option_archtype.go b/dhcpv6/option_archtype.go index a1b4a9b..231eddd 100644 --- a/dhcpv6/option_archtype.go +++ b/dhcpv6/option_archtype.go @@ -6,42 +6,14 @@ package dhcpv6 import ( "encoding/binary" "fmt" -) - -//ArchType encodes an architecture type in an uint16 -type ArchType uint16 + "strings" -// see rfc4578 -const ( - INTEL_X86PC ArchType = 0 - NEC_PC98 ArchType = 1 - EFI_ITANIUM ArchType = 2 - DEC_ALPHA ArchType = 3 - ARC_X86 ArchType = 4 - INTEL_LEAN_CLIENT ArchType = 5 - EFI_IA32 ArchType = 6 - EFI_BC ArchType = 7 - EFI_XSCALE ArchType = 8 - EFI_X86_64 ArchType = 9 + "github.com/insomniacslk/dhcp/iana" ) -// ArchTypeToStringMap maps an ArchType to a mnemonic name -var ArchTypeToStringMap = map[ArchType]string{ - INTEL_X86PC: "Intel x86PC", - NEC_PC98: "NEC/PC98", - EFI_ITANIUM: "EFI Itanium", - DEC_ALPHA: "DEC Alpha", - ARC_X86: "Arc x86", - INTEL_LEAN_CLIENT: "Intel Lean Client", - EFI_IA32: "EFI IA32", - EFI_BC: "EFI BC", - EFI_XSCALE: "EFI Xscale", - EFI_X86_64: "EFI x86-64", -} - // OptClientArchType represents an option CLIENT_ARCH_TYPE type OptClientArchType struct { - ArchType ArchType + ArchTypes []iana.ArchType } func (op *OptClientArchType) Code() OptionCode { @@ -49,23 +21,28 @@ func (op *OptClientArchType) Code() OptionCode { } func (op *OptClientArchType) ToBytes() []byte { - buf := make([]byte, 6) + buf := make([]byte, 4) binary.BigEndian.PutUint16(buf[0:2], uint16(OptionClientArchType)) binary.BigEndian.PutUint16(buf[2:4], uint16(op.Length())) - binary.BigEndian.PutUint16(buf[4:6], uint16(op.ArchType)) + u16 := make([]byte, 2) + for _, at := range op.ArchTypes { + binary.BigEndian.PutUint16(u16, uint16(at)) + buf = append(buf, u16...) + } return buf } func (op *OptClientArchType) Length() int { - return 2 + return 2*len(op.ArchTypes) } func (op *OptClientArchType) String() string { - name, ok := ArchTypeToStringMap[op.ArchType] - if !ok { - name = "Unknown" + atStrings := make([]string, 0) + for _, at := range op.ArchTypes { + name := iana.ArchTypeToString(at) + atStrings = append(atStrings, name) } - return fmt.Sprintf("OptClientArchType{archtype=%v}", name) + return fmt.Sprintf("OptClientArchType{archtype=%v}", strings.Join(atStrings, ", ")) } // ParseOptClientArchType builds an OptClientArchType structure from @@ -73,9 +50,12 @@ func (op *OptClientArchType) String() string { // length bytes. func ParseOptClientArchType(data []byte) (*OptClientArchType, error) { opt := OptClientArchType{} - if len(data) != 2 { - return nil, fmt.Errorf("Invalid arch type data length. Expected 2 bytes, got %v", len(data)) + if len(data) == 0 || len(data)%2 != 0 { + return nil, fmt.Errorf("Invalid arch type data length. Expected multiple of 2 larger than 2, got %v", len(data)) + } + for idx := 0; idx < len(data); idx += 2 { + b := data[idx : idx+2] + opt.ArchTypes = append(opt.ArchTypes, iana.ArchType(binary.BigEndian.Uint16(b))) } - opt.ArchType = ArchType(binary.BigEndian.Uint16(data)) return &opt, nil } diff --git a/dhcpv6/option_archtype_test.go b/dhcpv6/option_archtype_test.go index 748c8c5..1848e55 100644 --- a/dhcpv6/option_archtype_test.go +++ b/dhcpv6/option_archtype_test.go @@ -3,6 +3,7 @@ package dhcpv6 import ( "testing" + "github.com/insomniacslk/dhcp/iana" "github.com/stretchr/testify/require" ) @@ -12,7 +13,7 @@ func TestParseOptClientArchType(t *testing.T) { } opt, err := ParseOptClientArchType(data) require.NoError(t, err) - require.Equal(t, opt.ArchType, EFI_IA32) + require.Equal(t, opt.ArchTypes[0], iana.EFI_IA32) } func TestParseOptClientArchTypeInvalid(t *testing.T) { @@ -37,7 +38,7 @@ func TestOptClientArchTypeParseAndToBytes(t *testing.T) { func TestOptClientArchType(t *testing.T) { opt := OptClientArchType{ - ArchType: EFI_ITANIUM, + ArchTypes: []iana.ArchType{iana.EFI_ITANIUM}, } require.Equal(t, opt.Length(), 2) require.Equal(t, opt.Code(), OptionClientArchType) diff --git a/dhcpv6/utils.go b/dhcpv6/utils.go deleted file mode 100644 index 1681661..0000000 --- a/dhcpv6/utils.go +++ /dev/null @@ -1,77 +0,0 @@ -package dhcpv6 - -import ( - "errors" - "strings" -) - -// IsNetboot function takes a DHCPv6 message and returns true if the machine -// is trying to netboot. It checks if "boot file" is one of the requested -// options, which is useful for SOLICIT/REQUEST packet types, it also checks -// if the "boot file" option is included in the packet, which is useful for -// ADVERTISE/REPLY packet. -func IsNetboot(msg DHCPv6) bool { - for _, optoro := range msg.GetOption(OptionORO) { - for _, o := range optoro.(*OptRequestedOption).RequestedOptions() { - if o == OptionBootfileURL { - return true - } - } - } - if optbf := msg.GetOneOption(OptionBootfileURL); optbf != nil { - return true - } - return false -} - -// IsUsingUEFI function takes a DHCPv6 message and returns true if -// the machine trying to netboot is using UEFI of false if it is not. -func IsUsingUEFI(msg DHCPv6) bool { - // RFC 4578 says: - // As of the writing of this document, the following pre-boot - // architecture types have been requested. - // Type Architecture Name - // ---- ----------------- - // 0 Intel x86PC - // 1 NEC/PC98 - // 2 EFI Itanium - // 3 DEC Alpha - // 4 Arc x86 - // 5 Intel Lean Client - // 6 EFI IA32 - // 7 EFI BC - // 8 EFI Xscale - // 9 EFI x86-64 - if opt := msg.GetOneOption(OptionClientArchType); opt != nil { - optat := opt.(*OptClientArchType) - // TODO investigate if other types are appropriate - if optat.ArchType == EFI_BC || optat.ArchType == EFI_X86_64 { - return true - } - } - if opt := msg.GetOneOption(OptionUserClass); opt != nil { - optuc := opt.(*OptUserClass) - for _, uc := range optuc.UserClasses { - if strings.Contains(string(uc), "EFI") { - return true - } - } - } - return false -} - -// GetTransactionID returns a transactionID of a message or its inner message -// in case of relay -func GetTransactionID(packet DHCPv6) (uint32, error) { - if message, ok := packet.(*DHCPv6Message); ok { - return message.TransactionID(), nil - } - if relay, ok := packet.(*DHCPv6Relay); ok { - message, err := relay.GetInnerMessage() - if err != nil { - return 0, err - } - return GetTransactionID(message) - } - return 0, errors.New("Invalid DHCPv6 packet") -} diff --git a/dhcpv6/utils_test.go b/dhcpv6/utils_test.go deleted file mode 100644 index f3b53f0..0000000 --- a/dhcpv6/utils_test.go +++ /dev/null @@ -1,69 +0,0 @@ -package dhcpv6 - -import ( - "testing" - - "github.com/stretchr/testify/require" -) - -func TestIsNetboot(t *testing.T) { - msg1 := DHCPv6Message{} - require.False(t, IsNetboot(&msg1)) - - msg2 := DHCPv6Message{} - optro := OptRequestedOption{} - optro.AddRequestedOption(OptionBootfileURL) - msg2.AddOption(&optro) - require.True(t, IsNetboot(&msg2)) - - msg3 := DHCPv6Message{} - optbf := OptBootFileURL{} - msg3.AddOption(&optbf) - require.True(t, IsNetboot(&msg3)) -} - -func TestIsUsingUEFIArchTypeTrue(t *testing.T) { - msg := DHCPv6Message{} - opt := OptClientArchType{ArchType: EFI_BC} - msg.AddOption(&opt) - require.True(t, IsUsingUEFI(&msg)) -} - -func TestIsUsingUEFIArchTypeFalse(t *testing.T) { - msg := DHCPv6Message{} - opt := OptClientArchType{ArchType: INTEL_X86PC} - msg.AddOption(&opt) - require.False(t, IsUsingUEFI(&msg)) -} - -func TestIsUsingUEFIUserClassTrue(t *testing.T) { - msg := DHCPv6Message{} - opt := OptUserClass{UserClasses: [][]byte{[]byte("ipxeUEFI")}} - msg.AddOption(&opt) - require.True(t, IsUsingUEFI(&msg)) -} - -func TestIsUsingUEFIUserClassFalse(t *testing.T) { - msg := DHCPv6Message{} - opt := OptUserClass{UserClasses: [][]byte{[]byte("ipxeLegacy")}} - msg.AddOption(&opt) - require.False(t, IsUsingUEFI(&msg)) -} - -func TestGetTransactionIDMessage(t *testing.T) { - message, err := NewMessage() - require.NoError(t, err) - transactionID, err := GetTransactionID(message) - require.NoError(t, err) - require.Equal(t, transactionID, message.(*DHCPv6Message).TransactionID()) -} - -func TestGetTransactionIDRelay(t *testing.T) { - message, err := NewMessage() - require.NoError(t, err) - relay, err := EncapsulateRelay(message, MessageTypeRelayForward, nil, nil) - require.NoError(t, err) - transactionID, err := GetTransactionID(relay) - require.NoError(t, err) - require.Equal(t, transactionID, message.(*DHCPv6Message).TransactionID()) -} |