diff options
-rw-r--r-- | dhcpv4/option_domain_name_server.go | 2 | ||||
-rw-r--r-- | dhcpv4/option_ip_address_lease_time.go | 57 | ||||
-rw-r--r-- | dhcpv4/option_ip_address_lease_time_test.go | 41 | ||||
-rw-r--r-- | dhcpv4/option_maximum_dhcp_message_size.go | 4 | ||||
-rw-r--r-- | dhcpv4/option_maximum_dhcp_message_size_test.go | 2 | ||||
-rw-r--r-- | dhcpv4/option_router.go | 70 | ||||
-rw-r--r-- | dhcpv4/option_router_test.go | 65 | ||||
-rw-r--r-- | dhcpv4/options.go | 8 | ||||
-rw-r--r-- | dhcpv4/options_test.go | 16 |
9 files changed, 251 insertions, 14 deletions
diff --git a/dhcpv4/option_domain_name_server.go b/dhcpv4/option_domain_name_server.go index 78aaf90..470eaa0 100644 --- a/dhcpv4/option_domain_name_server.go +++ b/dhcpv4/option_domain_name_server.go @@ -48,7 +48,7 @@ func (o *OptDomainNameServer) Code() OptionCode { func (o *OptDomainNameServer) ToBytes() []byte { ret := []byte{byte(o.Code()), byte(o.Length())} for _, ns := range o.NameServers { - ret = append(ret, ns...) + ret = append(ret, ns.To4()...) } return ret } diff --git a/dhcpv4/option_ip_address_lease_time.go b/dhcpv4/option_ip_address_lease_time.go new file mode 100644 index 0000000..7562c58 --- /dev/null +++ b/dhcpv4/option_ip_address_lease_time.go @@ -0,0 +1,57 @@ +package dhcpv4 + +import ( + "encoding/binary" + "fmt" +) + +// This option implements the IP Address Lease Time option +// https://tools.ietf.org/html/rfc2132 + +// OptIPAddressLeaseTime represents the IP Address Lease Time option. +type OptIPAddressLeaseTime struct { + LeaseTime uint32 +} + +// ParseOptIPAddressLeaseTime constructs an OptIPAddressLeaseTime struct from a +// sequence of bytes and returns it, or an error. +func ParseOptIPAddressLeaseTime(data []byte) (*OptIPAddressLeaseTime, error) { + // Should at least have code, length, and lease time. + if len(data) < 6 { + return nil, ErrShortByteStream + } + code := OptionCode(data[0]) + if code != OptionIPAddressLeaseTime { + return nil, fmt.Errorf("expected option %v, got %v instead", OptionIPAddressLeaseTime, code) + } + length := int(data[1]) + if length != 4 { + return nil, fmt.Errorf("expected length 4, got %v instead", length) + } + leaseTime := binary.BigEndian.Uint32(data[2:6]) + return &OptIPAddressLeaseTime{LeaseTime: leaseTime}, nil +} + +// Code returns the option code. +func (o *OptIPAddressLeaseTime) Code() OptionCode { + return OptionIPAddressLeaseTime +} + +// ToBytes returns a serialized stream of bytes for this option. +func (o *OptIPAddressLeaseTime) ToBytes() []byte { + serializedTime := make([]byte, 4) + binary.BigEndian.PutUint32(serializedTime, o.LeaseTime) + serializedOpt := []byte{byte(o.Code()), byte(o.Length())} + return append(serializedOpt, serializedTime...) +} + +// String returns a human-readable string for this option. +func (o *OptIPAddressLeaseTime) String() string { + return fmt.Sprintf("IP Addresses Lease Time -> %v", o.LeaseTime) +} + +// Length returns the length of the data portion (excluding option code and byte +// for length, if any). +func (o *OptIPAddressLeaseTime) Length() int { + return 4 +} diff --git a/dhcpv4/option_ip_address_lease_time_test.go b/dhcpv4/option_ip_address_lease_time_test.go new file mode 100644 index 0000000..7d507bf --- /dev/null +++ b/dhcpv4/option_ip_address_lease_time_test.go @@ -0,0 +1,41 @@ +package dhcpv4 + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestOptIPAddressLeaseTimeInterfaceMethods(t *testing.T) { + o := OptIPAddressLeaseTime{LeaseTime: 43200} + require.Equal(t, OptionIPAddressLeaseTime, o.Code(), "Code") + require.Equal(t, 4, o.Length(), "Length") + require.Equal(t, []byte{51, 4, 0, 0, 168, 192}, o.ToBytes(), "ToBytes") +} + +func TestParseOptIPAddressLeaseTime(t *testing.T) { + data := []byte{51, 4, 0, 0, 168, 192} + o, err := ParseOptIPAddressLeaseTime(data) + require.NoError(t, err) + require.Equal(t, &OptIPAddressLeaseTime{LeaseTime: 43200}, o) + + // Short byte stream + data = []byte{51, 4, 168, 192} + _, err = ParseOptIPAddressLeaseTime(data) + require.Error(t, err, "should get error from short byte stream") + + // Wrong code + data = []byte{54, 4, 0, 0, 168, 192} + _, err = ParseOptIPAddressLeaseTime(data) + require.Error(t, err, "should get error from wrong code") + + // Bad length + data = []byte{51, 5, 1, 1, 1, 1, 1} + _, err = ParseOptIPAddressLeaseTime(data) + require.Error(t, err, "should get error from bad length") +} + +func TestOptIPAddressLeaseTimeString(t *testing.T) { + o := OptIPAddressLeaseTime{LeaseTime: 43200} + require.Equal(t, "IP Addresses Lease Time -> 43200", o.String()) +} diff --git a/dhcpv4/option_maximum_dhcp_message_size.go b/dhcpv4/option_maximum_dhcp_message_size.go index 05186f5..e5fedc6 100644 --- a/dhcpv4/option_maximum_dhcp_message_size.go +++ b/dhcpv4/option_maximum_dhcp_message_size.go @@ -8,7 +8,7 @@ import ( // This option implements the Maximum DHCP Message size option // https://tools.ietf.org/html/rfc2132 -// OptMaximumDHCPMessageSize represents the DHCP message type option. +// OptMaximumDHCPMessageSize represents the Maximum DHCP Message size option. type OptMaximumDHCPMessageSize struct { Size uint16 } @@ -16,7 +16,7 @@ type OptMaximumDHCPMessageSize struct { // ParseOptMaximumDHCPMessageSize constructs an OptMaximumDHCPMessageSize struct from a sequence of // bytes and returns it, or an error. func ParseOptMaximumDHCPMessageSize(data []byte) (*OptMaximumDHCPMessageSize, error) { - // Should at least have code, length, and message type. + // Should at least have code, length, and message size. if len(data) < 4 { return nil, ErrShortByteStream } diff --git a/dhcpv4/option_maximum_dhcp_message_size_test.go b/dhcpv4/option_maximum_dhcp_message_size_test.go index 65a26fc..f24b499 100644 --- a/dhcpv4/option_maximum_dhcp_message_size_test.go +++ b/dhcpv4/option_maximum_dhcp_message_size_test.go @@ -14,7 +14,7 @@ func TestOptMaximumDHCPMessageSizeInterfaceMethods(t *testing.T) { } func TestParseOptMaximumDHCPMessageSize(t *testing.T) { - data := []byte{57, 2, 5, 220} // DISCOVER + data := []byte{57, 2, 5, 220} o, err := ParseOptMaximumDHCPMessageSize(data) require.NoError(t, err) require.Equal(t, &OptMaximumDHCPMessageSize{Size: 1500}, o) diff --git a/dhcpv4/option_router.go b/dhcpv4/option_router.go new file mode 100644 index 0000000..b9bdff1 --- /dev/null +++ b/dhcpv4/option_router.go @@ -0,0 +1,70 @@ +package dhcpv4 + +import ( + "fmt" + "net" +) + +// This option implements the router option +// https://tools.ietf.org/html/rfc2132 + +// OptRouter represents an option encapsulating the routers. +type OptRouter struct { + Routers []net.IP +} + +// ParseOptRouter returns a new OptRouter from a byte stream, or error if any. +func ParseOptRouter(data []byte) (*OptRouter, error) { + if len(data) < 2 { + return nil, ErrShortByteStream + } + code := OptionCode(data[0]) + if code != OptionRouter { + return nil, fmt.Errorf("expected code %v, got %v", OptionRouter, code) + } + length := int(data[1]) + if length == 0 || length%4 != 0 { + return nil, fmt.Errorf("Invalid length: expected multiple of 4 larger than 4, got %v", length) + } + if len(data) < 2+length { + return nil, ErrShortByteStream + } + routers := make([]net.IP, 0, length%4) + for idx := 0; idx < length; idx += 4 { + b := data[2+idx : 2+idx+4] + routers = append(routers, net.IPv4(b[0], b[1], b[2], b[3])) + } + return &OptRouter{Routers: routers}, nil +} + +// Code returns the option code. +func (o *OptRouter) Code() OptionCode { + return OptionRouter +} + +// ToBytes returns a serialized stream of bytes for this option. +func (o *OptRouter) ToBytes() []byte { + ret := []byte{byte(o.Code()), byte(o.Length())} + for _, router := range o.Routers { + ret = append(ret, router.To4()...) + } + return ret +} + +// String returns a human-readable string. +func (o *OptRouter) String() string { + var routers string + for idx, router := range o.Routers { + routers += router.String() + if idx < len(o.Routers)-1 { + routers += ", " + } + } + return fmt.Sprintf("Routers -> %v", routers) +} + +// Length returns the length of the data portion (excluding option code an byte +// length). +func (o *OptRouter) Length() int { + return len(o.Routers) * 4 +} diff --git a/dhcpv4/option_router_test.go b/dhcpv4/option_router_test.go new file mode 100644 index 0000000..f492c22 --- /dev/null +++ b/dhcpv4/option_router_test.go @@ -0,0 +1,65 @@ +package dhcpv4 + +import ( + "net" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestOptRoutersInterfaceMethods(t *testing.T) { + routers := []net.IP{ + net.IPv4(192, 168, 0, 10), + net.IPv4(192, 168, 0, 20), + } + o := OptRouter{Routers: routers} + require.Equal(t, OptionRouter, o.Code(), "Code") + require.Equal(t, net.IPv4len*len(routers), o.Length(), "Length") + require.Equal(t, routers, o.Routers, "Routers") +} + +func TestParseOptRouter(t *testing.T) { + data := []byte{ + byte(OptionRouter), + 8, // Length + 192, 168, 0, 10, // Router #1 + 192, 168, 0, 20, // Router #2 + } + o, err := ParseOptRouter(data) + require.NoError(t, err) + routers := []net.IP{ + net.IPv4(192, 168, 0, 10), + net.IPv4(192, 168, 0, 20), + } + require.Equal(t, &OptRouter{Routers: routers}, o) + + // Short byte stream + data = []byte{byte(OptionRouter)} + _, err = ParseOptRouter(data) + require.Error(t, err, "should get error from short byte stream") + + // Wrong code + data = []byte{54, 2, 1, 1} + _, err = ParseOptRouter(data) + require.Error(t, err, "should get error from wrong code") + + // Bad length + data = []byte{byte(OptionRouter), 6, 1, 1, 1} + _, err = ParseOptRouter(data) + require.Error(t, err, "should get error from bad length") +} + +func TestParseOptRouterNoRouters(t *testing.T) { + // RFC2132 requires that at least one Router IP is specified + data := []byte{ + byte(OptionRouter), + 0, // Length + } + _, err := ParseOptRouter(data) + require.Error(t, err) +} + +func TestOptRouterString(t *testing.T) { + o := OptRouter{Routers: []net.IP{net.IPv4(192, 168, 0, 1), net.IPv4(192, 168, 0, 10)}} + require.Equal(t, "Routers -> 192.168.0.1, 192.168.0.10", o.String()) +} diff --git a/dhcpv4/options.go b/dhcpv4/options.go index 198fbb0..760bc98 100644 --- a/dhcpv4/options.go +++ b/dhcpv4/options.go @@ -42,12 +42,16 @@ func ParseOption(data []byte) (Option, error) { switch OptionCode(data[0]) { case OptionSubnetMask: opt, err = ParseOptSubnetMask(data) + case OptionRouter: + opt, err = ParseOptRouter(data) + case OptionRequestedIPAddress: + opt, err = ParseOptRequestedIPAddress(data) + case OptionIPAddressLeaseTime: + opt, err = ParseOptIPAddressLeaseTime(data) case OptionDHCPMessageType: opt, err = ParseOptMessageType(data) case OptionParameterRequestList: opt, err = ParseOptParameterRequestList(data) - case OptionRequestedIPAddress: - opt, err = ParseOptRequestedIPAddress(data) case OptionServerIdentifier: opt, err = ParseOptServerIdentifier(data) case OptionBroadcastAddress: diff --git a/dhcpv4/options_test.go b/dhcpv4/options_test.go index 01d7427..b3d7605 100644 --- a/dhcpv4/options_test.go +++ b/dhcpv4/options_test.go @@ -25,6 +25,14 @@ func TestParseOption(t *testing.T) { 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) + require.NoError(t, err) + require.Equal(t, OptionRequestedIPAddress, opt.Code(), "Code") + require.Equal(t, 4, opt.Length(), "Length") + require.Equal(t, option, opt.ToBytes(), "ToBytes") + // Message type option = []byte{53, 1, 1} opt, err = ParseOption(option) @@ -41,14 +49,6 @@ func TestParseOption(t *testing.T) { require.Equal(t, 3, 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) - require.NoError(t, err) - require.Equal(t, OptionRequestedIPAddress, opt.Code(), "Code") - require.Equal(t, 4, 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) |