Skip to content

Commit

Permalink
Merge pull request #20 from y-kzm/pref64_support
Browse files Browse the repository at this point in the history
Add pref64 option support
  • Loading branch information
YutaroHayakawa authored Jun 13, 2024
2 parents 5d77035 + 02c9a1e commit e9f1881
Show file tree
Hide file tree
Showing 5 changed files with 219 additions and 0 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
7 changes: 7 additions & 0 deletions advertiser.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down
31 changes: 31 additions & 0 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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())
Expand Down
161 changes: 161 additions & 0 deletions config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
19 changes: 19 additions & 0 deletions daemon_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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) {
Expand Down

0 comments on commit e9f1881

Please sign in to comment.