From a0528844abc59ea4e1bceea6bff7a02273b70253 Mon Sep 17 00:00:00 2001 From: Brian Candler Date: Sun, 25 Feb 2024 23:56:18 +0700 Subject: [PATCH] Add helpers for AutoConfigure from RFC 2563 Signed-off-by: Brian Candler --- dhcpv4/dhcpv4.go | 9 +++++ dhcpv4/option_autoconfigure.go | 61 +++++++++++++++++++++++++++++ dhcpv4/option_autoconfigure_test.go | 46 ++++++++++++++++++++++ dhcpv4/options.go | 4 ++ dhcpv4/options_test.go | 5 +++ 5 files changed, 125 insertions(+) create mode 100644 dhcpv4/option_autoconfigure.go create mode 100644 dhcpv4/option_autoconfigure_test.go diff --git a/dhcpv4/dhcpv4.go b/dhcpv4/dhcpv4.go index 6043cd95..c758a54e 100644 --- a/dhcpv4/dhcpv4.go +++ b/dhcpv4/dhcpv4.go @@ -737,6 +737,15 @@ func (d *DHCPv4) MaxMessageSize() (uint16, error) { return GetUint16(OptionMaximumDHCPMessageSize, d.Options) } +// AutoConfigure returns the value of the AutoConfigure option, and a +// boolean indicating if it was present. +// +// The AutoConfigure option is described by RFC 2563, Section 2. +func (d *DHCPv4) AutoConfigure() (AutoConfiguration, bool) { + v, err := GetByte(OptionAutoConfigure, d.Options) + return AutoConfiguration(v), err == nil +} + // MessageType returns the DHCPv4 Message Type option. func (d *DHCPv4) MessageType() MessageType { v := d.Options.Get(OptionDHCPMessageType) diff --git a/dhcpv4/option_autoconfigure.go b/dhcpv4/option_autoconfigure.go new file mode 100644 index 00000000..e2237bfc --- /dev/null +++ b/dhcpv4/option_autoconfigure.go @@ -0,0 +1,61 @@ +package dhcpv4 + +import ( + "fmt" +) + +// AutoConfiguration implements encoding and decoding functions for a +// byte enumeration as used in RFC 2563, Section 2. +type AutoConfiguration byte + +const ( + DoNotAutoConfigure AutoConfiguration = 0 + AutoConfigure AutoConfiguration = 1 +) + +var autoConfigureToString = map[AutoConfiguration]string{ + DoNotAutoConfigure: "DoNotAutoConfigure", + AutoConfigure: "AutoConfigure", +} + +// ToBytes returns a serialized stream of bytes for this option. +func (o AutoConfiguration) ToBytes() []byte { + return []byte{byte(o)} +} + +// String returns a human-readable string for this option. +func (o AutoConfiguration) String() string { + s := autoConfigureToString[o] + if s != "" { + return s + } + return fmt.Sprintf("UNKNOWN (%d)", byte(o)) +} + +// FromBytes parses a a single byte into AutoConfiguration +func (o *AutoConfiguration) FromBytes(data []byte) error { + if len(data) == 1 { + *o = AutoConfiguration(data[0]) + return nil + } + return fmt.Errorf("Invalid buffer length (%d)", len(data)) +} + +// GetByte parses any single-byte option +func GetByte(code OptionCode, o Options) (byte, error) { + data := o.Get(code) + if data == nil { + return 0, fmt.Errorf("option not present") + } + if len(data) != 1 { + return 0, fmt.Errorf("Invalid buffer length (%d)", len(data)) + } + return data[0], nil +} + +// OptAutoConfigure returns a new AutoConfigure option. +// +// The AutoConfigure option is described by RFC 2563, Section 2. +func OptAutoConfigure(autoconf AutoConfiguration) Option { + return Option{Code: OptionAutoConfigure, Value: autoconf} +} diff --git a/dhcpv4/option_autoconfigure_test.go b/dhcpv4/option_autoconfigure_test.go new file mode 100644 index 00000000..55bdde41 --- /dev/null +++ b/dhcpv4/option_autoconfigure_test.go @@ -0,0 +1,46 @@ +package dhcpv4 + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestOptAutoConfigure(t *testing.T) { + o := OptAutoConfigure(0) + require.Equal(t, OptionAutoConfigure, o.Code, "Code") + require.Equal(t, []byte{0}, o.Value.ToBytes(), "ToBytes") + require.Equal(t, "Auto-Configure: DoNotAutoConfigure", o.String()) + + o = OptAutoConfigure(1) + require.Equal(t, OptionAutoConfigure, o.Code, "Code") + require.Equal(t, []byte{1}, o.Value.ToBytes(), "ToBytes") + require.Equal(t, "Auto-Configure: AutoConfigure", o.String()) + + o = OptAutoConfigure(2) + require.Equal(t, OptionAutoConfigure, o.Code, "Code") + require.Equal(t, []byte{2}, o.Value.ToBytes(), "ToBytes") + require.Equal(t, "Auto-Configure: UNKNOWN (2)", o.String()) +} + +func TestGetAutoConfigure(t *testing.T) { + m, _ := New(WithGeneric(OptionAutoConfigure, []byte{1})) + o, ok := m.AutoConfigure() + require.True(t, ok) + require.Equal(t, AutoConfigure, o) + + // Missing + m, _ = New() + _, ok = m.AutoConfigure() + require.False(t, ok, "should get error if option missing") + + // Short byte stream + m, _ = New(WithGeneric(OptionAutoConfigure, []byte{})) + _, ok = m.AutoConfigure() + require.False(t, ok, "should get error from short byte stream") + + // Bad length + m, _ = New(WithGeneric(OptionAutoConfigure, []byte{2, 2})) + _, ok = m.AutoConfigure() + require.False(t, ok, "should get error from bad length") +} diff --git a/dhcpv4/options.go b/dhcpv4/options.go index 9d404b43..7296a5ea 100644 --- a/dhcpv4/options.go +++ b/dhcpv4/options.go @@ -352,6 +352,10 @@ func getOption(code OptionCode, data []byte, vendorDecoder OptionDecoder) fmt.St d = &s } + case OptionAutoConfigure: + var a AutoConfiguration + d = &a + case OptionVendorIdentifyingVendorClass: d = &VIVCIdentifiers{} diff --git a/dhcpv4/options_test.go b/dhcpv4/options_test.go index e918ee3d..331dd1b4 100644 --- a/dhcpv4/options_test.go +++ b/dhcpv4/options_test.go @@ -106,6 +106,11 @@ func TestParseOption(t *testing.T) { value: []byte{1, 2}, want: "258", }, + { + code: OptionAutoConfigure, + value: []byte{0}, + want: "DoNotAutoConfigure", + }, { code: OptionUserClassInformation, value: []byte{4, 't', 'e', 's', 't', 3, 'f', 'o', 'o'},