diff options
author | Sean Karlage <skarlage@fb.com> | 2018-03-10 22:47:27 -0800 |
---|---|---|
committer | insomniac <insomniacslk@users.noreply.github.com> | 2018-03-13 09:13:53 +0000 |
commit | 1d020f7d3aba3dfe1c142c5f73d3b3b3992ad2ad (patch) | |
tree | 15fbc777cd1643593bc2a16f57adf2886b797144 /dhcpv4 | |
parent | 7194d354e6b511c0af20bed1147347089da7315e (diff) |
Add OptionGeneric, comment out other code so tests compile/pass
Diffstat (limited to 'dhcpv4')
-rw-r--r-- | dhcpv4/bsdp/bsdp.go | 116 | ||||
-rw-r--r-- | dhcpv4/bsdp/bsdp_test.go | 111 | ||||
-rw-r--r-- | dhcpv4/dhcpv4.go | 81 | ||||
-rw-r--r-- | dhcpv4/dhcpv4_test.go | 2 | ||||
-rw-r--r-- | dhcpv4/option_generic.go | 43 | ||||
-rw-r--r-- | dhcpv4/option_generic_test.go | 70 | ||||
-rw-r--r-- | dhcpv4/options.go | 102 | ||||
-rw-r--r-- | dhcpv4/options_test.go | 71 | ||||
-rw-r--r-- | dhcpv4/types.go | 16 |
9 files changed, 347 insertions, 265 deletions
diff --git a/dhcpv4/bsdp/bsdp.go b/dhcpv4/bsdp/bsdp.go index d4d4e31..f18fdce 100644 --- a/dhcpv4/bsdp/bsdp.go +++ b/dhcpv4/bsdp/bsdp.go @@ -137,8 +137,8 @@ func ParseVendorOptionsFromOptions(options []dhcpv4.Option) []dhcpv4.Option { err error ) for _, opt := range options { - if opt.Code == dhcpv4.OptionVendorSpecificInformation { - vendorOpts, err = dhcpv4.OptionsFromBytesWithoutMagicCookie(opt.Data) + if opt.Code() == dhcpv4.OptionVendorSpecificInformation { + vendorOpts, err = dhcpv4.OptionsFromBytesWithoutMagicCookie(opt.(*dhcpv4.OptionGeneric).Data) if err != nil { log.Println("Warning: could not parse vendor options in DHCP options") return []dhcpv4.Option{} @@ -154,8 +154,8 @@ func ParseVendorOptionsFromOptions(options []dhcpv4.Option) []dhcpv4.Option { func ParseBootImageListFromAck(ack dhcpv4.DHCPv4) ([]BootImage, error) { var bootImages []BootImage for _, opt := range ParseVendorOptionsFromOptions(ack.Options()) { - if opt.Code == OptionBootImageList { - images, err := ParseBootImagesFromOption(opt.Data) + if opt.Code() == OptionBootImageList { + images, err := ParseBootImagesFromOption(opt.(*dhcpv4.OptionGeneric).Data) if err != nil { return nil, err } @@ -191,31 +191,35 @@ func NewInformListForInterface(iface string, replyPort uint16) (*dhcpv4.DHCPv4, // These are vendor-specific options used to pass along BSDP information. vendorOpts := []dhcpv4.Option{ - dhcpv4.Option{ - Code: OptionMessageType, - Data: []byte{byte(MessageTypeList)}, + dhcpv4.OptionGeneric{ + OptionCode: OptionMessageType, + Data: []byte{byte(MessageTypeList)}, }, - dhcpv4.Option{ - Code: OptionVersion, - Data: Version1_1, + dhcpv4.OptionGeneric{ + OptionCode: OptionVersion, + Data: Version1_1, }, } if needsReplyPort(replyPort) { vendorOpts = append(vendorOpts, - dhcpv4.Option{ - Code: OptionReplyPort, - Data: serializeReplyPort(replyPort), + dhcpv4.OptionGeneric{ + OptionCode: OptionReplyPort, + Data: serializeReplyPort(replyPort), }, ) } - d.AddOption(dhcpv4.Option{ - Code: dhcpv4.OptionVendorSpecificInformation, - Data: dhcpv4.OptionsToBytesWithoutMagicCookie(vendorOpts), + var vendorOptsBytes []byte + for _, opt := range vendorOpts { + vendorOptsBytes = append(vendorOptsBytes, opt.ToBytes()...) + } + d.AddOption(dhcpv4.OptionGeneric{ + OptionCode: dhcpv4.OptionVendorSpecificInformation, + Data: vendorOptsBytes, }) - d.AddOption(dhcpv4.Option{ - Code: dhcpv4.OptionParameterRequestList, + d.AddOption(dhcpv4.OptionGeneric{ + OptionCode: dhcpv4.OptionParameterRequestList, Data: []byte{ byte(dhcpv4.OptionVendorSpecificInformation), byte(dhcpv4.OptionClassIdentifier), @@ -224,21 +228,21 @@ func NewInformListForInterface(iface string, replyPort uint16) (*dhcpv4.DHCPv4, u16 := make([]byte, 2) binary.BigEndian.PutUint16(u16, MaxDHCPMessageSize) - d.AddOption(dhcpv4.Option{ - Code: dhcpv4.OptionMaximumDHCPMessageSize, - Data: u16, + d.AddOption(dhcpv4.OptionGeneric{ + OptionCode: dhcpv4.OptionMaximumDHCPMessageSize, + Data: u16, }) vendorClassID, err := makeVendorClassIdentifier() if err != nil { return nil, err } - d.AddOption(dhcpv4.Option{ - Code: dhcpv4.OptionClassIdentifier, - Data: []byte(vendorClassID), + d.AddOption(dhcpv4.OptionGeneric{ + OptionCode: dhcpv4.OptionClassIdentifier, + Data: []byte(vendorClassID), }) - d.AddOption(dhcpv4.Option{Code: dhcpv4.OptionEnd}) + d.AddOption(dhcpv4.OptionGeneric{OptionCode: dhcpv4.OptionEnd}) return d, nil } @@ -267,17 +271,17 @@ func InformSelectForAck(ack dhcpv4.DHCPv4, replyPort uint16, selectedImage BootI // Data for OptionSelectedBootImageID vendorOpts := []dhcpv4.Option{ - dhcpv4.Option{ - Code: OptionMessageType, - Data: []byte{byte(MessageTypeSelect)}, + dhcpv4.OptionGeneric{ + OptionCode: OptionMessageType, + Data: []byte{byte(MessageTypeSelect)}, }, - dhcpv4.Option{ - Code: OptionVersion, - Data: Version1_1, + dhcpv4.OptionGeneric{ + OptionCode: OptionVersion, + Data: Version1_1, }, - dhcpv4.Option{ - Code: OptionSelectedBootImageID, - Data: selectedImage.ID.ToBytes(), + dhcpv4.OptionGeneric{ + OptionCode: OptionSelectedBootImageID, + Data: selectedImage.ID.ToBytes(), }, } @@ -285,23 +289,23 @@ func InformSelectForAck(ack dhcpv4.DHCPv4, replyPort uint16, selectedImage BootI var serverIP net.IP // TODO replace this loop with `ack.GetOneOption(OptionBootImageList)` for _, opt := range ack.Options() { - if opt.Code == dhcpv4.OptionServerIdentifier { - serverIP = net.IP(opt.Data) + if opt.Code() == dhcpv4.OptionServerIdentifier { + serverIP = net.IP(opt.(*dhcpv4.OptionGeneric).Data) } } if serverIP.To4() == nil { return nil, fmt.Errorf("could not parse server identifier from ACK") } - vendorOpts = append(vendorOpts, dhcpv4.Option{ - Code: OptionServerIdentifier, - Data: serverIP, + vendorOpts = append(vendorOpts, dhcpv4.OptionGeneric{ + OptionCode: OptionServerIdentifier, + Data: serverIP, }) // Validate replyPort if requested. if needsReplyPort(replyPort) { - vendorOpts = append(vendorOpts, dhcpv4.Option{ - Code: OptionReplyPort, - Data: serializeReplyPort(replyPort), + vendorOpts = append(vendorOpts, dhcpv4.OptionGeneric{ + OptionCode: OptionReplyPort, + Data: serializeReplyPort(replyPort), }) } @@ -309,12 +313,12 @@ func InformSelectForAck(ack dhcpv4.DHCPv4, replyPort uint16, selectedImage BootI if err != nil { return nil, err } - d.AddOption(dhcpv4.Option{ - Code: dhcpv4.OptionClassIdentifier, - Data: []byte(vendorClassID), + d.AddOption(dhcpv4.OptionGeneric{ + OptionCode: dhcpv4.OptionClassIdentifier, + Data: []byte(vendorClassID), }) - d.AddOption(dhcpv4.Option{ - Code: dhcpv4.OptionParameterRequestList, + d.AddOption(dhcpv4.OptionGeneric{ + OptionCode: dhcpv4.OptionParameterRequestList, Data: []byte{ byte(dhcpv4.OptionSubnetMask), byte(dhcpv4.OptionRouter), @@ -323,14 +327,18 @@ func InformSelectForAck(ack dhcpv4.DHCPv4, replyPort uint16, selectedImage BootI byte(dhcpv4.OptionClassIdentifier), }, }) - d.AddOption(dhcpv4.Option{ - Code: dhcpv4.OptionDHCPMessageType, - Data: []byte{byte(dhcpv4.MessageTypeInform)}, + d.AddOption(dhcpv4.OptionGeneric{ + OptionCode: dhcpv4.OptionDHCPMessageType, + Data: []byte{byte(dhcpv4.MessageTypeInform)}, }) - d.AddOption(dhcpv4.Option{ - Code: dhcpv4.OptionVendorSpecificInformation, - Data: dhcpv4.OptionsToBytesWithoutMagicCookie(vendorOpts), + var vendorOptsBytes []byte + for _, opt := range vendorOpts { + vendorOptsBytes = append(vendorOptsBytes, opt.ToBytes()...) + } + d.AddOption(dhcpv4.OptionGeneric{ + OptionCode: dhcpv4.OptionVendorSpecificInformation, + Data: vendorOptsBytes, }) - d.AddOption(dhcpv4.Option{Code: dhcpv4.OptionEnd}) + d.AddOption(dhcpv4.OptionGeneric{OptionCode: dhcpv4.OptionEnd}) return d, nil } diff --git a/dhcpv4/bsdp/bsdp_test.go b/dhcpv4/bsdp/bsdp_test.go index 8ab2518..e323b52 100644 --- a/dhcpv4/bsdp/bsdp_test.go +++ b/dhcpv4/bsdp/bsdp_test.go @@ -208,27 +208,31 @@ func TestParseBootImageFail(t *testing.T) { */ func TestParseVendorOptions(t *testing.T) { expectedOpts := []dhcpv4.Option{ - dhcpv4.Option{ - Code: OptionMessageType, - Data: []byte{byte(MessageTypeList)}, + &dhcpv4.OptionGeneric{ + OptionCode: OptionMessageType, + Data: []byte{byte(MessageTypeList)}, }, - dhcpv4.Option{ - Code: OptionVersion, - Data: Version1_0, + &dhcpv4.OptionGeneric{ + OptionCode: OptionVersion, + Data: Version1_0, }, } + var expectedOptsBytes []byte + for _, opt := range expectedOpts { + expectedOptsBytes = append(expectedOptsBytes, opt.ToBytes()...) + } recvOpts := []dhcpv4.Option{ - dhcpv4.Option{ - Code: dhcpv4.OptionDHCPMessageType, - Data: []byte{byte(dhcpv4.MessageTypeAck)}, + &dhcpv4.OptionGeneric{ + OptionCode: dhcpv4.OptionDHCPMessageType, + Data: []byte{byte(dhcpv4.MessageTypeAck)}, }, - dhcpv4.Option{ - Code: dhcpv4.OptionBroadcastAddress, - Data: []byte{0xff, 0xff, 0xff, 0xff}, + &dhcpv4.OptionGeneric{ + OptionCode: dhcpv4.OptionBroadcastAddress, + Data: []byte{0xff, 0xff, 0xff, 0xff}, }, - dhcpv4.Option{ - Code: dhcpv4.OptionVendorSpecificInformation, - Data: dhcpv4.OptionsToBytesWithoutMagicCookie(expectedOpts), + &dhcpv4.OptionGeneric{ + OptionCode: dhcpv4.OptionVendorSpecificInformation, + Data: expectedOptsBytes, }, } opts := ParseVendorOptionsFromOptions(recvOpts) @@ -237,13 +241,13 @@ func TestParseVendorOptions(t *testing.T) { func TestParseVendorOptionsFromOptionsNotPresent(t *testing.T) { expectedOpts := []dhcpv4.Option{ - dhcpv4.Option{ - Code: dhcpv4.OptionDHCPMessageType, - Data: []byte{byte(dhcpv4.MessageTypeAck)}, + dhcpv4.OptionGeneric{ + OptionCode: dhcpv4.OptionDHCPMessageType, + Data: []byte{byte(dhcpv4.MessageTypeAck)}, }, - dhcpv4.Option{ - Code: dhcpv4.OptionBroadcastAddress, - Data: []byte{0xff, 0xff, 0xff, 0xff}, + dhcpv4.OptionGeneric{ + OptionCode: dhcpv4.OptionBroadcastAddress, + Data: []byte{0xff, 0xff, 0xff, 0xff}, }, } opts := ParseVendorOptionsFromOptions(expectedOpts) @@ -257,8 +261,8 @@ func TestParseVendorOptionsFromOptionsEmpty(t *testing.T) { func TestParseVendorOptionsFromOptionsFail(t *testing.T) { opts := []dhcpv4.Option{ - dhcpv4.Option{ - Code: dhcpv4.OptionVendorSpecificInformation, + &dhcpv4.OptionGeneric{ + OptionCode: dhcpv4.OptionVendorSpecificInformation, Data: []byte{ 0x1, 0x1, 0x1, // Option 1: LIST 0x2, 0x2, 0x01, // Option 2: Version (intentionally left short) @@ -296,14 +300,13 @@ func TestParseBootImageListFromAck(t *testing.T) { bootImageBytes = append(bootImageBytes, image.ToBytes()...) } ack, _ := dhcpv4.New() - ack.AddOption(dhcpv4.Option{ - Code: dhcpv4.OptionVendorSpecificInformation, - Data: dhcpv4.OptionsToBytesWithoutMagicCookie([]dhcpv4.Option{ - dhcpv4.Option{ - Code: OptionBootImageList, - Data: bootImageBytes, - }, - }), + bootImageListOpt := dhcpv4.OptionGeneric{ + OptionCode: OptionBootImageList, + Data: bootImageBytes, + } + ack.AddOption(&dhcpv4.OptionGeneric{ + OptionCode: dhcpv4.OptionVendorSpecificInformation, + Data: bootImageListOpt.ToBytes(), }) images, err := ParseBootImageListFromAck(*ack) @@ -313,9 +316,9 @@ func TestParseBootImageListFromAck(t *testing.T) { func TestParseBootImageListFromAckNoVendorOption(t *testing.T) { ack, _ := dhcpv4.New() - ack.AddOption(dhcpv4.Option{ - Code: OptionMessageType, - Data: []byte{byte(dhcpv4.MessageTypeAck)}, + ack.AddOption(dhcpv4.OptionGeneric{ + OptionCode: OptionMessageType, + Data: []byte{byte(dhcpv4.MessageTypeAck)}, }) images, err := ParseBootImageListFromAck(*ack) require.NoError(t, err, "no vendor extensions should not return error") @@ -324,29 +327,27 @@ func TestParseBootImageListFromAckNoVendorOption(t *testing.T) { func TestParseBootImageListFromAckFail(t *testing.T) { ack, _ := dhcpv4.New() - ack.AddOption(dhcpv4.Option{ - Code: OptionMessageType, - Data: []byte{byte(dhcpv4.MessageTypeAck)}, + ack.AddOption(dhcpv4.OptionGeneric{ + OptionCode: OptionMessageType, + Data: []byte{byte(dhcpv4.MessageTypeAck)}, }) - ack.AddOption(dhcpv4.Option{ - Code: dhcpv4.OptionVendorSpecificInformation, - Data: dhcpv4.OptionsToBytesWithoutMagicCookie([]dhcpv4.Option{ - dhcpv4.Option{ - Code: OptionBootImageList, - Data: []byte{ - // boot image 1 - 0x1, 0, 0x10, 0x10, // boot image ID - 7, // len(Name) - 98, 115, 100, 112, 45, 49, // byte-encoding of Name (intentionally short) + ack.AddOption(&dhcpv4.OptionGeneric{ + OptionCode: dhcpv4.OptionVendorSpecificInformation, + Data: []byte{ + 9, // OptionBootImageList + 24, // length + // boot image 1 + 0x1, 0, 0x10, 0x10, // boot image ID + 7, // len(Name) + 98, 115, 100, 112, 45, 49, // byte-encoding of Name (intentionally short) - // boot image 2 - 0x82, 0, 0x11, 0x22, // boot image ID - 8, // len(Name) - 98, 115, 100, 112, 45, 50, 50, 50, // byte-encoding of Name - }, - }, - }), - }) + // boot image 2 + 0x82, 0, 0x11, 0x22, // boot image ID + 8, // len(Name) + 98, 115, 100, 112, 45, 50, 50, 50, // byte-encoding of Name + }, + }, + ) images, err := ParseBootImageListFromAck(*ack) require.Nil(t, images, "should get nil on parse error") diff --git a/dhcpv4/dhcpv4.go b/dhcpv4/dhcpv4.go index 31bdd47..9ab1908 100644 --- a/dhcpv4/dhcpv4.go +++ b/dhcpv4/dhcpv4.go @@ -133,12 +133,12 @@ func NewDiscoveryForInterface(ifname string) (*DHCPv4, error) { d.SetHwAddrLen(uint8(len(iface.HardwareAddr))) d.SetClientHwAddr(iface.HardwareAddr) d.SetBroadcast() - d.AddOption(Option{ - Code: OptionDHCPMessageType, - Data: []byte{byte(MessageTypeDiscover)}, + d.AddOption(&OptionGeneric{ + OptionCode: OptionDHCPMessageType, + Data: []byte{byte(MessageTypeDiscover)}, }) - d.AddOption(Option{ - Code: OptionParameterRequestList, + d.AddOption(&OptionGeneric{ + OptionCode: OptionParameterRequestList, Data: []byte{ byte(OptionSubnetMask), byte(OptionRouter), @@ -147,7 +147,7 @@ func NewDiscoveryForInterface(ifname string) (*DHCPv4, error) { }, }) // the End option has to be added explicitly - d.AddOption(Option{Code: OptionEnd}) + d.AddOption(&OptionGeneric{OptionCode: OptionEnd}) return d, nil } @@ -183,9 +183,9 @@ func NewInformForInterface(ifname string, needsBroadcast bool) (*DHCPv4, error) } d.SetClientIPAddr(localIPs[0]) - d.AddOption(Option{ - Code: OptionDHCPMessageType, - Data: []byte{byte(MessageTypeInform)}, + d.AddOption(&OptionGeneric{ + OptionCode: OptionDHCPMessageType, + Data: []byte{byte(MessageTypeInform)}, }) return d, nil @@ -211,29 +211,29 @@ func RequestFromOffer(offer DHCPv4) (*DHCPv4, error) { // find server IP address var serverIP []byte for _, opt := range offer.options { - if opt.Code == OptionServerIdentifier { + if opt.Code() == OptionServerIdentifier { serverIP = make([]byte, 4) - copy(serverIP, opt.Data) + copy(serverIP, opt.(*OptionGeneric).Data) } } if serverIP == nil { return nil, errors.New("Missing Server IP Address in DHCP Offer") } d.SetServerIPAddr(serverIP) - d.AddOption(Option{ - Code: OptionDHCPMessageType, - Data: []byte{byte(MessageTypeRequest)}, + d.AddOption(&OptionGeneric{ + OptionCode: OptionDHCPMessageType, + Data: []byte{byte(MessageTypeRequest)}, }) - d.AddOption(Option{ - Code: OptionRequestedIPAddress, - Data: offer.YourIPAddr(), + d.AddOption(&OptionGeneric{ + OptionCode: OptionRequestedIPAddress, + Data: offer.YourIPAddr(), }) - d.AddOption(Option{ - Code: OptionServerIdentifier, - Data: serverIP, + d.AddOption(&OptionGeneric{ + OptionCode: OptionServerIdentifier, + Data: serverIP, }) // the End option has to be added explicitly - d.AddOption(Option{Code: OptionEnd}) + d.AddOption(&OptionGeneric{OptionCode: OptionEnd}) return d, nil } @@ -349,22 +349,27 @@ func (d *DHCPv4) SetTransactionID(transactionID uint32) { d.transactionID = transactionID } +// NumSeconds returns the number of seconds. func (d *DHCPv4) NumSeconds() uint16 { return d.numSeconds } +// SetNumSeconds sets the seconds field. func (d *DHCPv4) SetNumSeconds(numSeconds uint16) { d.numSeconds = numSeconds } +// Flags returns the DHCP flags portion of the packet. func (d *DHCPv4) Flags() uint16 { return d.flags } +// SetFlags sets the flags field in the packet. func (d *DHCPv4) SetFlags(flags uint16) { d.flags = flags } +// FlagsToString returns a human-readable representation of the flags field. func (d *DHCPv4) FlagsToString() string { flags := "" if d.IsBroadcast() { @@ -378,54 +383,67 @@ func (d *DHCPv4) FlagsToString() string { return flags } +// IsBroadcast indicates whether the packet is a broadcast packet. func (d *DHCPv4) IsBroadcast() bool { return d.flags&0x8000 == 0x8000 } +// SetBroadcast sets the packet to be a broadcast packet. func (d *DHCPv4) SetBroadcast() { d.flags |= 0x8000 } +// IsUnicast indicates whether the packet is a unicast packet. func (d *DHCPv4) IsUnicast() bool { return d.flags&0x8000 == 0 } +// SetUnicast sets the packet to be a unicast packet. func (d *DHCPv4) SetUnicast() { d.flags &= ^uint16(0x8000) } +// ClientIPAddr returns the client IP address. func (d *DHCPv4) ClientIPAddr() net.IP { return d.clientIPAddr } +// SetClientIPAddr sets the client IP address. func (d *DHCPv4) SetClientIPAddr(clientIPAddr net.IP) { d.clientIPAddr = clientIPAddr } +// YourIPAddr returns the "your IP address" field. func (d *DHCPv4) YourIPAddr() net.IP { return d.yourIPAddr } +// SetYourIPAddr sets the "your IP address" field. func (d *DHCPv4) SetYourIPAddr(yourIPAddr net.IP) { d.yourIPAddr = yourIPAddr } +// ServerIPAddr returns the server IP address. func (d *DHCPv4) ServerIPAddr() net.IP { return d.serverIPAddr } +// SetServerIPAddr sets the server IP address. func (d *DHCPv4) SetServerIPAddr(serverIPAddr net.IP) { d.serverIPAddr = serverIPAddr } +// GatewayIPAddr returns the gateway IP address. func (d *DHCPv4) GatewayIPAddr() net.IP { return d.gatewayIPAddr } +// SetGatewayIPAddr sets the gateway IP address. func (d *DHCPv4) SetGatewayIPAddr(gatewayIPAddr net.IP) { d.gatewayIPAddr = gatewayIPAddr } +// ClientHwAddr returns the client hardware (MAC) address. func (d *DHCPv4) ClientHwAddr() [16]byte { return d.clientHwAddr } @@ -438,6 +456,7 @@ func (d *DHCPv4) ClientHwAddrToString() string { return strings.Join(ret, ":") } +// SetClientHwAddr sets the client hardware address. func (d *DHCPv4) SetClientHwAddr(clientHwAddr []byte) { if len(clientHwAddr) > 16 { log.Printf("Warning: too long HW Address (%d bytes), truncating to 16 bytes", len(clientHwAddr)) @@ -517,7 +536,7 @@ func (d *DHCPv4) StrippedOptions() []Option { strippedOptions := []Option{} for _, opt := range d.options { strippedOptions = append(strippedOptions, opt) - if opt.Code == OptionEnd { + if opt.Code() == OptionEnd { break } } @@ -576,7 +595,7 @@ func (d *DHCPv4) Summary() string { ret += " options=\n" for _, opt := range d.options { ret += fmt.Sprintf(" %v\n", opt.String()) - if opt.Code == OptionEnd { + if opt.Code() == OptionEnd { break } } @@ -590,15 +609,15 @@ func (d *DHCPv4) ValidateOptions() { foundOptionEnd := false for _, opt := range d.options { if foundOptionEnd { - if opt.Code == OptionEnd { + if opt.Code() == OptionEnd { log.Print("Warning: found duplicate End option") } - if opt.Code != OptionEnd && opt.Code != OptionPad { - name := OptionCodeToString[opt.Code] - log.Printf("Warning: found option %v (%v) after End option", opt.Code, name) + if opt.Code() != OptionEnd && opt.Code() != OptionPad { + name := OptionCodeToString[opt.Code()] + log.Printf("Warning: found option %v (%v) after End option", opt.Code(), name) } } - if opt.Code == OptionEnd { + if opt.Code() == OptionEnd { foundOptionEnd = true } } @@ -632,7 +651,11 @@ func (d *DHCPv4) ToBytes() []byte { ret = append(ret, d.clientHwAddr[:16]...) ret = append(ret, d.serverHostName[:64]...) ret = append(ret, d.bootFileName[:128]...) + d.ValidateOptions() // print warnings about broken options, if any - ret = append(ret, OptionsToBytes(d.options)...) + ret = append(ret, MagicCookie...) + for _, opt := range d.options { + ret = append(ret, opt.ToBytes()...) + } return ret } diff --git a/dhcpv4/dhcpv4_test.go b/dhcpv4/dhcpv4_test.go index e9824ef..065b6e0 100644 --- a/dhcpv4/dhcpv4_test.go +++ b/dhcpv4/dhcpv4_test.go @@ -263,7 +263,7 @@ func TestToStringMethods(t *testing.T) { require.Equal(t, "/my/boot/file", d.BootFileNameToString()) } -func TestToBytes(t *testing.T) { +func TestNewToBytes(t *testing.T) { // the following bytes match what dhcpv4.New would create. Keep them in // sync! expected := []byte{ diff --git a/dhcpv4/option_generic.go b/dhcpv4/option_generic.go new file mode 100644 index 0000000..0fb57cd --- /dev/null +++ b/dhcpv4/option_generic.go @@ -0,0 +1,43 @@ +package dhcpv4 + +import ( + "fmt" +) + +// OptionGeneric is an option that only contains the option code and associated +// data. Every option that does not have a specific implementation will fall +// back to this option. +type OptionGeneric struct { + OptionCode OptionCode + Data []byte +} + +// Code returns the generic option code. +func (o OptionGeneric) Code() OptionCode { + return o.OptionCode +} + +// ToBytes returns a serialized generic option as a slice of bytes. +func (o OptionGeneric) ToBytes() []byte { + ret := []byte{byte(o.OptionCode)} + if o.OptionCode == OptionEnd || o.OptionCode == OptionPad { + return ret + } + ret = append(ret, byte(o.Length())) + ret = append(ret, o.Data...) + return ret +} + +// String returns a human-readable representation of a generic option. +func (o OptionGeneric) String() string { + code, ok := OptionCodeToString[o.OptionCode] + if !ok { + code = "Unknown" + } + return fmt.Sprintf("%v -> %v", code, o.Data) +} + +// Length returns the number of bytes comprising the data section of the option. +func (o OptionGeneric) Length() int { + return len(o.Data) +} diff --git a/dhcpv4/option_generic_test.go b/dhcpv4/option_generic_test.go new file mode 100644 index 0000000..33f8481 --- /dev/null +++ b/dhcpv4/option_generic_test.go @@ -0,0 +1,70 @@ +package dhcpv4 + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestOptionGenericCode(t *testing.T) { + o := OptionGeneric{ + OptionCode: OptionDHCPMessageType, + Data: []byte{byte(MessageTypeDiscover)}, + } + require.Equal(t, OptionDHCPMessageType, o.Code()) +} + +func TestOptionGenericData(t *testing.T) { + o := OptionGeneric{ + OptionCode: OptionNameServer, + Data: []byte{192, 168, 0, 1}, + } + require.Equal(t, []byte{192, 168, 0, 1}, o.Data) +} + +func TestOptionGenericToBytes(t *testing.T) { + o := OptionGeneric{ + OptionCode: OptionDHCPMessageType, + Data: []byte{byte(MessageTypeDiscover)}, + } + serialized := o.ToBytes() + expected := []byte{53, 1, 1} + require.Equal(t, expected, serialized) +} + +func TestOptionGenericToBytesZeroOptions(t *testing.T) { + o := OptionGeneric{OptionCode: OptionEnd} + serialized := o.ToBytes() + expected := []byte{255} + require.Equal(t, expected, serialized) + + o = OptionGeneric{OptionCode: OptionPad} + serialized = o.ToBytes() + expected = []byte{0} + require.Equal(t, expected, serialized) +} + +func TestOptionGenericString(t *testing.T) { + o := OptionGeneric{ + OptionCode: OptionDHCPMessageType, + Data: []byte{byte(MessageTypeDiscover)}, + } + require.Equal(t, "DHCP Message Type -> [1]", o.String()) +} + +func TestOptionGenericStringUnknown(t *testing.T) { + o := OptionGeneric{ + OptionCode: 102, // Returend option code. + Data: []byte{byte(MessageTypeDiscover)}, + } + require.Equal(t, "Unknown -> [1]", o.String()) +} + +func TestOptionGenericLength(t *testing.T) { + filename := "/path/to/file" + o := OptionGeneric{ + OptionCode: OptionBootfileName, + Data: []byte(filename), + } + require.Equal(t, len(filename), o.Length()) +} diff --git a/dhcpv4/options.go b/dhcpv4/options.go index 6576c05..e4a2200 100644 --- a/dhcpv4/options.go +++ b/dhcpv4/options.go @@ -6,36 +6,43 @@ import ( "fmt" ) -type OptionCode byte - +// MagicCookie is the magic 4-byte value at the beginning of the list of options +// in a DHCPv4 packet. var MagicCookie = []byte{99, 130, 83, 99} -// TODO: implement Option as an interface similar to dhcpv6. -type Option struct { - Code OptionCode - Data []byte +// OptionCode is a single byte representing the code for a given Option. +type OptionCode byte + +// Option is an interface that all DHCP v4 options adhere to. +type Option interface { + Code() OptionCode + ToBytes() []byte + Length() int + String() string } -func ParseOption(dataStart []byte) (*Option, error) { - // Parse a sequence of bytes as a single DHCPv4 option. - // Returns the option code, its data, and an error if any. - if len(dataStart) == 0 { - return nil, errors.New("Invalid zero-length DHCPv4 option") +// ParseOption parses a sequence of bytes as a single DHCPv4 option, returning +// the specific option structure or error, if any. +func ParseOption(data []byte) (Option, error) { + if len(data) == 0 { + return nil, errors.New("invalid zero-length DHCPv4 option") } - opt := OptionCode(dataStart[0]) - switch opt { - case OptionPad, OptionEnd: - return &Option{Code: opt, Data: []byte{}}, nil - default: - length := int(dataStart[1]) - if len(dataStart) < length+2 { - return nil, errors.New( - fmt.Sprintf("Invalid data length. Declared %v, actual %v", - length, len(dataStart), - )) + code := OptionCode(data[0]) + var ( + length int + optionData []byte + ) + if code != OptionPad && code != OptionEnd { + length = int(data[1]) + if len(data) < length+2 { + return nil, fmt.Errorf("invalid data length: declared %v, actual %v", length, len(data)) } - data := dataStart[2 : 2+length] - return &Option{Code: opt, Data: data}, nil + optionData = data[2 : length+2] + } + + switch code { + default: + return &OptionGeneric{OptionCode: code, Data: optionData}, nil } } @@ -44,10 +51,10 @@ func ParseOption(dataStart []byte) (*Option, error) { // error if any invalid option or length is found. func OptionsFromBytes(data []byte) ([]Option, error) { if len(data) < len(MagicCookie) { - return nil, errors.New("Invalid options: shorter than 4 bytes") + return nil, errors.New("invalid options: shorter than 4 bytes") } if !bytes.Equal(data[:len(MagicCookie)], MagicCookie) { - return nil, fmt.Errorf("Invalid Magic Cookie: %v", data[:len(MagicCookie)]) + return nil, fmt.Errorf("invalid magic cookie: %v", data[:len(MagicCookie)]) } opts, err := OptionsFromBytesWithoutMagicCookie(data[len(MagicCookie):]) if err != nil { @@ -66,56 +73,23 @@ func OptionsFromBytesWithoutMagicCookie(data []byte) ([]Option, error) { if idx == len(data) { break } + // This should never happen. if idx > len(data) { - // this should never happen - return nil, errors.New("Error: Reading past the end of options") + return nil, errors.New("read past the end of options") } opt, err := ParseOption(data[idx:]) idx++ if err != nil { return nil, err } - options = append(options, *opt) + options = append(options, opt) // Options with zero length have no length byte, so here we handle the // ones with nonzero length - if len(opt.Data) > 0 { + if opt.Length() > 0 { idx++ } - idx += len(opt.Data) + idx += opt.Length() } return options, nil } - -// OptionsToBytes converts a list of options to a wire-format representation -// with the DHCP magic cookie prepended. -func OptionsToBytes(options []Option) []byte { - return append(MagicCookie, OptionsToBytesWithoutMagicCookie(options)...) -} - -// OptionsToBytesWithoutMagicCookie converts a list of options to a wire-format -// representation. -func OptionsToBytesWithoutMagicCookie(options []Option) []byte { - ret := []byte{} - for _, opt := range options { - ret = append(ret, opt.ToBytes()...) - } - return ret -} - -func (o *Option) String() string { - code, ok := OptionCodeToString[o.Code] - if !ok { - code = "Unknown" - } - return fmt.Sprintf("%v -> %v", code, o.Data) -} - -func (o *Option) ToBytes() []byte { - // Convert a single option to its wire-format representation - ret := []byte{byte(o.Code)} - if o.Code != OptionPad && o.Code != OptionEnd { - ret = append(ret, byte(len(o.Data))) - } - return append(ret, o.Data...) -} diff --git a/dhcpv4/options_test.go b/dhcpv4/options_test.go index ca860cf..7ee0ebe 100644 --- a/dhcpv4/options_test.go +++ b/dhcpv4/options_test.go @@ -9,17 +9,12 @@ import ( func TestParseOption(t *testing.T) { option := []byte{5, 4, 192, 168, 1, 254} // DNS option opt, err := ParseOption(option) - require.NoError(t, err, "should not get error from parsing option") - require.Equal(t, OptionNameServer, opt.Code, "opt should have the same opcode") - require.Equal(t, option[2:], opt.Data, "opt should have the same data") -} - -func TestParseOptionPad(t *testing.T) { - option := []byte{0} - opt, err := ParseOption(option) - require.NoError(t, err, "should not get error from parsing option") - require.Equal(t, OptionPad, opt.Code, "should get pad option code") - require.Empty(t, opt.Data, "should get empty data with pad option") + require.NoError(t, err) + generic := opt.(*OptionGeneric) + require.Equal(t, OptionNameServer, generic.Code()) + require.Equal(t, []byte{192, 168, 1, 254}, generic.Data) + require.Equal(t, 4, generic.Length()) + require.Equal(t, "Name Server -> [192 168 1 254]", generic.String()) } func TestParseOptionZeroLength(t *testing.T) { @@ -43,16 +38,12 @@ func TestOptionsFromBytes(t *testing.T) { } opts, err := OptionsFromBytes(options) require.NoError(t, err) - require.Equal(t, []Option{ - Option{ - Code: OptionNameServer, - Data: []byte{192, 168, 1, 1}, - }, - Option{Code: OptionEnd, Data: []byte{}}, - Option{Code: OptionPad, Data: []byte{}}, - Option{Code: OptionPad, Data: []byte{}}, - Option{Code: OptionPad, Data: []byte{}}, - }, opts) + require.Equal(t, 5, len(opts)) + require.Equal(t, opts[0].(*OptionGeneric), &OptionGeneric{OptionCode: OptionNameServer, Data: []byte{192, 168, 1, 1}}) + require.Equal(t, opts[1].(*OptionGeneric), &OptionGeneric{OptionCode: OptionEnd}) + require.Equal(t, opts[2].(*OptionGeneric), &OptionGeneric{OptionCode: OptionPad}) + require.Equal(t, opts[3].(*OptionGeneric), &OptionGeneric{OptionCode: OptionPad}) + require.Equal(t, opts[4].(*OptionGeneric), &OptionGeneric{OptionCode: OptionPad}) } func TestOptionsFromBytesZeroLength(t *testing.T) { @@ -67,39 +58,11 @@ func TestOptionsFromBytesBadMagicCookie(t *testing.T) { require.Error(t, err) } -func TestOptionsToBytes(t *testing.T) { - originalOptions := []byte{ +func TestOptionsFromBytesShortOption(t *testing.T) { + options := []byte{ 99, 130, 83, 99, // Magic Cookie - 5, 4, 192, 168, 1, 1, // DNS - 255, // end - 0, 0, 0, //padding + 5, 4, 192, 168, // DNS } - options, err := OptionsFromBytes(originalOptions) - require.NoError(t, err) - finalOptions := OptionsToBytes(options) - require.Equal(t, originalOptions, finalOptions) -} - -func TestOptionsToBytesEmpty(t *testing.T) { - originalOptions := []byte{99, 130, 83, 99} - options, err := OptionsFromBytes(originalOptions) - require.NoError(t, err) - finalOptions := OptionsToBytes(options) - require.Equal(t, originalOptions, finalOptions) -} - -func TestOptionsToStringPad(t *testing.T) { - option := []byte{0} - opt, err := ParseOption(option) - require.NoError(t, err) - stropt := opt.String() - require.Equal(t, "Pad -> []", stropt) -} - -func TestOptionsToStringDHCPMessageType(t *testing.T) { - option := []byte{53, 1, 5} - opt, err := ParseOption(option) - require.NoError(t, err) - stropt := opt.String() - require.Equal(t, "DHCP Message Type -> [5]", stropt) + _, err := OptionsFromBytes(options) + require.Error(t, err) } diff --git a/dhcpv4/types.go b/dhcpv4/types.go index 91126d9..90ea5da 100644 --- a/dhcpv4/types.go +++ b/dhcpv4/types.go @@ -8,14 +8,14 @@ type MessageType byte // DHCP message types const ( - MessageTypeDiscover MessageType = iota + 1 - MessageTypeOffer - MessageTypeRequest - MessageTypeDecline - MessageTypeAck - MessageTypeNak - MessageTypeRelease - MessageTypeInform + MessageTypeDiscover MessageType = 1 + MessageTypeOffer MessageType = 2 + MessageTypeRequest MessageType = 3 + MessageTypeDecline MessageType = 4 + MessageTypeAck MessageType = 5 + MessageTypeNak MessageType = 6 + MessageTypeRelease MessageType = 7 + MessageTypeInform MessageType = 8 ) // OpcodeType represents a DHCPv4 opcode. |