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 6e88dd9
Show file tree
Hide file tree
Showing 8 changed files with 124 additions and 17 deletions.
10 changes: 6 additions & 4 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ 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
Expand All @@ -13,9 +13,9 @@ require (
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,8 @@ 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
)

replace github.com/mdlayher/ndp => github.com/jmbaur/ndp v0.0.0-20240312135007-dce8f215d01a
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 All @@ -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-20240312135007-dce8f215d01a h1:JvdIAGXWZeRJ/2xWT7HYK/l4m3GkAly3U/G1/iJ28Js=
github.com/jmbaur/ndp v0.0.0-20240312135007-dce8f215d01a/go.mod h1:FmgESgemgjl38vuOIyAHWUUL6vQKA/pQNkvXdWsdQFM=
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 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
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 6e88dd9

Please sign in to comment.