diff --git a/dhcpv4/dhcpv4.go b/dhcpv4/dhcpv4.go index a8754312..993b51d3 100644 --- a/dhcpv4/dhcpv4.go +++ b/dhcpv4/dhcpv4.go @@ -299,7 +299,15 @@ func NewReleaseFromACK(ack *DHCPv4, modifiers ...Modifier) (*DHCPv4, error) { // FromBytes decodes a DHCPv4 packet from a sequence of bytes, and returns an // error if the packet is not valid. +// Octets after End option are checked against the pad option. func FromBytes(q []byte) (*DHCPv4, error) { + return FromBytesWithRelaxedPadding(q, false) +} + +// FromBytesWithRelaxedPadding decodes a DHCPv4 packet from a sequence of bytes, and returns an +// error if the packet is not valid. +// Octets after the End option are checked or not according to pad option. +func FromBytesWithRelaxedPadding(q []byte, relaxedPadding bool) (*DHCPv4, error) { var p DHCPv4 buf := uio.NewBigEndianBuffer(q) @@ -353,7 +361,7 @@ func FromBytes(q []byte) (*DHCPv4, error) { } p.Options = make(Options) - if err := p.Options.fromBytesCheckEnd(buf.Data(), true); err != nil { + if err := p.Options.fromBytesWithRelaxedPadding(buf.Data(), true, relaxedPadding); err != nil { return nil, err } return &p, nil diff --git a/dhcpv4/nclient4/client.go b/dhcpv4/nclient4/client.go index b4e4b567..a818e92b 100644 --- a/dhcpv4/nclient4/client.go +++ b/dhcpv4/nclient4/client.go @@ -143,6 +143,11 @@ type Client struct { // bufferCap is the channel capacity for each TransactionID. bufferCap int + // relaxedPadding is a flag that allows you to relax one of the packet parsing requirements. + // The RFC says that octets after the End option SHOULD be pad options. Not MUST. + // So we need a way to accept packets not padded with zeros. + relaxedPadding bool + // serverAddr is the UDP address to send all packets to. // // This may be an actual broadcast address, or a unicast address. @@ -267,7 +272,7 @@ func (c *Client) receiveLoop() { return } - msg, err := dhcpv4.FromBytes(b[:n]) + msg, err := dhcpv4.FromBytesWithRelaxedPadding(b[:n], c.relaxedPadding) if err != nil { // Not a valid DHCP packet; keep listening. continue @@ -396,6 +401,16 @@ func WithServerAddr(n *net.UDPAddr) ClientOpt { } } +// WithRelaxedPadding is an option that allows you to relax one of the packet parsing requirements. +// The RFC says that octets after the End option SHOULD be pad options. Not MUST. +// So we need a way to accept packets not padded with zeros. +func WithRelaxedPadding() ClientOpt { + return func(c *Client) (err error) { + c.relaxedPadding = true + return + } +} + // Matcher matches DHCP packets. type Matcher func(*dhcpv4.DHCPv4) bool diff --git a/dhcpv4/options.go b/dhcpv4/options.go index 1e5fcbdb..402dcebf 100644 --- a/dhcpv4/options.go +++ b/dhcpv4/options.go @@ -106,7 +106,7 @@ func (o Options) ToBytes() []byte { // // Returns an error if any invalid option or length is found. func (o Options) FromBytes(data []byte) error { - return o.fromBytesCheckEnd(data, false) + return o.fromBytesWithRelaxedPadding(data, false, false) } const ( @@ -115,9 +115,9 @@ const ( optEnd = 255 ) -// FromBytesCheckEnd parses Options from byte sequences using the +// fromBytesWithRelaxedPadding parses Options from byte sequences using the // parsing function that is passed in as a paremeter -func (o Options) fromBytesCheckEnd(data []byte, checkEndOption bool) error { +func (o Options) fromBytesWithRelaxedPadding(data []byte, checkEndOption bool, relaxedPadding bool) error { if len(data) == 0 { return nil } @@ -161,6 +161,10 @@ func (o Options) fromBytesCheckEnd(data []byte, checkEndOption bool) error { return io.ErrUnexpectedEOF } + if relaxedPadding { + return nil + } + // Any bytes left must be padding. var pad uint8 for buf.Len() >= 1 { diff --git a/dhcpv4/options_test.go b/dhcpv4/options_test.go index ff11f888..2730ae01 100644 --- a/dhcpv4/options_test.go +++ b/dhcpv4/options_test.go @@ -227,9 +227,10 @@ func TestOptionsMarshal(t *testing.T) { func TestOptionsUnmarshal(t *testing.T) { for i, tt := range []struct { - input []byte - want Options - wantError bool + input []byte + relaxedPadding bool + want Options + wantError bool }{ { // Buffer missing data. @@ -259,6 +260,12 @@ func TestOptionsUnmarshal(t *testing.T) { input: []byte{byte(OptionEnd), 3}, wantError: true, }, + { + // Option present after the End if relaxedPadding. + input: []byte{byte(OptionEnd), 3}, + relaxedPadding: true, + want: Options{}, + }, { input: []byte{byte(OptionEnd)}, want: Options{}, @@ -306,7 +313,7 @@ func TestOptionsUnmarshal(t *testing.T) { } { t.Run(fmt.Sprintf("Test %02d", i), func(t *testing.T) { opt := make(Options) - err := opt.fromBytesCheckEnd(tt.input, true) + err := opt.fromBytesWithRelaxedPadding(tt.input, true, tt.relaxedPadding) if tt.wantError { require.Error(t, err) } else {