summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorPablo Mazzini <pmazzini@gmail.com>2020-03-09 18:16:37 +0000
committerGitHub <noreply@github.com>2020-03-09 18:16:37 +0000
commitbd34b7c6963c8c124b45759423d41987d428668e (patch)
treeb48d7636ee8a827816653f8c4d6d394d1fe6c3e4
parent200399fb8fcb0ba141e171ed0ce3ce23758b877a (diff)
parent50dff916c60c6eb81be8be8eab2cd396ad6ccd91 (diff)
Merge pull request #361 from mikma/feature/dhcp4-o-dhcp6
dhcpv6: add DHCPv4-over-DHCPv6 support
-rw-r--r--dhcpv6/dhcpv6message.go13
-rw-r--r--dhcpv6/modifiers.go10
-rw-r--r--dhcpv6/modifiers_test.go15
-rw-r--r--dhcpv6/option_dhcpv4_msg.go39
-rw-r--r--dhcpv6/option_dhcpv4_msg_test.go105
-rw-r--r--dhcpv6/option_dhcpv4_o_dhcpv6_server.go46
-rw-r--r--dhcpv6/option_dhcpv4_o_dhcpv6_server_test.go55
-rw-r--r--dhcpv6/options.go4
-rw-r--r--dhcpv6/types.go6
9 files changed, 293 insertions, 0 deletions
diff --git a/dhcpv6/dhcpv6message.go b/dhcpv6/dhcpv6message.go
index 76404f2..3b850fb 100644
--- a/dhcpv6/dhcpv6message.go
+++ b/dhcpv6/dhcpv6message.go
@@ -217,6 +217,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 ce5ce97..ffea9ed 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.