summaryrefslogtreecommitdiffhomepage
path: root/dhcpv4
diff options
context:
space:
mode:
authorSean Karlage <skarlage@fb.com>2018-03-10 22:47:27 -0800
committerinsomniac <insomniacslk@users.noreply.github.com>2018-03-13 09:13:53 +0000
commit1d020f7d3aba3dfe1c142c5f73d3b3b3992ad2ad (patch)
tree15fbc777cd1643593bc2a16f57adf2886b797144 /dhcpv4
parent7194d354e6b511c0af20bed1147347089da7315e (diff)
Add OptionGeneric, comment out other code so tests compile/pass
Diffstat (limited to 'dhcpv4')
-rw-r--r--dhcpv4/bsdp/bsdp.go116
-rw-r--r--dhcpv4/bsdp/bsdp_test.go111
-rw-r--r--dhcpv4/dhcpv4.go81
-rw-r--r--dhcpv4/dhcpv4_test.go2
-rw-r--r--dhcpv4/option_generic.go43
-rw-r--r--dhcpv4/option_generic_test.go70
-rw-r--r--dhcpv4/options.go102
-rw-r--r--dhcpv4/options_test.go71
-rw-r--r--dhcpv4/types.go16
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.