Skip to content

Commit

Permalink
Support setting non-option RA fields defined in the RFC4861
Browse files Browse the repository at this point in the history
Expose configuration parameters to set RA fields defined in the RFC4861
except options like MTU or Prefix Information.

Signed-off-by: Yutaro Hayakawa <[email protected]>
  • Loading branch information
YutaroHayakawa committed May 11, 2024
1 parent 55f987f commit 50e2184
Show file tree
Hide file tree
Showing 4 changed files with 185 additions and 2 deletions.
35 changes: 35 additions & 0 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,46 @@ type Config struct {
type InterfaceConfig struct {
// Required: Network interface name. Must be unique within the configuration.
Name string `yaml:"name" json:"name" validate:"required"`

// Interval between sending unsolicited RA. Must be >= 70 and <= 1800000. Default is 600000.
// The upper bound is chosen to be compliant with RFC4861. The lower bound is intentionally
// chosen to be lower than RFC4861 for faster convergence. If you don't wish to overwhelm the
// network, and wish to be compliant with RFC4861, set to higher than 3000 as RFC4861 suggests.
RAIntervalMilliseconds int `yaml:"raIntervalMilliseconds" json:"raIntervalMilliseconds" validate:"required,gte=70,lte=1800000" default:"600000"`

// RA header fields

// The default value that should be placed in the Hop Count field of
// the IP header for outgoing IP packets. Must be >= 0 and <= 255.
// Default is 0. If set to zero, it means the reachable time is
// unspecified by this router.
CurrentHopLimit int `yaml:"currentHopLimit" json:"currentHopLimit" validate:"gte=0,lte=255" default:"0"`

// Set M (Managed address configuration) flag. When set, it indicates
// that addresses are available via DHCPv6. Default is false.
Managed bool `yaml:"managed" json:"managed"`

// Set O (Other configuration) flag. When set, it indicates that other
// configuration information is available via DHCPv6. Default is false.
Other bool `yaml:"other" json:"other"`

// The lifetime associated with the default router in seconds. Must be
// >= 0 and <= 65535. Default is 0. The upper bound is chosen to be
// compliant to the RFC8319. If set to zero, the router is not
// considered as a default router.
RouterLifetimeSeconds int `yaml:"routerLifetimeSeconds" json:"routerLifetimeSeconds" validate:"gte=0,lte=65535"`

// The time, in milliseconds, that a node assumes a neighbor is
// reachable after having received a reachability confirmation. Must be
// >= 0 and <= 4294967295. Default is 0. If set to zero, it means the
// reachable time is unspecified by this router.
ReachableTimeMilliseconds int `yaml:"reachableTimeMilliseconds" json:"reachableTimeMilliseconds" validate:"gte=0,lte=4294967295"`

// The time, in milliseconds, between retransmitted Neighbor
// Solicitation messages. Must be >= 0 and <= 4294967295. Default is 0.
// If set to zero, it means the retransmission time is unspecified by
// this router.
RetransmitTimeMilliseconds int `yaml:"retransmitTimeMilliseconds" json:"retransmitTimeMilliseconds" validate:"gte=0,lte=4294967295"`
}

// ValidationErrors is a type alias for the validator.ValidationErrors
Expand Down
120 changes: 120 additions & 0 deletions config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,126 @@ func TestConfigValidation(t *testing.T) {
errorField: "RAIntervalMilliseconds",
errorTag: "lte",
},
{
name: "CurrentHopLimit < 0",
config: &Config{
Interfaces: []*InterfaceConfig{
{
Name: "net0",
RAIntervalMilliseconds: 1000,
CurrentHopLimit: -1,
},
},
},
expectError: true,
errorField: "CurrentHopLimit",
errorTag: "gte",
},
{
name: "CurrentHopLimit > 255",
config: &Config{
Interfaces: []*InterfaceConfig{
{
Name: "net0",
RAIntervalMilliseconds: 1000,
CurrentHopLimit: 256,
},
},
},
expectError: true,
errorField: "CurrentHopLimit",
errorTag: "lte",
},
{
name: "RouterLifetimeSeconds < 0",
config: &Config{
Interfaces: []*InterfaceConfig{
{
Name: "net0",
RAIntervalMilliseconds: 1000,
RouterLifetimeSeconds: -1,
},
},
},
expectError: true,
errorField: "RouterLifetimeSeconds",
errorTag: "gte",
},
{
name: "RouterLifetimeSeconds > 65535",
config: &Config{
Interfaces: []*InterfaceConfig{
{
Name: "net0",
RAIntervalMilliseconds: 1000,
RouterLifetimeSeconds: 65536,
},
},
},
expectError: true,
errorField: "RouterLifetimeSeconds",
errorTag: "lte",
},
{
name: "ReachableTimeMilliseconds < 0",
config: &Config{
Interfaces: []*InterfaceConfig{
{
Name: "net0",
RAIntervalMilliseconds: 1000,
ReachableTimeMilliseconds: -1,
},
},
},
expectError: true,
errorField: "ReachableTimeMilliseconds",
errorTag: "gte",
},
{
name: "ReachableTimeMilliseconds > 4294967295",
config: &Config{
Interfaces: []*InterfaceConfig{
{
Name: "net0",
RAIntervalMilliseconds: 1000,
ReachableTimeMilliseconds: 4294967296,
},
},
},
expectError: true,
errorField: "ReachableTimeMilliseconds",
errorTag: "lte",
},
{
name: "RetransmitTimeMilliseconds < 0",
config: &Config{
Interfaces: []*InterfaceConfig{
{
Name: "net0",
RAIntervalMilliseconds: 1000,
RetransmitTimeMilliseconds: -1,
},
},
},
expectError: true,
errorField: "RetransmitTimeMilliseconds",
errorTag: "gte",
},
{
name: "RetransmitTimeMilliseconds > 4294967295",
config: &Config{
Interfaces: []*InterfaceConfig{
{
Name: "net0",
RAIntervalMilliseconds: 1000,
RetransmitTimeMilliseconds: 4294967296,
},
},
},
expectError: true,
errorField: "RetransmitTimeMilliseconds",
errorTag: "lte",
},
}

for _, tt := range tests {
Expand Down
26 changes: 24 additions & 2 deletions daemon_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,14 @@ func TestDaemonHappyPath(t *testing.T) {
config := &Config{
Interfaces: []*InterfaceConfig{
{
Name: "net0",
RAIntervalMilliseconds: 100,
Name: "net0",
RAIntervalMilliseconds: 100,
CurrentHopLimit: 10,
Managed: true,
Other: true,
RouterLifetimeSeconds: 10,
ReachableTimeMilliseconds: 10000,
RetransmitTimeMilliseconds: 10000,
},
{
Name: "net1",
Expand Down Expand Up @@ -92,6 +98,22 @@ func TestDaemonHappyPath(t *testing.T) {
require.True(t, assertRAInterval(t, sock, time.Millisecond*100))
})

t.Run("Ensure the RA parameter is reflected to the packet", func(t *testing.T) {
sock, err := reg.getSock("net0")
require.NoError(t, err)

// Sampling one RA
ra := <-sock.txMulticastCh()

// Check the parameters
require.Equal(t, uint8(10), ra.msg.CurrentHopLimit)
require.True(t, ra.msg.ManagedConfiguration)
require.True(t, ra.msg.OtherConfiguration)
require.Equal(t, time.Second*10, ra.msg.RouterLifetime)
require.Equal(t, time.Millisecond*10000, ra.msg.ReachableTime)
require.Equal(t, time.Millisecond*10000, ra.msg.RetransmitTimer)
})

t.Run("Ensure the status is running and the result is ordered by name", func(t *testing.T) {
status := d.Status()
require.NoError(t, err)
Expand Down
6 changes: 6 additions & 0 deletions ra_sender.go
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,12 @@ func (s *raSender) run(ctx context.Context) {
reload:
for {
msg := &ndp.RouterAdvertisement{
CurrentHopLimit: uint8(config.CurrentHopLimit),
ManagedConfiguration: config.Managed,
OtherConfiguration: config.Other,
RouterLifetime: time.Duration(config.RouterLifetimeSeconds) * time.Second,
ReachableTime: time.Duration(config.ReachableTimeMilliseconds) * time.Millisecond,
RetransmitTimer: time.Duration(config.RetransmitTimeMilliseconds) * time.Millisecond,
Options: []ndp.Option{
&ndp.LinkLayerAddress{
Direction: ndp.Source,
Expand Down

0 comments on commit 50e2184

Please sign in to comment.