diff options
author | Mikael Magnusson <mikma@users.sourceforge.net> | 2020-03-04 21:43:00 +0100 |
---|---|---|
committer | Mikael Magnusson <mikma@users.sourceforge.net> | 2020-03-06 21:46:38 +0100 |
commit | 19ee83379cf483ab151dd1ad4e36d09483d6a223 (patch) | |
tree | 8f818ae12245130a63aee17c49050062db9aa8db /dhcpv6 | |
parent | 7ea59fc95373dc2c34b2a81c0917618402affe0f (diff) |
dhcpv6: add DHCPv4-over-DHCPv6 support
Add message types, options, and modifier for handling DHCPv4-over-DHCPv6.
Refer to RFC 7341
Signed-off-by: Mikael Magnusson <mikma@users.sourceforge.net>
Diffstat (limited to 'dhcpv6')
-rw-r--r-- | dhcpv6/dhcpv6message.go | 13 | ||||
-rw-r--r-- | dhcpv6/modifiers.go | 10 | ||||
-rw-r--r-- | dhcpv6/modifiers_test.go | 15 | ||||
-rw-r--r-- | dhcpv6/option_dhcpv4_msg.go | 39 | ||||
-rw-r--r-- | dhcpv6/option_dhcpv4_msg_test.go | 105 | ||||
-rw-r--r-- | dhcpv6/option_dhcpv4_o_dhcpv6_server.go | 46 | ||||
-rw-r--r-- | dhcpv6/option_dhcpv4_o_dhcpv6_server_test.go | 55 | ||||
-rw-r--r-- | dhcpv6/options.go | 4 | ||||
-rw-r--r-- | dhcpv6/types.go | 6 |
9 files changed, 293 insertions, 0 deletions
diff --git a/dhcpv6/dhcpv6message.go b/dhcpv6/dhcpv6message.go index 56f8627..2ec3203 100644 --- a/dhcpv6/dhcpv6message.go +++ b/dhcpv6/dhcpv6message.go @@ -193,6 +193,19 @@ func (mo MessageOptions) FQDN() *OptFQDN { return nil } +// DHCP4oDHCP6Server returns the DHCP 4o6 Server Address option as +// defined by RFC 7341. +func (mo MessageOptions) DHCP4oDHCP6Server() *OptDHCP4oDHCP6Server { + opt := mo.Options.GetOne(OptionDHCP4oDHCP6Server) + if opt == nil { + return nil + } + if server, ok := opt.(*OptDHCP4oDHCP6Server); ok { + return server + } + return nil +} + // Message represents a DHCPv6 Message as defined by RFC 3315 Section 6. type Message struct { MessageType MessageType diff --git a/dhcpv6/modifiers.go b/dhcpv6/modifiers.go index 14bfe51..549ec38 100644 --- a/dhcpv6/modifiers.go +++ b/dhcpv6/modifiers.go @@ -119,3 +119,13 @@ func WithRequestedOptions(codes ...OptionCode) Modifier { } } } + +// WithDHCP4oDHCP6Server adds or updates an OptDHCP4oDHCP6Server +func WithDHCP4oDHCP6Server(addrs ...net.IP) Modifier { + return func(d DHCPv6) { + opt := OptDHCP4oDHCP6Server{ + DHCP4oDHCP6Servers: addrs, + } + d.UpdateOption(&opt) + } +} diff --git a/dhcpv6/modifiers_test.go b/dhcpv6/modifiers_test.go index b99d4a2..c240067 100644 --- a/dhcpv6/modifiers_test.go +++ b/dhcpv6/modifiers_test.go @@ -92,3 +92,18 @@ func TestWithFQDN(t *testing.T) { require.Equal(t, uint8(4), ofqdn.Flags) require.Equal(t, "cnos.localhost", ofqdn.DomainName) } + +func TestWithDHCP4oDHCP6Server(t *testing.T) { + var d Message + WithDHCP4oDHCP6Server([]net.IP{ + net.ParseIP("fe80::1"), + net.ParseIP("fe80::2"), + }...)(&d) + require.Equal(t, 1, len(d.Options.Options)) + opt := d.Options.DHCP4oDHCP6Server() + require.Equal(t, OptionDHCP4oDHCP6Server, opt.Code()) + require.Equal(t, 2, len(opt.DHCP4oDHCP6Servers)) + require.Equal(t, net.ParseIP("fe80::1"), opt.DHCP4oDHCP6Servers[0]) + require.Equal(t, net.ParseIP("fe80::2"), opt.DHCP4oDHCP6Servers[1]) + require.NotEqual(t, net.ParseIP("fe80::1"), opt.DHCP4oDHCP6Servers[1]) +} diff --git a/dhcpv6/option_dhcpv4_msg.go b/dhcpv6/option_dhcpv4_msg.go new file mode 100644 index 0000000..0a1a2b3 --- /dev/null +++ b/dhcpv6/option_dhcpv4_msg.go @@ -0,0 +1,39 @@ +package dhcpv6 + +import ( + "fmt" + + "github.com/insomniacslk/dhcp/dhcpv4" +) + +// OptDHCPv4Msg represents a OptionDHCPv4Msg option +// +// This module defines the OptDHCPv4Msg structure. +// https://www.ietf.org/rfc/rfc7341.txt +type OptDHCPv4Msg struct { + Msg *dhcpv4.DHCPv4 +} + +// Code returns the option code +func (op *OptDHCPv4Msg) Code() OptionCode { + return OptionDHCPv4Msg +} + +// ToBytes returns the option serialized to bytes. +func (op *OptDHCPv4Msg) ToBytes() []byte { + return op.Msg.ToBytes() +} + +func (op *OptDHCPv4Msg) String() string { + return fmt.Sprintf("OptDHCPv4Msg{%v}", op.Msg) +} + +// ParseOptDHCPv4Msg builds an OptDHCPv4Msg structure +// from a sequence of bytes. The input data does not include option code and length +// bytes. +func ParseOptDHCPv4Msg(data []byte) (*OptDHCPv4Msg, error) { + var opt OptDHCPv4Msg + var err error + opt.Msg, err = dhcpv4.FromBytes(data) + return &opt, err +} diff --git a/dhcpv6/option_dhcpv4_msg_test.go b/dhcpv6/option_dhcpv4_msg_test.go new file mode 100644 index 0000000..1ffa17a --- /dev/null +++ b/dhcpv6/option_dhcpv4_msg_test.go @@ -0,0 +1,105 @@ +package dhcpv6 + +import ( + "bytes" + "net" + "testing" + + "github.com/insomniacslk/dhcp/dhcpv4" + "github.com/insomniacslk/dhcp/iana" + "github.com/stretchr/testify/require" +) + +var magicCookie = [4]byte{99, 130, 83, 99} + +func TestParseOptDHCPv4Msg(t *testing.T) { + data := []byte{ + 1, // dhcp request + 1, // ethernet hw type + 6, // hw addr length + 3, // hop count + 0xaa, 0xbb, 0xcc, 0xdd, // transaction ID, big endian (network) + 0, 3, // number of seconds + 0, 1, // broadcast + 0, 0, 0, 0, // client IP address + 0, 0, 0, 0, // your IP address + 0, 0, 0, 0, // server IP address + 0, 0, 0, 0, // gateway IP address + 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // client MAC address + padding + } + + // server host name + expectedHostname := []byte{} + for i := 0; i < 64; i++ { + expectedHostname = append(expectedHostname, 0) + } + data = append(data, expectedHostname...) + // boot file name + expectedBootfilename := []byte{} + for i := 0; i < 128; i++ { + expectedBootfilename = append(expectedBootfilename, 0) + } + data = append(data, expectedBootfilename...) + // magic cookie, then no options + data = append(data, magicCookie[:]...) + + opt, err := ParseOptDHCPv4Msg(data) + d := opt.Msg + require.NoError(t, err) + require.Equal(t, d.OpCode, dhcpv4.OpcodeBootRequest) + require.Equal(t, d.HWType, iana.HWTypeEthernet) + require.Equal(t, d.HopCount, byte(3)) + require.Equal(t, d.TransactionID, dhcpv4.TransactionID{0xaa, 0xbb, 0xcc, 0xdd}) + require.Equal(t, d.NumSeconds, uint16(3)) + require.Equal(t, d.Flags, uint16(1)) + require.True(t, d.ClientIPAddr.Equal(net.IPv4zero)) + require.True(t, d.YourIPAddr.Equal(net.IPv4zero)) + require.True(t, d.GatewayIPAddr.Equal(net.IPv4zero)) + require.Equal(t, d.ClientHWAddr, net.HardwareAddr{0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff}) + require.Equal(t, d.ServerHostName, "") + require.Equal(t, d.BootFileName, "") + // no need to check Magic Cookie as it is already validated in FromBytes + // above +} + +func TestOptDHCPv4MsgToBytes(t *testing.T) { + // the following bytes match what dhcpv4.New would create. Keep them in + // sync! + expected := []byte{ + 1, // Opcode BootRequest + 1, // HwType Ethernet + 6, // HwAddrLen + 0, // HopCount + 0x11, 0x22, 0x33, 0x44, // TransactionID + 0, 0, // NumSeconds + 0, 0, // Flags + 0, 0, 0, 0, // ClientIPAddr + 0, 0, 0, 0, // YourIPAddr + 0, 0, 0, 0, // ServerIPAddr + 0, 0, 0, 0, // GatewayIPAddr + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // ClientHwAddr + } + // ServerHostName + expected = append(expected, bytes.Repeat([]byte{0}, 64)...) + // BootFileName + expected = append(expected, bytes.Repeat([]byte{0}, 128)...) + + // Magic Cookie + expected = append(expected, magicCookie[:]...) + + // Minimum message length padding. + // + // 236 + 4 byte cookie + 59 bytes padding + 1 byte end. + expected = append(expected, bytes.Repeat([]byte{0}, 59)...) + + // End + expected = append(expected, 0xff) + + d, err := dhcpv4.New() + require.NoError(t, err) + // fix TransactionID to match the expected one, since it's randomly + // generated in New() + d.TransactionID = dhcpv4.TransactionID{0x11, 0x22, 0x33, 0x44} + opt := OptDHCPv4Msg{Msg: d} + require.Equal(t, expected, opt.ToBytes()) +} diff --git a/dhcpv6/option_dhcpv4_o_dhcpv6_server.go b/dhcpv6/option_dhcpv4_o_dhcpv6_server.go new file mode 100644 index 0000000..a46ecac --- /dev/null +++ b/dhcpv6/option_dhcpv4_o_dhcpv6_server.go @@ -0,0 +1,46 @@ +package dhcpv6 + +import ( + "fmt" + "net" + + "github.com/u-root/u-root/pkg/uio" +) + +// OptDHCP4oDHCP6Server represents a OptionDHCP4oDHCP6Server option +// +// This module defines the OptDHCP4oDHCP6Server structure. +// https://www.ietf.org/rfc/rfc7341.txt +type OptDHCP4oDHCP6Server struct { + DHCP4oDHCP6Servers []net.IP +} + +// Code returns the option code +func (op *OptDHCP4oDHCP6Server) Code() OptionCode { + return OptionDHCP4oDHCP6Server +} + +// ToBytes returns the option serialized to bytes. +func (op *OptDHCP4oDHCP6Server) ToBytes() []byte { + buf := uio.NewBigEndianBuffer(nil) + for _, addr := range op.DHCP4oDHCP6Servers { + buf.WriteBytes(addr.To16()) + } + return buf.Data() +} + +func (op *OptDHCP4oDHCP6Server) String() string { + return fmt.Sprintf("OptDHCP4oDHCP6Server{4o6-servers=%v}", op.DHCP4oDHCP6Servers) +} + +// ParseOptDHCP4oDHCP6Server builds an OptDHCP4oDHCP6Server structure +// from a sequence of bytes. The input data does not include option code and length +// bytes. +func ParseOptDHCP4oDHCP6Server(data []byte) (*OptDHCP4oDHCP6Server, error) { + var opt OptDHCP4oDHCP6Server + buf := uio.NewBigEndianBuffer(data) + for buf.Has(net.IPv6len) { + opt.DHCP4oDHCP6Servers = append(opt.DHCP4oDHCP6Servers, buf.CopyN(net.IPv6len)) + } + return &opt, buf.FinError() +} diff --git a/dhcpv6/option_dhcpv4_o_dhcpv6_server_test.go b/dhcpv6/option_dhcpv4_o_dhcpv6_server_test.go new file mode 100644 index 0000000..de86594 --- /dev/null +++ b/dhcpv6/option_dhcpv4_o_dhcpv6_server_test.go @@ -0,0 +1,55 @@ +package dhcpv6 + +import ( + "net" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestParseOptDHCP4oDHCP6Server(t *testing.T) { + data := []byte{ + 0x2a, 0x03, 0x28, 0x80, 0xff, 0xfe, 0x00, 0x0c, 0xfa, 0xce, 0xb0, 0x0c, 0x00, 0x00, 0x00, 0x35, + } + expected := []net.IP{ + net.IP(data), + } + opt, err := ParseOptDHCP4oDHCP6Server(data) + require.NoError(t, err) + require.Equal(t, expected, opt.DHCP4oDHCP6Servers) + require.Equal(t, OptionDHCP4oDHCP6Server, opt.Code()) + require.Contains(t, opt.String(), "4o6-servers=[2a03:2880:fffe:c:face:b00c:0:35]", "String() should contain the correct DHCP4-over-DHCP6 server output") +} + +func TestOptDHCP4oDHCP6ServerToBytes(t *testing.T) { + ip1 := net.ParseIP("2a03:2880:fffe:c:face:b00c:0:35") + ip2 := net.ParseIP("2001:4860:4860::8888") + servers := []net.IP{ip1, ip2} + expected := append([]byte{}, []byte(ip1)...) + expected = append(expected, []byte(ip2)...) + opt := OptDHCP4oDHCP6Server{DHCP4oDHCP6Servers: servers} + require.Equal(t, expected, opt.ToBytes()) +} + +func TestParseOptDHCP4oDHCP6ServerParseNoAddr(t *testing.T) { + data := []byte{ + } + var expected []net.IP + opt, err := ParseOptDHCP4oDHCP6Server(data) + require.NoError(t, err) + require.Equal(t, expected, opt.DHCP4oDHCP6Servers) +} + +func TestOptDHCP4oDHCP6ServerToBytesNoAddr(t *testing.T) { + expected := []byte(nil) + opt := OptDHCP4oDHCP6Server{} + require.Equal(t, expected, opt.ToBytes()) +} + +func TestParseOptDHCP4oDHCP6ServerParseBogus(t *testing.T) { + data := []byte{ + 0x2a, 0x03, 0x28, 0x80, 0xff, 0xfe, 0x00, 0x0c, // invalid IPv6 address + } + _, err := ParseOptDHCP4oDHCP6Server(data) + require.Error(t, err, "An invalid IPv6 address should return an error") +} diff --git a/dhcpv6/options.go b/dhcpv6/options.go index 9b76f6d..87a33ff 100644 --- a/dhcpv6/options.go +++ b/dhcpv6/options.go @@ -87,6 +87,10 @@ func ParseOption(code OptionCode, optData []byte) (Option, error) { var o OptNetworkInterfaceID err = o.FromBytes(optData) opt = &o + case OptionDHCPv4Msg: + opt, err = ParseOptDHCPv4Msg(optData) + case OptionDHCP4oDHCP6Server: + opt, err = ParseOptDHCP4oDHCP6Server(optData) case Option4RD: opt, err = ParseOpt4RD(optData) case Option4RDMapRule: diff --git a/dhcpv6/types.go b/dhcpv6/types.go index 560581c..7c4052f 100644 --- a/dhcpv6/types.go +++ b/dhcpv6/types.go @@ -36,6 +36,10 @@ const ( MessageTypeLeaseQueryReply MessageType = 15 MessageTypeLeaseQueryDone MessageType = 16 MessageTypeLeaseQueryData MessageType = 17 + _ MessageType = 18 + _ MessageType = 19 + MessageTypeDHCPv4Query MessageType = 20 + MessageTypeDHCPv4Response MessageType = 21 ) // String prints the message type name. @@ -66,6 +70,8 @@ var messageTypeToStringMap = map[MessageType]string{ MessageTypeLeaseQueryReply: "LEASEQUERY-REPLY", MessageTypeLeaseQueryDone: "LEASEQUERY-DONE", MessageTypeLeaseQueryData: "LEASEQUERY-DATA", + MessageTypeDHCPv4Query: "DHCPv4-QUERY", + MessageTypeDHCPv4Response: "DHCPv4-RESPONSE", } // OptionCode is a single byte representing the code for a given Option. |