Skip to content

Commit

Permalink
Implement RFC8106 RDNSS and DNSSL options
Browse files Browse the repository at this point in the history
Support advertising RDNSS and DNSSL options.

Signed-off-by: Yutaro Hayakawa <[email protected]>
  • Loading branch information
YutaroHayakawa committed May 20, 2024
1 parent d4c4062 commit 6cf4ad4
Show file tree
Hide file tree
Showing 4 changed files with 431 additions and 15 deletions.
49 changes: 34 additions & 15 deletions advertiser.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ func newAdvertiser(initialConfig *InterfaceConfig, ctor socketCtor, logger *slog
}

func (s *advertiser) createRAMsg(config *InterfaceConfig) *ndp.RouterAdvertisement {
msg := &ndp.RouterAdvertisement{
return &ndp.RouterAdvertisement{
CurrentHopLimit: uint8(config.CurrentHopLimit),
ManagedConfiguration: config.Managed,
OtherConfiguration: config.Other,
Expand All @@ -62,12 +62,27 @@ func (s *advertiser) createRAMsg(config *InterfaceConfig) *ndp.RouterAdvertiseme
RetransmitTimer: time.Duration(config.RetransmitTimeMilliseconds) * time.Millisecond,
Options: s.createOptions(config),
}
}

func (s *advertiser) createOptions(config *InterfaceConfig) []ndp.Option {
options := []ndp.Option{
&ndp.LinkLayerAddress{
Direction: ndp.Source,
Addr: s.sock.hardwareAddr(),
},
}

if config.MTU > 0 {
options = append(options, &ndp.MTU{
MTU: uint32(config.MTU),
})
}

for _, prefix := range config.Prefixes {
// At this point, we should have validated the
// configuration. If we haven't, it's a bug.
p := netip.MustParsePrefix(prefix.Prefix)
msg.Options = append(msg.Options, &ndp.PrefixInformation{
options = append(options, &ndp.PrefixInformation{
PrefixLength: uint8(p.Bits()),
OnLink: prefix.OnLink,
AutonomousAddressConfiguration: prefix.Autonomous,
Expand All @@ -81,30 +96,34 @@ func (s *advertiser) createRAMsg(config *InterfaceConfig) *ndp.RouterAdvertiseme
// At this point, we should have validated the
// configuration. If we haven't, it's a bug.
p := netip.MustParsePrefix(route.Prefix)
msg.Options = append(msg.Options, &ndp.RouteInformation{
options = append(options, &ndp.RouteInformation{
PrefixLength: uint8(p.Bits()),
Preference: s.toNDPPreference(route.Preference),
RouteLifetime: time.Second * time.Duration(route.LifetimeSeconds),
Prefix: p.Addr(),
})
}

return msg
}

func (s *advertiser) createOptions(config *InterfaceConfig) []ndp.Option {
options := []ndp.Option{
&ndp.LinkLayerAddress{
Direction: ndp.Source,
Addr: s.sock.hardwareAddr(),
},
for _, rdnss := range config.RDNSSes {
addresses := []netip.Addr{}
for _, addr := range rdnss.Addresses {
// At this point, we should have validated the
// configuration. If we haven't, it's a bug.
addresses = append(addresses, netip.MustParseAddr(addr))
}
options = append(options, &ndp.RecursiveDNSServer{
Lifetime: time.Second * time.Duration(rdnss.LifetimeSeconds),
Servers: addresses,
})
}

if config.MTU > 0 {
options = append(options, &ndp.MTU{
MTU: uint32(config.MTU),
for _, dnssl := range config.DNSSLs {
options = append(options, &ndp.DNSSearchList{
Lifetime: time.Second * time.Duration(dnssl.LifetimeSeconds),
DomainNames: dnssl.DomainNames,
})
}

return options
}

Expand Down
36 changes: 36 additions & 0 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"io"
"net/netip"
"os"
"regexp"

"github.com/creasty/defaults"
"github.com/go-playground/validator/v10"
Expand Down Expand Up @@ -88,6 +89,12 @@ type InterfaceConfig struct {
// Route-specific configuration parameters. The prefix fields must not
// be the same each other. The slice itself and elements must not be nil.
Routes []*RouteConfig `yaml:"routes" json:"routes" validate:"unique=Prefix,dive,required" default:"[]"`

// RDNSS-specific configuration parameters.
RDNSSes []*RDNSSConfig `yaml:"rdnsses" json:"rdnsses" validate:"dive,required" default:"[]"`

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

// PrefixConfig represents the prefix-specific configuration parameters
Expand Down Expand Up @@ -131,9 +138,32 @@ type RouteConfig struct {
Preference string `yaml:"preference" json:"preference" validate:"oneof=low medium high" default:"medium"`
}

// RDNSSConfig represents the RDNSS-specific configuration parameters
type RDNSSConfig struct {
// Required: The maximum time in seconds over which these RDNSS
// addresses may be used for name resolution.
LifetimeSeconds int `yaml:"lifetimeSeconds" json:"lifetimeSeconds" validate:"required,gte=0,lte=4294967295"`

// Required: The addresses of the RDNSS servers. You must specify at least one address.
Addresses []string `yaml:"addresses" json:"addresses" validate:"required,unique,min=1,dive,ipv6"`
}

// DNSSLConfig represents the DNSSL-specific configuration parameters
type DNSSLConfig struct {
// Required: The maximum time in seconds over which these DNSSL domain
// names may be used for name resolution.
LifetimeSeconds int `yaml:"lifetimeSeconds" json:"lifetimeSeconds" validate:"required,gte=0,lte=4294967295"`

// Required: The domain names to be used for DNS search list. You must specify at least one domain name.
DomainNames []string `yaml:"domainNames" json:"domainNames" validate:"required,unique,min=1,dive,domain"`
}

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

// Regular expression to validate the domain name in DNSSL configuration
var domainRegexp = regexp.MustCompile(`^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z0-9][a-z0-9-]{0,61}[a-z0-9]$`)

func (c *Config) defaultAndValidate() error {
if err := defaults.Set(c); err != nil {
panic("BUG (Please report 🙏): Defaulting failed: " + err.Error())
Expand Down Expand Up @@ -181,6 +211,12 @@ func (c *Config) defaultAndValidate() error {
return true
})

// Adhoc custom validator which validates the string is a valid domain name.
validate.RegisterValidation("domain", func(fl validator.FieldLevel) bool {
dom := fl.Field().String()
return domainRegexp.Match([]byte(dom))
})

if err := validate.Struct(c); err != nil {
if _, ok := err.(*validator.InvalidValidationError); ok {
panic("BUG (Please report 🙏): Invalid validation: " + err.Error())
Expand Down
Loading

0 comments on commit 6cf4ad4

Please sign in to comment.