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

Add pref64 option support #20

Merged
merged 1 commit into from
Jun 13, 2024
Merged
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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -20,6 +20,7 @@ declarative configuration interface on top of it.
- Prefix discovery with Prefix Information option
- DNS configuration discovery with RDNSS/DNSSL option
- Route advertisement with Route Information option
- NAT64 prefix discovery with PREF64 option

## Installation

7 changes: 7 additions & 0 deletions advertiser.go
Original file line number Diff line number Diff line change
@@ -125,6 +125,13 @@ func (s *advertiser) createOptions(config *InterfaceConfig, deviceState *deviceS
})
}

for _, nat64prefix := range config.NAT64Prefixes {
options = append(options, &ndp.PREF64{
Lifetime: time.Second * time.Duration(*nat64prefix.LifetimeSeconds),
Prefix: netip.MustParsePrefix(nat64prefix.Prefix),
})
}

return options
}

31 changes: 31 additions & 0 deletions config.go
Original file line number Diff line number Diff line change
@@ -95,6 +95,9 @@ type InterfaceConfig struct {

// DNSSL-specific configuration parameters.
DNSSLs []*DNSSLConfig `yaml:"dnssls" json:"dnssls" validate:"dive,required" default:"[]"`

// NAT64 prefix-specific configuration parameters.
NAT64Prefixes []*NAT64PrefixConfig `yaml:"nat64prefixes" json:"nat64prefixes" validate:"dive,required" default:"[]"`
}

// PrefixConfig represents the prefix-specific configuration parameters
@@ -158,6 +161,19 @@ type DNSSLConfig struct {
DomainNames []string `yaml:"domainNames" json:"domainNames" validate:"required,unique,min=1,dive,domain"`
}

// NAT64PrefixConfig represents the NAT64 prefix-specific configuration parameters
type NAT64PrefixConfig struct {
// Required: NAT64 prefix. Must be a valid IPv6 prefix.
// Can only be one of /32, /40, /48, /56, /64, or /96.
Prefix string `yaml:"prefix" json:"prefix" validate:"required,cidrv6,invalid_prefix_len"`

// Required: The valid lifetime of the NAT64 prefix in seconds. Must be >= 0
// and <= 65528. If set to 0, it indicates that the prefix should not be used anymore.
// Should not be shorter than Router Lifetime. This lifetime is encoded
// in units of 8-seconds increments as ScaledLifetime.
LifetimeSeconds *int `yaml:"lifetimeSeconds" json:"lifetimeSeconds" validate:"required,gte=0,lte=65528" default:"65528"`
}

// ValidationErrors is a type alias for the validator.ValidationErrors
type ValidationErrors = validator.ValidationErrors

@@ -217,6 +233,21 @@ func (c *Config) defaultAndValidate() error {
return domainRegexp.Match([]byte(dom))
})

// Adhoc custom validator which validates the prefix length must
// be one of /32, /40, /48, /56, /64, or /96.
validate.RegisterValidation("invalid_prefix_len", func(fl validator.FieldLevel) bool {
p := netip.MustParsePrefix(fl.Field().String())
validPrefixLengths := map[int]bool{
32: true,
40: true,
48: true,
56: true,
64: true,
96: true,
}
return validPrefixLengths[p.Bits()]
})

if err := validate.Struct(c); err != nil {
if _, ok := err.(*validator.InvalidValidationError); ok {
panic("BUG (Please report 🙏): Invalid validation: " + err.Error())
161 changes: 161 additions & 0 deletions config_test.go
Original file line number Diff line number Diff line change
@@ -1039,6 +1039,167 @@ func TestConfigValidation(t *testing.T) {
errorField: "DomainNames[0]",
errorTag: "domain",
},

// NAT64PrefixConfig
{
name: "Nil NAT64PrefixConfig",
config: &Config{
Interfaces: []*InterfaceConfig{
{
Name: "net0",
RAIntervalMilliseconds: 1000,
NAT64Prefixes: nil,
},
},
},
expectError: false,
},
{
name: "Empty NAT64PrefixConfig",
config: &Config{
Interfaces: []*InterfaceConfig{
{
Name: "net0",
RAIntervalMilliseconds: 1000,
NAT64Prefixes: []*NAT64PrefixConfig{},
},
},
},
expectError: false,
},
{
name: "Nil NAT64PrefixConfig Element",
config: &Config{
Interfaces: []*InterfaceConfig{
{
Name: "net0",
RAIntervalMilliseconds: 1000,
NAT64Prefixes: []*NAT64PrefixConfig{nil},
},
},
},
expectError: true,
errorField: "Prefix",
errorTag: "required",
},
{
name: "No NAT64Prefix",
config: &Config{
Interfaces: []*InterfaceConfig{
{
Name: "net0",
RAIntervalMilliseconds: 1000,
NAT64Prefixes: []*NAT64PrefixConfig{
{
LifetimeSeconds: ptr.To(1800),
},
},
},
},
},
expectError: true,
errorField: "Prefix",
errorTag: "required",
},
{
name: "Multiple NAT64PrefixConfig",
config: &Config{
Interfaces: []*InterfaceConfig{
{
Name: "net0",
RAIntervalMilliseconds: 1000,
NAT64Prefixes: []*NAT64PrefixConfig{
{
Prefix: "fc64:ff9b::/96",
LifetimeSeconds: ptr.To(1800),
},
{
Prefix: "fd64:ff9b::/96",
LifetimeSeconds: ptr.To(1800),
},
},
},
},
},
expectError: false,
},
{
name: "Invalid NAT64Prefix length",
config: &Config{
Interfaces: []*InterfaceConfig{
{
Name: "net0",
RAIntervalMilliseconds: 1000,
NAT64Prefixes: []*NAT64PrefixConfig{
{
Prefix: "64:ff9b::/104",
},
},
},
},
},
expectError: true,
errorField: "Prefix",
errorTag: "invalid_prefix_len",
},
{
name: "LifetimeSeconds = 65528",
config: &Config{
Interfaces: []*InterfaceConfig{
{
Name: "net0",
RAIntervalMilliseconds: 1000,
NAT64Prefixes: []*NAT64PrefixConfig{
{
Prefix: "64:ff9b::/96",
LifetimeSeconds: ptr.To(65528),
},
},
},
},
},
expectError: false,
},
{
name: "LifetimeSeconds < 0",
config: &Config{
Interfaces: []*InterfaceConfig{
{
Name: "net0",
RAIntervalMilliseconds: 1000,
NAT64Prefixes: []*NAT64PrefixConfig{
{
Prefix: "64:ff9b::/96",
LifetimeSeconds: ptr.To(-1),
},
},
},
},
},
expectError: true,
errorField: "LifetimeSeconds",
errorTag: "gte",
},
{
name: "LifetimeSeconds > 65528",
config: &Config{
Interfaces: []*InterfaceConfig{
{
Name: "net0",
RAIntervalMilliseconds: 1000,
NAT64Prefixes: []*NAT64PrefixConfig{
{
Prefix: "64:ff9b::/96",
LifetimeSeconds: ptr.To(65529),
},
},
},
},
},
expectError: true,
errorField: "LifetimeSeconds",
errorTag: "lte",
},
}

for _, tt := range tests {
19 changes: 19 additions & 0 deletions daemon_test.go
Original file line number Diff line number Diff line change
@@ -97,6 +97,12 @@ func TestDaemonHappyPath(t *testing.T) {
DomainNames: []string{"example.com", "foo.example.com"},
},
},
NAT64Prefixes: []*NAT64PrefixConfig{
{
Prefix: "64:ff9b::/96",
LifetimeSeconds: ptr.To(1800),
},
},
},
{
Name: "net1",
@@ -250,6 +256,19 @@ func TestDaemonHappyPath(t *testing.T) {
require.NotNil(t, dnsslOptions, "DNSSL option is not advertised")
require.Equal(t, time.Second*400, dnsslOptions.Lifetime)
require.ElementsMatch(t, []string{"example.com", "foo.example.com"}, dnsslOptions.DomainNames)

// Find and check NAT64Prefix options
nat64prefixOptions := map[netip.Prefix]*ndp.PREF64{}
for _, option := range ra.msg.Options {
if opt, ok := option.(*ndp.PREF64); ok {
nat64prefixOptions[opt.Prefix] = opt
}
}
nat64prefix := netip.MustParsePrefix("64:ff9b::/96")
require.Contains(t, nat64prefixOptions, nat64prefix)
nat64prefixInfo := nat64prefixOptions[nat64prefix]
require.Equal(t, int(96), nat64prefixInfo.Prefix.Bits())
require.Equal(t, time.Second*1800, nat64prefixInfo.Lifetime)
})

t.Run("Ensure the status is running and the result is ordered by name", func(t *testing.T) {
Loading