diff options
-rw-r--r-- | dhcpv4/option_archtype.go | 109 | ||||
-rw-r--r-- | dhcpv4/option_archtype_test.go | 68 | ||||
-rw-r--r-- | dhcpv4/options.go | 22 | ||||
-rw-r--r-- | dhcpv4/options_test.go | 64 |
4 files changed, 245 insertions, 18 deletions
diff --git a/dhcpv4/option_archtype.go b/dhcpv4/option_archtype.go new file mode 100644 index 0000000..16ca98d --- /dev/null +++ b/dhcpv4/option_archtype.go @@ -0,0 +1,109 @@ +package dhcpv4 + +// This option implements the Client System Architecture Type option +// https://tools.ietf.org/html/rfc4578 + +import ( + "encoding/binary" + "fmt" +) + +//ArchType encodes an architecture type in an uint16 +type ArchType uint16 + +// 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 +) + +// 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 encapsulating the Client System +// Architecture Type option Definition. +type OptClientArchType struct { + ArchTypes []ArchType +} + +// Code returns the option code. +func (o *OptClientArchType) Code() OptionCode { + return OptionClientSystemArchitectureType +} + +// ToBytes returns a serialized stream of bytes for this option. +func (o *OptClientArchType) ToBytes() []byte { + ret := []byte{byte(o.Code()), byte(o.Length())} + for _, at := range o.ArchTypes { + buf := make([]byte, 2) + binary.BigEndian.PutUint16(buf[0:2], uint16(at)) + ret = append(ret, buf...) + } + return ret +} + +// Length returns the length of the data portion (excluding option code an byte +// length). +func (o *OptClientArchType) Length() int { + return 2*len(o.ArchTypes) +} + +// String returns a human-readable string. +func (o *OptClientArchType) String() string { + var archTypes string + for idx, at := range o.ArchTypes { + name, ok := ArchTypeToStringMap[at] + if !ok { + name = "Unknown" + } + archTypes += name + if idx < len(o.ArchTypes)-1 { + archTypes += ", " + } + } + return fmt.Sprintf("Client System Architecture Type -> %v", archTypes) +} + +// ParseOptClientArchType returns a new OptClientArchType from a byte stream, +// or error if any. +func ParseOptClientArchType(data []byte) (*OptClientArchType, error) { + if len(data) < 2 { + return nil, ErrShortByteStream + } + code := OptionCode(data[0]) + if code != OptionClientSystemArchitectureType { + return nil, fmt.Errorf("expected code %v, got %v", OptionClientSystemArchitectureType, code) + } + length := int(data[1]) + if length == 0 || length%2 != 0 { + return nil, fmt.Errorf("Invalid length: expected multiple of 2 larger than 2, got %v", length) + } + if len(data) < 2+length { + return nil, ErrShortByteStream + } + archTypes := make([]ArchType, 0, length%2) + for idx := 0; idx < length; idx += 2 { + b := data[2+idx : 2+idx+2] + archTypes = append(archTypes, ArchType(binary.BigEndian.Uint16(b))) + } + return &OptClientArchType{ArchTypes: archTypes}, nil +} diff --git a/dhcpv4/option_archtype_test.go b/dhcpv4/option_archtype_test.go new file mode 100644 index 0000000..cb9bc3f --- /dev/null +++ b/dhcpv4/option_archtype_test.go @@ -0,0 +1,68 @@ +package dhcpv4 + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestParseOptClientArchType(t *testing.T) { + data := []byte{ + 93, // OptionClientSystemArchitectureType + 2, // Length + 0, 6, // EFI_IA32 + } + opt, err := ParseOptClientArchType(data) + require.NoError(t, err) + require.Equal(t, opt.ArchTypes[0], EFI_IA32) +} + +func TestParseOptClientArchTypeMultiple(t *testing.T) { + data := []byte{ + 93, // OptionClientSystemArchitectureType + 4, // Length + 0, 6, // EFI_IA32 + 0, 2, // EFI_ITANIUM + } + opt, err := ParseOptClientArchType(data) + require.NoError(t, err) + require.Equal(t, opt.ArchTypes[0], EFI_IA32) + require.Equal(t, opt.ArchTypes[1], EFI_ITANIUM) +} + +func TestParseOptClientArchTypeInvalid(t *testing.T) { + data := []byte{42} + _, err := ParseOptClientArchType(data) + require.Error(t, err) +} + +func TestOptClientArchTypeParseAndToBytes(t *testing.T) { + data := []byte{ + 93, // OptionClientSystemArchitectureType + 2, // Length + 0, 8, // EFI_XSCALE + } + opt, err := ParseOptClientArchType(data) + require.NoError(t, err) + require.Equal(t, opt.ToBytes(), data) +} + +func TestOptClientArchTypeParseAndToBytesMultiple(t *testing.T) { + data := []byte{ + 93, // OptionClientSystemArchitectureType + 4, // Length + 0, 8, // EFI_XSCALE + 0, 6, // EFI_IA32 + } + opt, err := ParseOptClientArchType(data) + require.NoError(t, err) + require.Equal(t, opt.ToBytes(), data) +} + +func TestOptClientArchType(t *testing.T) { + opt := OptClientArchType{ + ArchTypes: []ArchType{EFI_ITANIUM}, + } + require.Equal(t, opt.Length(), 2) + require.Equal(t, opt.Code(), OptionClientSystemArchitectureType) +} diff --git a/dhcpv4/options.go b/dhcpv4/options.go index 0be494f..d869b7d 100644 --- a/dhcpv4/options.go +++ b/dhcpv4/options.go @@ -44,8 +44,14 @@ func ParseOption(data []byte) (Option, error) { opt, err = ParseOptSubnetMask(data) case OptionRouter: opt, err = ParseOptRouter(data) + case OptionDomainNameServer: + opt, err = ParseOptDomainNameServer(data) case OptionHostName: opt, err = ParseOptHostName(data) + case OptionDomainName: + opt, err = ParseOptDomainName(data) + case OptionBroadcastAddress: + opt, err = ParseOptBroadcastAddress(data) case OptionNTPServers: opt, err = ParseOptNTPServers(data) case OptionRequestedIPAddress: @@ -54,28 +60,24 @@ func ParseOption(data []byte) (Option, error) { opt, err = ParseOptIPAddressLeaseTime(data) case OptionDHCPMessageType: opt, err = ParseOptMessageType(data) - case OptionParameterRequestList: - opt, err = ParseOptParameterRequestList(data) case OptionServerIdentifier: opt, err = ParseOptServerIdentifier(data) - case OptionBroadcastAddress: - opt, err = ParseOptBroadcastAddress(data) + case OptionParameterRequestList: + opt, err = ParseOptParameterRequestList(data) case OptionMaximumDHCPMessageSize: opt, err = ParseOptMaximumDHCPMessageSize(data) case OptionClassIdentifier: opt, err = ParseOptClassIdentifier(data) - case OptionDomainName: - opt, err = ParseOptDomainName(data) - case OptionDomainNameServer: - opt, err = ParseOptDomainNameServer(data) - case OptionVendorIdentifyingVendorClass: - opt, err = ParseOptVIVC(data) case OptionTFTPServerName: opt, err = ParseOptTFTPServerName(data) case OptionBootfileName: opt, err = ParseOptBootfileName(data) case OptionUserClassInformation: opt, err = ParseOptUserClass(data) + case OptionClientSystemArchitectureType: + opt, err = ParseOptClientArchType(data) + case OptionVendorIdentifyingVendorClass: + opt, err = ParseOptVIVC(data) case OptionDNSDomainSearchList: opt, err = ParseOptDomainSearch(data) default: diff --git a/dhcpv4/options_test.go b/dhcpv4/options_test.go index b3d7605..0268483 100644 --- a/dhcpv4/options_test.go +++ b/dhcpv4/options_test.go @@ -25,6 +25,46 @@ func TestParseOption(t *testing.T) { require.Equal(t, 4, opt.Length(), "Length") require.Equal(t, option, opt.ToBytes(), "ToBytes") + // Option router + option = []byte{3, 4, 192, 168, 1, 1} + opt, err = ParseOption(option) + require.NoError(t, err) + require.Equal(t, OptionRouter, opt.Code(), "Code") + require.Equal(t, 4, opt.Length(), "Length") + require.Equal(t, option, opt.ToBytes(), "ToBytes") + + // Option domain name server + option = []byte{6, 4, 192, 168, 1, 1} + opt, err = ParseOption(option) + require.NoError(t, err) + require.Equal(t, OptionDomainNameServer, opt.Code(), "Code") + require.Equal(t, 4, opt.Length(), "Length") + require.Equal(t, option, opt.ToBytes(), "ToBytes") + + // Option host name + option = []byte{12, 4, 't', 'e', 's', 't'} + opt, err = ParseOption(option) + require.NoError(t, err) + require.Equal(t, OptionHostName, opt.Code(), "Code") + require.Equal(t, 4, opt.Length(), "Length") + require.Equal(t, option, opt.ToBytes(), "ToBytes") + + // Option domain name + option = []byte{15, 4, 't', 'e', 's', 't'} + opt, err = ParseOption(option) + require.NoError(t, err) + require.Equal(t, OptionDomainName, opt.Code(), "Code") + require.Equal(t, 4, opt.Length(), "Length") + require.Equal(t, option, opt.ToBytes(), "ToBytes") + + // Option NTP servers + option = []byte{42, 4, 10, 10, 10, 10} + opt, err = ParseOption(option) + require.NoError(t, err) + require.Equal(t, OptionNTPServers, opt.Code(), "Code") + require.Equal(t, 4, opt.Length(), "Length") + require.Equal(t, option, opt.ToBytes(), "ToBytes") + // Requested IP address option = []byte{50, 4, 1, 2, 3, 4} opt, err = ParseOption(option) @@ -41,14 +81,6 @@ func TestParseOption(t *testing.T) { require.Equal(t, 1, opt.Length(), "Length") require.Equal(t, option, opt.ToBytes(), "ToBytes") - // Parameter request list - option = []byte{55, 3, 5, 53, 61} - opt, err = ParseOption(option) - require.NoError(t, err) - require.Equal(t, OptionParameterRequestList, opt.Code(), "Code") - require.Equal(t, 3, opt.Length(), "Length") - require.Equal(t, option, opt.ToBytes(), "ToBytes") - // Option server ID option = []byte{54, 4, 1, 2, 3, 4} opt, err = ParseOption(option) @@ -57,6 +89,14 @@ func TestParseOption(t *testing.T) { require.Equal(t, 4, opt.Length(), "Length") require.Equal(t, option, opt.ToBytes(), "ToBytes") + // Parameter request list + option = []byte{55, 3, 5, 53, 61} + opt, err = ParseOption(option) + require.NoError(t, err) + require.Equal(t, OptionParameterRequestList, opt.Code(), "Code") + require.Equal(t, 3, opt.Length(), "Length") + require.Equal(t, option, opt.ToBytes(), "ToBytes") + // Option max message size option = []byte{57, 2, 1, 2} opt, err = ParseOption(option) @@ -96,6 +136,14 @@ func TestParseOption(t *testing.T) { require.Equal(t, OptionUserClassInformation, opt.Code(), "Code") require.Equal(t, 5, opt.Length(), "Length") require.Equal(t, option, opt.ToBytes(), "ToBytes") + + // Option client system architecture type option + option = []byte{93, 4, 't', 'e', 's', 't'} + opt, err = ParseOption(option) + require.NoError(t, err) + require.Equal(t, OptionClientSystemArchitectureType, opt.Code(), "Code") + require.Equal(t, 4, opt.Length(), "Length") + require.Equal(t, option, opt.ToBytes(), "ToBytes") } func TestParseOptionZeroLength(t *testing.T) { |