Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

internal/*: add PREF64 support #42

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
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
jmbaur marked this conversation as resolved.
Show resolved Hide resolved
# 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
45 changes: 45 additions & 0 deletions internal/plugin/plugin.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,51 @@ 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 {
mdlayher marked this conversation as resolved.
Show resolved Hide resolved
Inner *ndp.PREF64
jmbaur marked this conversation as resolved.
Show resolved Hide resolved
}

const maxPref64Lifetime = 8191 * 8 * time.Second

func NewPREF64(prefix netip.Prefix, maxInterval time.Duration) *PREF64 {
mdlayher marked this conversation as resolved.
Show resolved Hide resolved
// 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
116 changes: 113 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,95 @@ 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 TestNewPREF64(t *testing.T) {
simplePrefix := netip.MustParsePrefix("2001:db8::/96")

tests := []struct {
name string
prefix netip.Prefix
maxInterval time.Duration
want *PREF64
}{
{
name: "zero lifetime",
prefix: simplePrefix,
maxInterval: time.Second * 0,
want: &PREF64{
Inner: &ndp.PREF64{
Prefix: simplePrefix,
Lifetime: time.Second * 0,
},
},
},
{
name: "small lifetime",
prefix: simplePrefix,
maxInterval: time.Minute * 10,
want: &PREF64{
Inner: &ndp.PREF64{
Prefix: simplePrefix,
Lifetime: time.Minute * 10,
},
},
},
{
name: "max lifetime",
prefix: simplePrefix,
maxInterval: time.Second * 8 * 8191,
want: &PREF64{
Inner: &ndp.PREF64{
Prefix: simplePrefix,
Lifetime: time.Second * 8 * 8191,
},
},
},
{
name: "larger than max lifetime",
prefix: simplePrefix,
maxInterval: time.Second * (8*8191 + 1),
want: &PREF64{
Inner: &ndp.PREF64{
Prefix: simplePrefix,
Lifetime: time.Second * 8 * 8191,
},
},
},
{
name: "lifetime not divisible by 8",
prefix: simplePrefix,
maxInterval: time.Second * 9,
want: &PREF64{
Inner: &ndp.PREF64{
Prefix: simplePrefix,
Lifetime: time.Second * 16,
},
},
},
{
name: "non-zero lifetime less than 8",
prefix: simplePrefix,
maxInterval: time.Second * 1,
want: &PREF64{
Inner: &ndp.PREF64{
Prefix: simplePrefix,
Lifetime: time.Second * 8,
},
},
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if diff := cmp.Diff(tt.want, NewPREF64(tt.prefix, tt.maxInterval), cmp.Comparer(prefixEqual)); diff != "" {
t.Fatalf("unexpected pref64 result (-want +got):\n%s", diff)
}
})
}
}

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
Loading