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.

Signed-off-by: Matt Layher <[email protected]>
  • Loading branch information
jmbaur authored and mdlayher committed Mar 12, 2024
1 parent 9cded43 commit 207ae7c
Show file tree
Hide file tree
Showing 9 changed files with 267 additions and 18 deletions.
10 changes: 5 additions & 5 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,19 @@ module github.com/mdlayher/corerad
go 1.21

require (
github.com/google/go-cmp v0.5.9
github.com/google/go-cmp v0.6.0
github.com/jsimonetti/rtnetlink v1.3.3
github.com/mdlayher/metricslite v0.0.0-20220406114248-d75c70dd4887
github.com/mdlayher/ndp v1.0.1
github.com/mdlayher/ndp v1.1.0
github.com/mdlayher/netlink v1.7.2
github.com/mdlayher/schedgroup v1.0.0
github.com/mdlayher/sdnotify v1.0.0
github.com/pelletier/go-toml v1.9.5
github.com/prometheus/client_golang v1.16.0
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1
golang.org/x/net v0.11.0
golang.org/x/net v0.22.0
golang.org/x/sync v0.3.0
golang.org/x/sys v0.9.0
golang.org/x/sys v0.18.0
)

require (
Expand All @@ -28,6 +28,6 @@ require (
github.com/prometheus/client_model v0.4.0 // indirect
github.com/prometheus/common v0.44.0 // indirect
github.com/prometheus/procfs v0.11.0 // indirect
golang.org/x/text v0.10.0 // indirect
golang.org/x/text v0.14.0 // indirect
google.golang.org/protobuf v1.30.0 // indirect
)
20 changes: 10 additions & 10 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -115,8 +115,8 @@ github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
Expand Down Expand Up @@ -158,8 +158,8 @@ 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/ndp v1.1.0 h1:QylGKGVtH60sKZUE88+IW5ila1Z/M9/OXhWdsVKuscs=
github.com/mdlayher/ndp v1.1.0/go.mod h1:FmgESgemgjl38vuOIyAHWUUL6vQKA/pQNkvXdWsdQFM=
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 Expand Up @@ -294,8 +294,8 @@ golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81R
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.11.0 h1:Gi2tvZIJyBtO9SDr1q9h5hEQCp/4L2RQ+ar0qjx2oNU=
golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ=
golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc=
golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
Expand Down Expand Up @@ -353,8 +353,8 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220405210540-1e041c57c461/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s=
golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
Expand All @@ -364,8 +364,8 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.10.0 h1:UpjohKhiEgNc0CSauXmwYftY1+LlaC75SJwh0SgCX58=
golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
Expand Down
6 changes: 6 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,11 @@ type rawRDNSS struct {
Servers []string `toml:"servers"`
}

// A rawPREF64 is the raw configuration file representation of a PREF64 plugin.
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
19 changes: 19 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,21 @@ func parsePlugins(ifi rawInterface, maxInterval time.Duration, epoch time.Time)
plugins = append(plugins, cp)
}

for _, p := range ifi.PREF64 {
base := defaultPREF64Prefix
if p.Prefix != nil && *p.Prefix != "" {
// Use the caller's prefix.
base = *p.Prefix
}

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

plugins = append(plugins, plugin.NewPREF64(prefix, maxInterval))
}

return plugins, nil
}

Expand Down
55 changes: 55 additions & 0 deletions internal/config/plugin_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -736,6 +736,61 @@ func Test_parseRDNSS(t *testing.T) {
}
}

func Test_parsePREF64(t *testing.T) {
t.Parallel()

tests := []struct {
name string
s string
p *plugin.PREF64
ok bool
}{
{
name: "bad prefix",
s: `
[[interfaces]]
[[interfaces.pref64]]
prefix = "xxx"
`,
},
{
name: "OK implicit",
s: `
[[interfaces]]
[[interfaces.pref64]]
`,
p: plugin.NewPREF64(netip.MustParsePrefix(defaultPREF64Prefix), 10*time.Minute),
ok: true,
},
{
name: "OK empty prefix",
s: `
[[interfaces]]
[[interfaces.pref64]]
prefix = ""
`,
p: plugin.NewPREF64(netip.MustParsePrefix(defaultPREF64Prefix), 10*time.Minute),
ok: true,
},
{
name: "OK explicit",
s: `
[[interfaces]]
[[interfaces.pref64]]
prefix = "64:f9b:dead:beef::/96"
`,
p: plugin.NewPREF64(netip.MustParsePrefix("64:f9b:dead:beef::/96"), 10*time.Minute),
ok: true,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
pluginDecode(t, tt.s, tt.ok, tt.p)
})
}
}

func pluginDecode(t *testing.T, s string, ok bool, want plugin.Plugin) {
t.Helper()

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"]

# PREF64: 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
49 changes: 49 additions & 0 deletions internal/plugin/plugin.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,55 @@ func (cp *CaptivePortal) Apply(ra *ndp.RouterAdvertisement) error {
return nil
}

// PREF64 configures a NDP PREF64 option, used to communicate prefixes to
// clients for NAT64.
type PREF64 struct {
Inner *ndp.PREF64
}

// Constant derived from https://datatracker.ietf.org/doc/html/rfc8781#section-4.1.
const maxPref64Lifetime = 8191 * 8 * time.Second

// NewPREF64 computes PREF64 plugin values given a prefix and router
// advertisement max interval value.
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
lifetime := maxPref64Lifetime
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 }

// 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
Loading

0 comments on commit 207ae7c

Please sign in to comment.