Skip to content

Commit

Permalink
internal/*: add PREF64 support
Browse files Browse the repository at this point in the history
Adds initial PREF64 support. Multiple prefixes can be added on an
interface, defaulting to a single prefix of the RFC 6052 reserved value
"64:ff9b::/96" if the config's TOML section is defined but the prefix
value is either unset or empty.
  • Loading branch information
jmbaur committed Mar 12, 2024
1 parent 9cded43 commit 3b87369
Show file tree
Hide file tree
Showing 8 changed files with 112 additions and 5 deletions.
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,5 @@ require (
golang.org/x/text v0.10.0 // indirect
google.golang.org/protobuf v1.30.0 // indirect
)

replace github.com/mdlayher/ndp => github.com/jmbaur/ndp v0.0.0-20240312033014-0641f49d773f
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,8 @@ github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5m
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/jmbaur/ndp v0.0.0-20240312033014-0641f49d773f h1:V233lghTpBeZp+h2pJFJ31tNXTbUZ5FyER5DhEDSC18=
github.com/jmbaur/ndp v0.0.0-20240312033014-0641f49d773f/go.mod h1:rf3wKaWhAYJEXFKpgF8kQ2AxypxVbfNcZbqoAo6fVzk=
github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA=
github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
Expand All @@ -158,8 +160,6 @@ github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zk
github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
github.com/mdlayher/metricslite v0.0.0-20220406114248-d75c70dd4887 h1:bQHlyj7L8//jVylGe6HYuTqfUweTtAqHmhuOLy6C3vs=
github.com/mdlayher/metricslite v0.0.0-20220406114248-d75c70dd4887/go.mod h1:BqYH//q1ULAuVmKB/whePnSt4JCyGBV7bxZU6CjKR1s=
github.com/mdlayher/ndp v1.0.1 h1:+yAD79/BWyFlvAoeG5ncPS0ItlHP/eVbH7bQ6/+LVA4=
github.com/mdlayher/ndp v1.0.1/go.mod h1:rf3wKaWhAYJEXFKpgF8kQ2AxypxVbfNcZbqoAo6fVzk=
github.com/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/g=
github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw=
github.com/mdlayher/schedgroup v1.0.0 h1:MJS37Rkver2jHaRV5WE5xszN56xZt5yQlqNjtBI7Hsc=
Expand Down
5 changes: 5 additions & 0 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ type rawInterface struct {
Routes []rawRoute `toml:"route"`
RDNSS []rawRDNSS `toml:"rdnss"`
DNSSL []rawDNSSL `toml:"dnssl"`
PREF64 []rawPREF64 `toml:"pref64"`
MTU int `toml:"mtu"`
SourceLLA *bool `toml:"source_lla"`
CaptivePortal string `toml:"captive_portal"`
Expand Down Expand Up @@ -117,6 +118,10 @@ type rawRDNSS struct {
Servers []string `toml:"servers"`
}

type rawPREF64 struct {
Prefix *string `toml:"prefix"`
}

// Config specifies the configuration for CoreRAD.
type Config struct {
// User-specified.
Expand Down
4 changes: 4 additions & 0 deletions internal/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,9 @@ func TestParse(t *testing.T) {
lifetime = "auto"
domain_names = ["lan.example.com"]
[[interfaces.pref64]]
prefix = ""
[[interfaces]]
name = "eth1"
min_interval = "auto"
Expand Down Expand Up @@ -244,6 +247,7 @@ func TestParse(t *testing.T) {
},
plugin.NewMTU(1500),
&plugin.LLA{},
plugin.NewPREF64(netip.MustParsePrefix("64:ff9b::/96"), 10 * time.Minute),
},
},
{
Expand Down
22 changes: 22 additions & 0 deletions internal/config/plugin.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ import (
"golang.org/x/exp/slices"
)

// The Well-Known Prefix for IPv4 to IPv6 translation, as specified in RFC
// 6052 Section 2.1.
const defaultPREF64Prefix = "64:ff9b::/96"

// parsePlugin parses raw plugin configuration into a slice of plugins.
func parsePlugins(ifi rawInterface, maxInterval time.Duration, epoch time.Time) ([]plugin.Plugin, error) {
prefixes := make([]*plugin.Prefix, 0, len(ifi.Prefixes))
Expand Down Expand Up @@ -128,6 +132,24 @@ func parsePlugins(ifi rawInterface, maxInterval time.Duration, epoch time.Time)
plugins = append(plugins, cp)
}

fallbackPREF64 := defaultPREF64Prefix
for _, p := range ifi.PREF64 {
if p.Prefix == nil {
p.Prefix = &fallbackPREF64
} else if *p.Prefix == "" {
p.Prefix = &fallbackPREF64
}

prefix, err := netip.ParsePrefix(*p.Prefix)
if err != nil {
return nil, err
}

pref64 := plugin.NewPREF64(prefix, maxInterval)

plugins = append(plugins, pref64)
}

return plugins, nil
}

Expand Down
6 changes: 6 additions & 0 deletions internal/config/reference.toml
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,12 @@ preference = "medium"
lifetime = "auto"
domain_names = ["foo.example.com"]

# Enable PREF64 on the interface using the given prefix. Note that this often
# requires further configuration of the network for NAT64 and/or DNS64. If an
# empty string is given, the default prefix of "64:ff9b::/96" is assumed.
[[interfaces.pref64]]
prefix = "64:ff9b::/96"

# Enable or disable the debug HTTP server for facilities such as Prometheus
# metrics and pprof support.
#
Expand Down
44 changes: 44 additions & 0 deletions internal/plugin/plugin.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,50 @@ func (cp *CaptivePortal) Apply(ra *ndp.RouterAdvertisement) error {
return nil
}

type PREF64 struct {
Inner *ndp.PREF64
}

func NewPREF64(prefix netip.Prefix, maxInterval time.Duration) *PREF64 {
// Calculate the scaled lifetime using MaxRtrAdvInterval.
// See https://datatracker.ietf.org/doc/html/rfc8781#section-4.1-2
maxLifetime := 8191 * 8 * time.Second

lifetime := maxLifetime

if int(maxInterval.Seconds())*3 < int(lifetime.Seconds()) {

lifetimeSeconds := int(maxInterval.Seconds())

if r := int(lifetimeSeconds) % 8; r > 0 {
lifetimeSeconds += 8 - r
}

lifetime = time.Duration(lifetimeSeconds) * time.Second
}

return &PREF64{Inner: &ndp.PREF64{Prefix: prefix, Lifetime: lifetime}}
}

// Name implements Plugin.
func (*PREF64) Name() string { return "pref64" }

// String implements Plugin.
func (p *PREF64) String() string {
return fmt.Sprintf("%s, lifetime: %s", p.Inner.Prefix, p.Inner.Lifetime)
}

// Prepare implements Plugin.
// func (*PREF64) Prepare(_ *net.Interface) error { return nil }
func (*PREF64) Prepare(_ *net.Interface) error { return nil }

// Apply implements Plugin.
func (p *PREF64) Apply(ra *ndp.RouterAdvertisement) error {
ra.Options = append(ra.Options, p.Inner)

return nil
}

// DNSSL configures a NDP DNS Search List option.
type DNSSL struct {
Lifetime time.Duration
Expand Down
30 changes: 27 additions & 3 deletions internal/plugin/plugin_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,16 @@ func TestPluginString(t *testing.T) {
p: UnrestrictedPortal(),
s: `URI: "urn:ietf:params:capport:unrestricted"`,
},
{
name: "PREF64",
p: &PREF64{
&ndp.PREF64{
Lifetime: time.Minute * 10,
Prefix: netip.MustParsePrefix("2001:db8::/96"),
},
},
s: "2001:db8::/96, lifetime: 10m0s",
},
{
name: "DNSSL",
p: &DNSSL{
Expand Down Expand Up @@ -184,6 +194,19 @@ func TestBuild(t *testing.T) {
},
ok: true,
},
{
name: "PREF64",
plugin: NewPREF64(netip.MustParsePrefix("2001:db8::/96"), 24*time.Hour),
ra: &ndp.RouterAdvertisement{
Options: []ndp.Option{
&ndp.PREF64{
Lifetime: 8191 * 8 * time.Second, // max lifetime
Prefix: netip.MustParsePrefix("2001:db8::/96"),
},
},
},
ok: true,
},
{
name: "DNSSL",
plugin: &DNSSL{
Expand Down Expand Up @@ -641,7 +664,7 @@ func TestBuild(t *testing.T) {
return
}

if diff := cmp.Diff(tt.ra, ra, cmp.Comparer(addrEqual)); diff != "" {
if diff := cmp.Diff(tt.ra, ra, cmp.Comparer(addrEqual), cmp.Comparer(prefixEqual)); diff != "" {
t.Fatalf("unexpected RA (-want +got):\n%s", diff)
}
})
Expand Down Expand Up @@ -806,8 +829,9 @@ func Test_betterRDNSS(t *testing.T) {
}
}

func addrEqual(x, y netip.Addr) bool { return x == y }
func ipEqual(x, y system.IP) bool { return x == y }
func addrEqual(x, y netip.Addr) bool { return x == y }
func ipEqual(x, y system.IP) bool { return x == y }
func prefixEqual(x, y netip.Prefix) bool { return x == y }

func mustCaptivePortal(uri string) *ndp.CaptivePortal {
cp, err := ndp.NewCaptivePortal(uri)
Expand Down

0 comments on commit 3b87369

Please sign in to comment.