summaryrefslogtreecommitdiffhomepage
path: root/dhcpv6
diff options
context:
space:
mode:
authorAnatole Denis <natolumin@unverle.fr>2019-11-03 16:46:39 +0100
committerAnatole Denis <natolumin@unverle.fr>2019-11-03 17:00:50 +0100
commit51aead750bd8a5a9fb757f63d4663ba1b4be9bb6 (patch)
tree4b7b0a0e634181a8f058dc77f8218f0cf54bdd22 /dhcpv6
parent80d8a71da7249766be583e8dd64d3cd566ab86c5 (diff)
dhcpv6: Add support for 4RD options
IPv4 Residual Deployment (4RD) is a strategy for providing IPv4 connectivity in IPv6-only networks. The standard includes autoconfiguration via DHCPv6, as described in RFC7600. This adds support for the 3 options defined in that RFC Signed-off-by: Anatole Denis <natolumin@unverle.fr>
Diffstat (limited to 'dhcpv6')
-rw-r--r--dhcpv6/option_4rd.go178
-rw-r--r--dhcpv6/option_4rd_test.go168
-rw-r--r--dhcpv6/options.go6
3 files changed, 352 insertions, 0 deletions
diff --git a/dhcpv6/option_4rd.go b/dhcpv6/option_4rd.go
new file mode 100644
index 0000000..9bac74a
--- /dev/null
+++ b/dhcpv6/option_4rd.go
@@ -0,0 +1,178 @@
+package dhcpv6
+
+import (
+ "fmt"
+ "net"
+
+ "github.com/u-root/u-root/pkg/uio"
+)
+
+// Opt4RD represents a 4RD option. It is only a container for 4RD_*_RULE options
+type Opt4RD Options
+
+// Code returns the Option Code for this option
+func (op *Opt4RD) Code() OptionCode {
+ return Option4RD
+}
+
+// ToBytes serializes this option
+func (op *Opt4RD) ToBytes() []byte {
+ return (*Options)(op).ToBytes()
+}
+
+// String returns a human-readable representation of the option
+func (op *Opt4RD) String() string {
+ return fmt.Sprintf("Opt4RD{%v}", (*Options)(op))
+}
+
+// ParseOpt4RD builds an Opt4RD structure from a sequence of bytes.
+// The input data does not include option code and length bytes
+func ParseOpt4RD(data []byte) (*Opt4RD, error) {
+ var opt Options
+ err := opt.FromBytes(data)
+ return (*Opt4RD)(&opt), err
+}
+
+// Opt4RDMapRule represents a 4RD Mapping Rule option
+// The option is described in https://tools.ietf.org/html/rfc7600#section-4.9
+// The 4RD mapping rules are described in https://tools.ietf.org/html/rfc7600#section-4.2
+type Opt4RDMapRule struct {
+ // Prefix4 is the IPv4 prefix mapped by this rule
+ Prefix4 net.IPNet
+ // Prefix6 is the IPv6 prefix mapped by this rule
+ Prefix6 net.IPNet
+ // EABitsLength is the number of bits of an address used in constructing the mapped address
+ EABitsLength uint8
+ // WKPAuthorized determines if well-known ports are assigned to addresses in an A+P mapping
+ // It can only be set if the length of Prefix4 + EABits > 32
+ WKPAuthorized bool
+}
+
+const (
+ // opt4RDWKPAuthorizedMask is the mask for the WKPAuthorized flag in its
+ // byte in Opt4RDMapRule
+ opt4RDWKPAuthorizedMask = 1 << 7
+ // opt4RDHubAndSpokeMask is the mask for the HubAndSpoke flag in its
+ // byte in Opt4RDNonMapRule
+ opt4RDHubAndSpokeMask = 1 << 7
+ // opt4RDTrafficClassMask is the mask for the TrafficClass flag in its
+ // byte in Opt4RDNonMapRule
+ opt4RDTrafficClassMask = 1 << 0
+)
+
+// Code returns the option code representing this option
+func (op *Opt4RDMapRule) Code() OptionCode { return Option4RDMapRule }
+
+// ToBytes serializes this option
+func (op *Opt4RDMapRule) ToBytes() []byte {
+ buf := uio.NewBigEndianBuffer(nil)
+ p4Len, _ := op.Prefix4.Mask.Size()
+ p6Len, _ := op.Prefix6.Mask.Size()
+ buf.Write8(uint8(p4Len))
+ buf.Write8(uint8(p6Len))
+ buf.Write8(op.EABitsLength)
+ if op.WKPAuthorized {
+ buf.Write8(opt4RDWKPAuthorizedMask)
+ } else {
+ buf.Write8(0)
+ }
+ if op.Prefix4.IP.To4() == nil {
+ // The API prevents us from returning an error here
+ // We just write zeros instead, which is pretty bad behaviour
+ buf.Write32(0)
+ } else {
+ buf.WriteBytes(op.Prefix4.IP.To4())
+ }
+ if op.Prefix6.IP.To16() == nil {
+ buf.Write64(0)
+ buf.Write64(0)
+ } else {
+ buf.WriteBytes(op.Prefix6.IP.To16())
+ }
+ return buf.Data()
+}
+
+// String returns a human-readable description of this option
+func (op *Opt4RDMapRule) String() string {
+ return fmt.Sprintf("Opt4RDMapRule{Prefix4=%s, Prefix6=%s, EA-Bits=%d, WKPAuthorized=%t}",
+ op.Prefix4.String(), op.Prefix6.String(), op.EABitsLength, op.WKPAuthorized)
+}
+
+// ParseOpt4RDMapRule builds an Opt4RDMapRule structure from a sequence of bytes.
+// The input data does not include option code and length bytes.
+func ParseOpt4RDMapRule(data []byte) (*Opt4RDMapRule, error) {
+ var opt Opt4RDMapRule
+ buf := uio.NewBigEndianBuffer(data)
+ opt.Prefix4.Mask = net.CIDRMask(int(buf.Read8()), 32)
+ opt.Prefix6.Mask = net.CIDRMask(int(buf.Read8()), 128)
+ opt.EABitsLength = buf.Read8()
+ opt.WKPAuthorized = (buf.Read8() & opt4RDWKPAuthorizedMask) != 0
+ opt.Prefix4.IP = net.IP(buf.CopyN(net.IPv4len))
+ opt.Prefix6.IP = net.IP(buf.CopyN(net.IPv6len))
+ return &opt, buf.FinError()
+}
+
+// Opt4RDNonMapRule represents 4RD parameters other than mapping rules
+type Opt4RDNonMapRule struct {
+ // HubAndSpoke is whether the network topology is hub-and-spoke or meshed
+ HubAndSpoke bool
+ // TrafficClass is an optional 8-bit tunnel traffic class identifier
+ TrafficClass *uint8
+ // DomainPMTU is the Path MTU for this 4RD domain
+ DomainPMTU uint16
+}
+
+// Code returns the option code for this option
+func (op *Opt4RDNonMapRule) Code() OptionCode {
+ return Option4RDNonMapRule
+}
+
+// ToBytes serializes this option
+func (op *Opt4RDNonMapRule) ToBytes() []byte {
+ buf := uio.NewBigEndianBuffer(nil)
+ var flags uint8
+ var trafficClassValue uint8
+ if op.HubAndSpoke {
+ flags |= opt4RDHubAndSpokeMask
+ }
+ if op.TrafficClass != nil {
+ flags |= opt4RDTrafficClassMask
+ trafficClassValue = *op.TrafficClass
+ }
+
+ buf.Write8(flags)
+ buf.Write8(trafficClassValue)
+ buf.Write16(op.DomainPMTU)
+
+ return buf.Data()
+}
+
+// String returns a human-readable description of this option
+func (op *Opt4RDNonMapRule) String() string {
+ var tClass interface{} = false
+ if op.TrafficClass != nil {
+ tClass = *op.TrafficClass
+ }
+
+ return fmt.Sprintf("Opt4RDNonMapRule{HubAndSpoke=%t, TrafficClass=%v, DomainPMTU=%d}",
+ op.HubAndSpoke, tClass, op.DomainPMTU)
+}
+
+// ParseOpt4RDNonMapRule builds an Opt4RDNonMapRule structure from a sequence of bytes.
+// The input data does not include option code and length bytes
+func ParseOpt4RDNonMapRule(data []byte) (*Opt4RDNonMapRule, error) {
+ var opt Opt4RDNonMapRule
+ buf := uio.NewBigEndianBuffer(data)
+ flags := buf.Read8()
+
+ opt.HubAndSpoke = flags&opt4RDHubAndSpokeMask != 0
+
+ tClass := buf.Read8()
+ if flags&opt4RDTrafficClassMask != 0 {
+ opt.TrafficClass = &tClass
+ }
+
+ opt.DomainPMTU = buf.Read16()
+
+ return &opt, buf.FinError()
+}
diff --git a/dhcpv6/option_4rd_test.go b/dhcpv6/option_4rd_test.go
new file mode 100644
index 0000000..1251b13
--- /dev/null
+++ b/dhcpv6/option_4rd_test.go
@@ -0,0 +1,168 @@
+package dhcpv6
+
+import (
+ "net"
+ "testing"
+
+ "github.com/stretchr/testify/require"
+)
+
+func TestOpt4RDNonMapRuleParse(t *testing.T) {
+ data := []byte{0x81, 0xaa, 0x05, 0xd4}
+ opt, err := ParseOpt4RDNonMapRule(data)
+ require.NoError(t, err)
+ require.True(t, opt.HubAndSpoke)
+ require.NotNil(t, opt.TrafficClass)
+ require.EqualValues(t, 0xaa, *opt.TrafficClass)
+ require.EqualValues(t, 1492, opt.DomainPMTU)
+
+ // Remove the TrafficClass flag and check value is ignored
+ data[0] = 0x80
+ opt, err = ParseOpt4RDNonMapRule(data)
+ require.NoError(t, err)
+ require.True(t, opt.HubAndSpoke)
+ require.Nil(t, opt.TrafficClass)
+ require.EqualValues(t, 1492, opt.DomainPMTU)
+}
+
+func TestOpt4RDNonMapRuleToBytes(t *testing.T) {
+ var tClass uint8 = 0xaa
+ opt := Opt4RDNonMapRule{
+ HubAndSpoke: true,
+ TrafficClass: &tClass,
+ DomainPMTU: 1492,
+ }
+ expected := []byte{0x81, 0xaa, 0x05, 0xd4}
+
+ require.Equal(t, expected, opt.ToBytes())
+
+ // Unsetting TrafficClass should zero the corresponding bytes in the output
+ opt.TrafficClass = nil
+ expected[0], expected[1] = 0x80, 0x00
+
+ require.Equal(t, expected, opt.ToBytes())
+}
+
+func TestOpt4RDNonMapRuleString(t *testing.T) {
+ var tClass uint8 = 120
+ opt := Opt4RDNonMapRule{
+ HubAndSpoke: true,
+ TrafficClass: &tClass,
+ DomainPMTU: 9000,
+ }
+
+ str := opt.String()
+
+ require.Contains(t, str, "HubAndSpoke=true",
+ "String() should contain the HubAndSpoke flag value")
+ require.Contains(t, str, "TrafficClass=120",
+ "String() should contain the TrafficClass flag value")
+ require.Contains(t, str, "DomainPMTU=9000",
+ "String() should contain the domain PMTU")
+}
+
+func TestOpt4RDMapRuleParse(t *testing.T) {
+ ip6addr, ip6net, err := net.ParseCIDR("2001:db8::1234:5678:0:aabb/64")
+ ip6net.IP = ip6addr // We want to keep the entire address however, not apply the mask
+ require.NoError(t, err)
+ ip4addr, ip4net, err := net.ParseCIDR("100.64.0.234/10")
+ ip4net.IP = ip4addr.To4()
+ require.NoError(t, err)
+ data := append([]byte{
+ 10, // IPv4 prefix length
+ 64, // IPv6 prefix length
+ 32, // EA-bits
+ 0x80, // WKPs authorized
+ },
+ append(ip4addr.To4(), ip6addr...)...,
+ )
+
+ opt, err := ParseOpt4RDMapRule(data)
+ require.NoError(t, err)
+ require.EqualValues(t, *ip6net, opt.Prefix6)
+ require.EqualValues(t, *ip4net, opt.Prefix4)
+ require.EqualValues(t, 32, opt.EABitsLength)
+ require.True(t, opt.WKPAuthorized)
+}
+
+func TestOpt4RDMapRuleToBytes(t *testing.T) {
+ opt := Opt4RDMapRule{
+ Prefix4: net.IPNet{
+ IP: net.IPv4(100, 64, 0, 238),
+ Mask: net.CIDRMask(24, 32),
+ },
+ Prefix6: net.IPNet{
+ IP: net.ParseIP("2001:db8::1234:5678:0:aabb"),
+ Mask: net.CIDRMask(80, 128),
+ },
+ EABitsLength: 32,
+ WKPAuthorized: true,
+ }
+
+ expected := append([]byte{
+ 24, // v4 prefix length
+ 80, // v6 prefix length
+ 32, // EA-bits
+ 0x80, // WKPs authorized
+ },
+ append(opt.Prefix4.IP.To4(), opt.Prefix6.IP.To16()...)...,
+ )
+
+ require.Equal(t, expected, opt.ToBytes())
+}
+
+// FIXME: Invalid packets are serialized without error
+
+func TestOpt4RDMapRuleString(t *testing.T) {
+ opt := Opt4RDMapRule{
+ Prefix4: net.IPNet{
+ IP: net.IPv4(100, 64, 0, 238),
+ Mask: net.CIDRMask(24, 32),
+ },
+ Prefix6: net.IPNet{
+ IP: net.ParseIP("2001:db8::1234:5678:0:aabb"),
+ Mask: net.CIDRMask(80, 128),
+ },
+ EABitsLength: 32,
+ WKPAuthorized: true,
+ }
+
+ str := opt.String()
+ require.Contains(t, str, "WKPAuthorized=true", "String() should write the flag values")
+ require.Contains(t, str, "Prefix6=2001:db8::1234:5678:0:aabb/80",
+ "String() should include the IPv6 prefix")
+ require.Contains(t, str, "Prefix4=100.64.0.238/24",
+ "String() should include the IPv4 prefix")
+ require.Contains(t, str, "EA-Bits=32", "String() should include the value for EA-Bits")
+}
+
+// This test round-trip serialization/deserialization of both kinds of 4RD
+// options, and the container option
+func TestOpt4RDRoundTrip(t *testing.T) {
+ var tClass uint8 = 0xaa
+ opt := Opt4RD{
+ &Opt4RDMapRule{
+ Prefix4: net.IPNet{
+ IP: net.IPv4(100, 64, 0, 238).To4(),
+ Mask: net.CIDRMask(24, 32),
+ },
+ Prefix6: net.IPNet{
+ IP: net.ParseIP("2001:db8::1234:5678:0:aabb"),
+ Mask: net.CIDRMask(80, 128),
+ },
+ EABitsLength: 32,
+ WKPAuthorized: true,
+ },
+ &Opt4RDNonMapRule{
+ HubAndSpoke: true,
+ TrafficClass: &tClass,
+ DomainPMTU: 9000,
+ },
+ }
+
+ rtOpt, err := ParseOpt4RD(opt.ToBytes())
+
+ require.NoError(t, err)
+ require.NotNil(t, rtOpt)
+ require.Equal(t, opt, *rtOpt)
+}
diff --git a/dhcpv6/options.go b/dhcpv6/options.go
index 47ccdca..cf192fc 100644
--- a/dhcpv6/options.go
+++ b/dhcpv6/options.go
@@ -79,6 +79,12 @@ func ParseOption(code OptionCode, optData []byte) (Option, error) {
opt, err = ParseOptClientArchType(optData)
case OptionNII:
opt, err = ParseOptNetworkInterfaceId(optData)
+ case Option4RD:
+ opt, err = ParseOpt4RD(optData)
+ case Option4RDMapRule:
+ opt, err = ParseOpt4RDMapRule(optData)
+ case Option4RDNonMapRule:
+ opt, err = ParseOpt4RDNonMapRule(optData)
default:
opt = &OptionGeneric{OptionCode: code, OptionData: optData}
}