diff --git a/cmd/run.go b/cmd/run.go index 51e46d9..8f05915 100644 --- a/cmd/run.go +++ b/cmd/run.go @@ -8,6 +8,8 @@ import ( import _ "net/http/pprof" // remove in stable version of nylon +var opts state.NylonOptions + // runCmd represents the run command var runCmd = &cobra.Command{ Use: "run", @@ -23,7 +25,7 @@ var runCmd = &cobra.Command{ isVerbose = true } - core.Bootstrap(centralPath, nodePath, logPath, isVerbose) + core.Bootstrap(centralPath, nodePath, logPath, isVerbose, opts) }, GroupID: "ny", } @@ -32,13 +34,13 @@ func init() { rootCmd.AddCommand(runCmd) runCmd.Flags().BoolP("verbose", "v", false, "Verbose output") - runCmd.Flags().BoolVarP(&state.DBG_log_probe, "dbg-probe", "p", false, "Write probes to console") - runCmd.Flags().BoolVarP(&state.DBG_log_wireguard, "dbg-wg", "w", false, "Outputs wireguard logs to the console") - runCmd.Flags().BoolVarP(&state.DBG_log_repo_updates, "dbg-repo", "", false, "Outputs repo updates to the console") - runCmd.Flags().BoolVarP(&state.DBG_debug, "dbg-perf", "", false, "Enables performance debugging server on port 6060") - runCmd.Flags().BoolVarP(&state.DBG_trace, "dbg-trace", "", false, "Enables trace to trace.out") - runCmd.Flags().BoolVarP(&state.DBG_trace_tc, "dbg-trace-tc", "", false, "Enables logging of packet routing") - runCmd.Flags().BoolVarP(&state.DBG_log_json, "json", "j", false, "Enables structued json logging") + runCmd.Flags().BoolVarP(&opts.DBG_log_probe, "dbg-probe", "p", false, "Write probes to console") + runCmd.Flags().BoolVarP(&opts.DBG_log_wireguard, "dbg-wg", "w", false, "Outputs wireguard logs to the console") + runCmd.Flags().BoolVarP(&opts.DBG_log_repo_updates, "dbg-repo", "", false, "Outputs repo updates to the console") + runCmd.Flags().BoolVarP(&opts.DBG_debug, "dbg-perf", "", false, "Enables performance debugging server on port 6060") + runCmd.Flags().BoolVarP(&opts.DBG_trace, "dbg-trace", "", false, "Enables trace to trace.out") + runCmd.Flags().BoolVarP(&opts.DBG_trace_tc, "dbg-trace-tc", "", false, "Enables logging of packet routing") + runCmd.Flags().BoolVarP(&opts.DBG_log_json, "json", "j", false, "Enables structued json logging") runCmd.Flags().StringP("config", "c", DefaultConfigPath, "Path to the config file") runCmd.Flags().StringP("node", "n", DefaultNodeConfigPath, "Path to the node config file") runCmd.Flags().StringP("log", "l", "", "Path to the log file (overrides config)") diff --git a/core/entrypoint.go b/core/entrypoint.go index 206872d..e023788 100644 --- a/core/entrypoint.go +++ b/core/entrypoint.go @@ -12,8 +12,8 @@ import ( "github.com/goccy/go-yaml" ) -func setupDebugging() { - if state.DBG_trace { +func setupDebugging(opts state.NylonOptions) { + if opts.DBG_trace { f, err := os.Create("trace.out") if err != nil { log.Fatal(err) @@ -25,7 +25,7 @@ func setupDebugging() { } log.Println("Started tracing") } - if state.DBG_debug { + if opts.DBG_debug { go func() { log.Println(http.ListenAndServe("0.0.0.0:6060", nil)) }() @@ -96,8 +96,8 @@ func readNodeConfig(nodePath string) (*state.LocalCfg, error) { } // Bootstrap provides startup logic in a real environment -func Bootstrap(centralPath, nodePath, logPath string, verbose bool) { - setupDebugging() +func Bootstrap(centralPath, nodePath, logPath string, verbose bool, opts state.NylonOptions) { + setupDebugging(opts) level := slog.LevelInfo if verbose { level = slog.LevelDebug @@ -124,7 +124,7 @@ func Bootstrap(centralPath, nodePath, logPath string, verbose bool) { if err != nil { panic(err) } - n, err := NewNylon(*centralCfg, *nodeCfg, level, centralPath, nil) + n, err := NewNylon(*centralCfg, *nodeCfg, level, centralPath, nil, opts, nil) if err != nil { panic(err) } diff --git a/core/ipc_handler.go b/core/ipc_handler.go index 86a2328..f687b24 100644 --- a/core/ipc_handler.go +++ b/core/ipc_handler.go @@ -129,7 +129,7 @@ func handleStatus(n *Nylon, req *protocol.StatusRequest) *protocol.IpcResponse { PublicKey: keyString(n.LocalCfg.Key.Pubkey()), ListenPort: listenPort, ConfigTimestamp: n.CentralCfg.Timestamp, - TraceEnabled: state.DBG_trace_tc, + TraceEnabled: n.DBG_trace_tc, Advertised: buildAdvertisements(n), Seqnos: buildSeqnos(n), Stats: &protocol.NodeStats{ @@ -470,7 +470,7 @@ func handleIPCReload(n *Nylon, req *protocol.ReloadRequest) *protocol.IpcRespons } func handleTrace(n *Nylon, rw *bufio.ReadWriter) error { - if !state.DBG_trace_tc { + if !n.DBG_trace_tc { if err := writeResponse(rw, errResponse("tracing not enabled; restart with --dbg-trace-tc")); err != nil { return err } diff --git a/core/nylon.go b/core/nylon.go index 4016300..0ca69a7 100644 --- a/core/nylon.go +++ b/core/nylon.go @@ -29,6 +29,10 @@ import ( type Nylon struct { Trace *NylonTrace + // tunables and options + state.RouterTunables + state.NylonOptions + // state state.ConfigState RouterState *state.RouterState @@ -73,13 +77,20 @@ type AppliedSystemState struct { Peers map[state.NodeId]state.NyPublicKey } -func NewNylon(ccfg state.CentralCfg, ncfg state.LocalCfg, logLevel slog.Level, configPath string, aux map[string]any) (*Nylon, error) { +func NewNylon(ccfg state.CentralCfg, ncfg state.LocalCfg, logLevel slog.Level, configPath string, aux map[string]any, opts state.NylonOptions, tunables *state.RouterTunables) (*Nylon, error) { ctx, cancel := context.WithCancelCause(context.Background()) dispatch := make(chan func() error, 128) + var rt state.RouterTunables + if tunables != nil { + rt = *tunables + } else { + rt = state.DefaultRouterTunables() + } + handlers := make([]slog.Handler, 0) - if state.DBG_log_json { + if opts.DBG_log_json { handlers = append(handlers, slog.NewJSONHandler(os.Stderr, &slog.HandlerOptions{ Level: logLevel, @@ -120,7 +131,9 @@ func NewNylon(ccfg state.CentralCfg, ncfg state.LocalCfg, logLevel slog.Level, c } n := &Nylon{ - Trace: &NylonTrace{}, + Trace: &NylonTrace{}, + RouterTunables: rt, + NylonOptions: opts, ConfigState: state.ConfigState{ CentralCfg: ccfg, LocalCfg: ncfg, @@ -172,7 +185,7 @@ func (n *Nylon) Init() error { n.RepeatTask(func() error { return nylonGc(n) - }, state.GcDelay) + }, n.GcDelay) // wireguard configuration err = n.initWireGuard() @@ -183,14 +196,14 @@ func (n *Nylon) Init() error { // endpoint probing n.RepeatTask(func() error { return n.probeLinks(true) - }, state.ProbeDelay) + }, n.ProbeDelay) n.RepeatTask(func() error { // refresh dynamic endpoints for _, neigh := range n.RouterState.Neighbours { for _, ep := range neigh.Eps { if nep, ok := ep.(*state.NylonEndpoint); ok { go func() { - _, err := nep.DynEP.Refresh() + _, err := nep.DynEP.Refresh(n.EndpointResolveExpiry) if err != nil { n.Log.Debug("failed to resolve endpoint", "ep", nep.DynEP.Value, "err", err.Error()) } @@ -199,13 +212,13 @@ func (n *Nylon) Init() error { } } return nil - }, state.EndpointResolveDelay) + }, n.EndpointResolveDelay) n.RepeatTask(func() error { return n.probeLinks(false) - }, state.ProbeRecoveryDelay) + }, n.ProbeRecoveryDelay) n.RepeatTask(func() error { return n.probeNew() - }, state.ProbeDiscoveryDelay) + }, n.ProbeDiscoveryDelay) n.startAdvertisedPrefixHealth() @@ -219,7 +232,7 @@ func (n *Nylon) Init() error { for _, repo := range n.CentralCfg.Dist.Repos { n.Log.Info("config source", "repo", repo) } - n.RepeatTask(func() error { return checkForConfigUpdates(n) }, state.CentralUpdateDelay) + n.RepeatTask(func() error { return checkForConfigUpdates(n) }, n.CentralUpdateDelay) } return nil } diff --git a/core/nylon_apply.go b/core/nylon_apply.go index a501ab2..b5ad501 100644 --- a/core/nylon_apply.go +++ b/core/nylon_apply.go @@ -70,7 +70,7 @@ func (n *Nylon) reconcileRouterState(next *state.CentralCfg) error { continue } // configure existing neighbours - reconcileConfiguredEndpoints(neigh, cfg.Endpoints) + reconcileConfiguredEndpoints(neigh, cfg.Endpoints, &n.RouterTunables) neighs = append(neighs, neigh) delete(desired, neigh.Id) } @@ -89,7 +89,7 @@ func (n *Nylon) reconcileRouterState(next *state.CentralCfg) error { Eps: make([]state.Endpoint, 0, len(cfg.Endpoints)), } for _, ep := range cfg.Endpoints { - stNeigh.Eps = append(stNeigh.Eps, state.NewEndpoint(ep, false, nil)) + stNeigh.Eps = append(stNeigh.Eps, state.NewEndpoint(ep, false, nil, &n.RouterTunables)) } neighs = append(neighs, stNeigh) } @@ -107,7 +107,7 @@ func (n *Nylon) reconcileRouterState(next *state.CentralCfg) error { return nil } -func reconcileConfiguredEndpoints(neigh *state.Neighbour, desired []*state.DynamicEndpoint) { +func reconcileConfiguredEndpoints(neigh *state.Neighbour, desired []*state.DynamicEndpoint, t *state.RouterTunables) { desiredByValue := make(map[string]*state.DynamicEndpoint, len(desired)) for _, ep := range desired { desiredByValue[ep.Value] = ep @@ -131,7 +131,7 @@ func reconcileConfiguredEndpoints(neigh *state.Neighbour, desired []*state.Dynam if _, ok := seen[ep.Value]; ok { continue } - eps = append(eps, state.NewEndpoint(ep, false, nil)) + eps = append(eps, state.NewEndpoint(ep, false, nil, t)) } neigh.Eps = eps } @@ -164,7 +164,7 @@ func (n *Nylon) reconcileAdvertisedPrefixes(next *state.CentralCfg) { for prefix, desired := range desiredLocal { if _, ok := currentLocal[prefix]; !ok { n.Log.Debug("starting prefix healthcheck", "prefix", prefix) - desired.Start(n.Log) + desired.Start(n.Log, &n.RouterTunables) } n.RouterState.Advertised[prefix] = state.Advertisement{ NodeId: n.LocalCfg.Id, @@ -180,7 +180,7 @@ func (n *Nylon) reconcileAdvertisedPrefixes(next *state.CentralCfg) { func (n *Nylon) startAdvertisedPrefixHealth() { for _, ph := range n.GetNode(n.LocalCfg.Id).Prefixes { n.Log.Debug("starting prefix healthcheck", "prefix", ph.GetPrefix()) - ph.Start(n.Log) + ph.Start(n.Log, &n.RouterTunables) } } diff --git a/core/nylon_distribution.go b/core/nylon_distribution.go index 7ca4d5b..82d6acf 100644 --- a/core/nylon_distribution.go +++ b/core/nylon_distribution.go @@ -90,7 +90,7 @@ func checkForConfigUpdates(n *Nylon) error { return err } if config.Timestamp <= currentTimestamp { - if state.DBG_log_repo_updates { + if n.DBG_log_repo_updates { n.Log.Debug(fmt.Sprintf("found old update bundle at %s, skipping", repo)) } return nil diff --git a/core/nylon_endpoints.go b/core/nylon_endpoints.go index 976b2b4..67812ab 100644 --- a/core/nylon_endpoints.go +++ b/core/nylon_endpoints.go @@ -103,7 +103,7 @@ func handleProbePing(n *Nylon, node state.NodeId, wgEndpoint conn.Endpoint) { } dep.Renew() - if state.DBG_log_probe { + if n.DBG_log_probe { n.Log.Debug("probe from", "addr", ap.String()) } return @@ -113,7 +113,7 @@ func handleProbePing(n *Nylon, node state.NodeId, wgEndpoint conn.Endpoint) { // create a new link if we dont have a link for _, neigh := range n.RouterState.Neighbours { if neigh.Id == node { - newEp := state.NewEndpoint(state.NewDynamicEndpoint(wgEndpoint.DstIPPort().String()), true, wgEndpoint) + newEp := state.NewEndpoint(state.NewDynamicEndpoint(wgEndpoint.DstIPPort().String()), true, wgEndpoint, &n.RouterTunables) newEp.Renew() neigh.Eps = append(neigh.Eps, newEp) // push route update to improve convergence time @@ -135,7 +135,7 @@ func handleProbePong(n *Nylon, node state.NodeId, token uint64, ep conn.Endpoint health := linkHealth.Value() latency := time.Since(health.TimeSent) // we have a link - if state.DBG_log_probe { + if n.DBG_log_probe { n.Log.Debug("probe back", "peer", node, "ping", latency) } dpLink.Renew() @@ -194,7 +194,7 @@ func (n *Nylon) probeNew() error { }) if idx == -1 { // add the link to the neighbour - dpl := state.NewEndpoint(ep, false, nil) + dpl := state.NewEndpoint(ep, false, nil, &n.RouterTunables) neigh.Eps = append(neigh.Eps, dpl) err := n.Probe(peer, dpl, false) if err != nil { diff --git a/core/nylon_passive.go b/core/nylon_passive.go index df5f957..7e63029 100644 --- a/core/nylon_passive.go +++ b/core/nylon_passive.go @@ -9,7 +9,7 @@ import ( func (n *Nylon) initPassiveClient() error { n.RepeatTask(func() error { return scanPassivePeers(n) - }, state.ProbeDelay) + }, n.ProbeDelay) return nil } @@ -39,7 +39,7 @@ func scanPassivePeers(n *Nylon) error { // TODO: we could make this expire after a longer period of time, like 24h. However, this would require our passive client to wait for the full route propagation time after 24 hours. (Might cause unexpected interruptions) - recentlyUpdated := time.Since(peer.LastReceivedPacket()) < state.ClientDeadThreshold + recentlyUpdated := time.Since(peer.LastReceivedPacket()) < n.ClientDeadThreshold if n.IsClient(*nid) { // we have a passive client for _, newPrefix := range ncfg.Prefixes { diff --git a/core/nylon_tc.go b/core/nylon_tc.go index 750d06a..cce0816 100644 --- a/core/nylon_tc.go +++ b/core/nylon_tc.go @@ -20,7 +20,7 @@ const ( func (n *Nylon) InstallTC() { t := n.Trace - if state.DBG_trace_tc { + if n.DBG_trace_tc { n.Device.InstallFilter(func(dev *device.Device, packet *device.TCElement) (device.TCAction, error) { if packet.Validate() { // make sure it's an IP packet peer := packet.FromPeer @@ -59,7 +59,7 @@ func (n *Nylon) InstallTC() { return device.TcDrop, nil } packet.ToPeer = entry.Peer - if state.DBG_trace_tc { + if n.DBG_trace_tc { t.Submit(fmt.Sprintf("Fwd packet: %v -> %v, via %s\n", packet.GetSrc(), packet.GetDst(), entry.Nh)) } return device.TcForward, nil @@ -75,7 +75,7 @@ func (n *Nylon) InstallTC() { return device.TcDrop, nil } packet.ToPeer = entry.Peer - if state.DBG_trace_tc { + if n.DBG_trace_tc { t.Submit(fmt.Sprintf("Fwd packet: %v -> %v, via %s\n", packet.GetSrc(), packet.GetDst(), entry.Nh)) } return device.TcForward, nil @@ -93,7 +93,7 @@ func (n *Nylon) InstallTC() { packet.DecrementTTL() } if ttl == 0 { - if state.DBG_trace_tc { + if n.DBG_trace_tc { t.Submit(fmt.Sprintf("TTL Expired: %v -> %v\n", packet.GetSrc(), packet.GetDst())) } return device.TcBounce, nil @@ -110,7 +110,7 @@ func (n *Nylon) InstallTC() { entry, ok := n.router.ExitTable.Load().Lookup(packet.GetDst()) // we should only accept packets destined to us, but not our passive clients if ok && entry.Nh == n.LocalCfg.Id { - if state.DBG_trace_tc { + if n.DBG_trace_tc { t.Submit(fmt.Sprintf("Exit: %v -> %v\n", packet.GetSrc(), packet.GetDst())) } //dev.Log.Verbosef("BounceCur packet: %v -> %v", packet.GetSrc(), packet.GetDst()) diff --git a/core/nylon_wireguard.go b/core/nylon_wireguard.go index 5870d8c..73443bd 100644 --- a/core/nylon_wireguard.go +++ b/core/nylon_wireguard.go @@ -92,7 +92,7 @@ listen_port=%d // init wireguard related tasks n.RepeatTask(func() error { return n.UpdateWireGuard() - }, state.ProbeDelay) + }, n.ProbeDelay) return nil } diff --git a/core/router.go b/core/router.go index 60fa2f9..bee7c44 100644 --- a/core/router.go +++ b/core/router.go @@ -26,7 +26,7 @@ func (n *Nylon) GetNeighIO(neigh state.NodeId) *IOPending { if !ok { nio = &IOPending{ SeqnoReq: make(map[state.Source]state.Pair[uint16, uint8]), - SeqnoDedup: ttlcache.New[state.Source, uint16](ttlcache.WithTTL[state.Source, uint16](state.SeqnoDedupTTL), ttlcache.WithDisableTouchOnHit[state.Source, uint16]()), + SeqnoDedup: ttlcache.New[state.Source, uint16](ttlcache.WithTTL[state.Source, uint16](n.SeqnoDedupTTL), ttlcache.WithDisableTouchOnHit[state.Source, uint16]()), Acks: make(map[netip.Prefix]struct{}), Updates: make(map[netip.Prefix]*protocol.Ny_Update), } @@ -172,12 +172,13 @@ func (n *Nylon) InitRouter() error { n.router.ForwardTable.Store(new(bart.Table[RouteTableEntry]{})) n.router.ExitTable.Store(new(bart.Table[RouteTableEntry]{})) n.RouterState = &state.RouterState{ - Id: n.LocalCfg.Id, - SelfSeqno: make(map[netip.Prefix]uint16), - Routes: make(map[netip.Prefix]state.SelRoute), - Sources: make(map[state.Source]state.FD), - Neighbours: make([]*state.Neighbour, 0), - Advertised: make(map[netip.Prefix]state.Advertisement), + RouterTunables: &n.RouterTunables, + Id: n.LocalCfg.Id, + SelfSeqno: make(map[netip.Prefix]uint16), + Routes: make(map[netip.Prefix]state.SelRoute), + Sources: make(map[state.Source]state.FD), + Neighbours: make([]*state.Neighbour, 0), + Advertised: make(map[netip.Prefix]state.Advertisement), } maxTime := time.Unix(1<<63-62135596801, 999999999) for _, prefix := range n.GetRouter(n.LocalCfg.Id).Prefixes { @@ -194,15 +195,15 @@ func (n *Nylon) InitRouter() error { n.RepeatTask(func() error { FullTableUpdate(n.RouterState, n) return nil - }, state.RouteUpdateDelay) + }, n.RouteUpdateDelay) n.RepeatTask(func() error { SolveStarvation(n.RouterState, n) return nil - }, state.StarvationDelay) + }, n.StarvationDelay) n.RepeatTask(func() error { return n.flushIO() - }, state.NeighbourIOFlushDelay) + }, n.NeighbourIOFlushDelay) return nil } @@ -250,7 +251,7 @@ func (n *Nylon) updatePassiveClient(prefix state.PrefixHealthWrapper, node state // passive nodes may only have static prefixes, so we don't call prefix.Start() n.RouterState.Advertised[prefix.GetPrefix()] = state.Advertisement{ NodeId: node, - Expiry: time.Now().Add(state.ClientKeepaliveInterval), + Expiry: time.Now().Add(n.ClientKeepaliveInterval), IsPassiveHold: passiveHold, MetricFn: prefix.GetMetric, ExpiryFn: func() { @@ -382,7 +383,7 @@ func (n *Nylon) flushIO() error { HopCount: uint32(nio.SeqnoReq[seqR].V2), }, }} - if tLength+proto.Size(req) >= state.SafeMTU { + if tLength+proto.Size(req) >= n.SafeMTU { goto send } delete(nio.SeqnoReq, seqR) @@ -394,7 +395,7 @@ func (n *Nylon) flushIO() error { req := &protocol.Ny{Type: &protocol.Ny_RouteOp{ RouteOp: update, }} - if tLength+proto.Size(req) >= state.SafeMTU { + if tLength+proto.Size(req) >= n.SafeMTU { goto send } delete(nio.Updates, id) diff --git a/core/router_algo.go b/core/router_algo.go index f57973f..52453bb 100644 --- a/core/router_algo.go +++ b/core/router_algo.go @@ -118,8 +118,8 @@ func RunGC(s *state.RouterState, r Router) { } else { // route expired, set metric to INF route.Metric = state.INF - route.ExpireAt = time.Now().Add(state.RouteExpiryTime) // reset expiry time - neigh.Routes[prefix] = route // update the route + route.ExpireAt = time.Now().Add(s.RouteExpiryTime) // reset expiry time + neigh.Routes[prefix] = route // update the route r.RouterEvent(log.EventRouteExpired, "expired and marked", "neigh", neigh.Id, "prefix", prefix) } } @@ -317,7 +317,7 @@ func HandleNeighbourUpdate(s *state.RouterState, r Router, neighId state.NodeId, // create the route n.Routes[adv.Prefix] = state.NeighRoute{ PubRoute: adv, - ExpireAt: time.Now().Add(state.RouteExpiryTime), + ExpireAt: time.Now().Add(s.RouteExpiryTime), } } else { // If such an entry exists: @@ -336,7 +336,7 @@ func HandleNeighbourUpdate(s *state.RouterState, r Router, neighId state.NodeId, } bestEp := n.BestEndpoint() if bestEp != nil { - dummy.Metric = AddMetric(dummy.Metric, AddMetric(bestEp.Metric(), state.HopCost)) + dummy.Metric = AddMetric(dummy.Metric, AddMetric(bestEp.Metric(), s.HopCost)) } else { dummy.Metric = state.INF } @@ -348,7 +348,7 @@ func HandleNeighbourUpdate(s *state.RouterState, r Router, neighId state.NodeId, // receives an unfeasible update for a route that is currently selected. // The requested sequence number is computed from the source table as in // Section 3.8.2.1. - r.RequestSeqno(neighId, adv.Source, s.Sources[adv.Source].Seqno+1, state.SeqnoRequestHopCount) + r.RequestSeqno(neighId, adv.Source, s.Sources[adv.Source].Seqno+1, s.SeqnoRequestHopCount) return // ignore the unfeasible update, we are conservative with retractions here } else if isMoreOptimal { // Additionally, since metric computation does not necessarily coincide @@ -357,7 +357,7 @@ func HandleNeighbourUpdate(s *state.RouterState, r Router, neighId state.NodeId, // lead to the received route becoming selected were it feasible. In that // case, the node SHOULD send a unicast seqno request to the neighbour // that advertised the preferable update. - r.RequestSeqno(neighId, adv.Source, s.Sources[adv.Source].Seqno+1, state.SeqnoRequestHopCount) + r.RequestSeqno(neighId, adv.Source, s.Sources[adv.Source].Seqno+1, s.SeqnoRequestHopCount) } } @@ -375,7 +375,7 @@ func HandleNeighbourUpdate(s *state.RouterState, r Router, neighId state.NodeId, nr.PubRoute = adv if adv.Metric != state.INF { - nr.ExpireAt = time.Now().Add(state.RouteExpiryTime) + nr.ExpireAt = time.Now().Add(s.RouteExpiryTime) } n.Routes[adv.Prefix] = nr } @@ -465,7 +465,7 @@ func ComputeRoutes(s *state.RouterState, r Router) { }, }, Nh: adv.NodeId, // next hop is self or directly connected client - ExpireAt: slices.MinFunc([]time.Time{time.Now().Add(state.RouteExpiryTime), adv.Expiry}, time.Time.Compare), + ExpireAt: slices.MinFunc([]time.Time{time.Now().Add(s.RouteExpiryTime), adv.Expiry}, time.Time.Compare), } } @@ -504,7 +504,7 @@ func ComputeRoutes(s *state.RouterState, r Router) { if bestEp != nil { CAB = bestEp.Metric() - CAB = AddMetric(CAB, state.HopCost) // to prevent 0 cost metric + CAB = AddMetric(CAB, s.HopCost) // to prevent 0 cost metric } // enumerate through neighbour advertisements @@ -584,7 +584,7 @@ func ComputeRoutes(s *state.RouterState, r Router) { if !exists || oldRoute.Source.NodeId != newRoute.Source.NodeId || oldRoute.FD.Seqno != newRoute.FD.Seqno || - abs(int(newRoute.Metric)-int(oldRoute.Metric)) > int(state.LargeChangeThreshold) && newRoute.Metric != state.INF { + abs(int(newRoute.Metric)-int(oldRoute.Metric)) > int(s.LargeChangeThreshold) && newRoute.Metric != state.INF { // criteria met, send update updateFeasibility(s, newRoute.PubRoute) r.RouterEvent(log.EventMajorRouteChange, "major change", "prefix", prefix, "old", oldRoute, "new", newRoute) @@ -658,7 +658,7 @@ func SolveStarvation(router *state.RouterState, r Router) { for src, feasible := range isFeasible { if !feasible && src.NodeId != router.Id { - r.BroadcastRequestSeqno(src, router.Sources[src].Seqno+1, state.SeqnoRequestHopCount) + r.BroadcastRequestSeqno(src, router.Sources[src].Seqno+1, router.SeqnoRequestHopCount) r.RouterEvent(log.EventSeqnoRequested, "requested seqno", "src", src, "seqno", router.Sources[src].Seqno+1) } } diff --git a/core/router_harness.go b/core/router_harness.go index 1108571..23c32cf 100644 --- a/core/router_harness.go +++ b/core/router_harness.go @@ -16,9 +16,11 @@ import ( "github.com/stretchr/testify/assert" ) -func ConfigureConstants() { - state.HopCost = 0 - state.RouteExpiryTime = 10 * time.Hour +func ConfigureConstants() *state.RouterTunables { + t := state.DefaultRouterTunables() + t.HopCost = 0 + t.RouteExpiryTime = 10 * time.Hour + return &t } type MockEndpoint struct { diff --git a/core/router_test.go b/core/router_test.go index 8ba61b1..dfd4ccc 100644 --- a/core/router_test.go +++ b/core/router_test.go @@ -28,15 +28,17 @@ func nodeToPrefix(nodeId string) netip.Prefix { } func TestRouterBasicComputeRoutes(t *testing.T) { + tunables := ConfigureConstants() h := &RouterHarness{} aPrefix := nodeToPrefix("a") rs := state.RouterState{ - Id: "a", - SelfSeqno: make(map[netip.Prefix]uint16), - Routes: make(map[netip.Prefix]state.SelRoute), - Sources: make(map[state.Source]state.FD), - Neighbours: MakeNeighbours("b", "c", "d"), - Advertised: map[netip.Prefix]state.Advertisement{aPrefix: {NodeId: state.NodeId("a"), Expiry: maxTime}}, + RouterTunables: tunables, + Id: "a", + SelfSeqno: make(map[netip.Prefix]uint16), + Routes: make(map[netip.Prefix]state.SelRoute), + Sources: make(map[state.Source]state.FD), + Neighbours: MakeNeighbours("b", "c", "d"), + Advertised: map[netip.Prefix]state.Advertisement{aPrefix: {NodeId: state.NodeId("a"), Expiry: maxTime}}, } ComputeRoutes(&rs, h) // we should have only routes to ourselves @@ -51,7 +53,7 @@ func TestRouterBasicComputeRoutes(t *testing.T) { } func TestRouterNet1A_BasicRetraction(t *testing.T) { - ConfigureConstants() + tunables := ConfigureConstants() // This test is for the following network with our router being A: // B // 1 /| @@ -64,12 +66,13 @@ func TestRouterNet1A_BasicRetraction(t *testing.T) { h := &RouterHarness{} aPrefix := nodeToPrefix("A") rs := &state.RouterState{ - Id: "A", - SelfSeqno: make(map[netip.Prefix]uint16), - Routes: make(map[netip.Prefix]state.SelRoute), - Sources: make(map[state.Source]state.FD), - Neighbours: MakeNeighbours("S", "B", "C"), - Advertised: map[netip.Prefix]state.Advertisement{aPrefix: {NodeId: state.NodeId("A"), Expiry: maxTime}}, + RouterTunables: tunables, + Id: "A", + SelfSeqno: make(map[netip.Prefix]uint16), + Routes: make(map[netip.Prefix]state.SelRoute), + Sources: make(map[state.Source]state.FD), + Neighbours: MakeNeighbours("S", "B", "C"), + Advertised: map[netip.Prefix]state.Advertisement{aPrefix: {NodeId: state.NodeId("A"), Expiry: maxTime}}, } sr := AddLink(rs, NewMockEndpoint("S", 1)) @@ -147,7 +150,7 @@ func TestRouterNet1A_BasicRetraction(t *testing.T) { } func TestRouterNet2S_SolveStarvation(t *testing.T) { - ConfigureConstants() + tunables := ConfigureConstants() // This test is for the following network with our router being S: // A // 1 /| D(A) = 1 @@ -159,12 +162,13 @@ func TestRouterNet2S_SolveStarvation(t *testing.T) { h := &RouterHarness{} rs := &state.RouterState{ - Id: "S", - SelfSeqno: make(map[netip.Prefix]uint16), - Routes: make(map[netip.Prefix]state.SelRoute), - Sources: make(map[state.Source]state.FD), - Neighbours: MakeNeighbours("A", "B"), - Advertised: map[netip.Prefix]state.Advertisement{nodeToPrefix("S"): {NodeId: state.NodeId("S"), Expiry: maxTime}}, + RouterTunables: tunables, + Id: "S", + SelfSeqno: make(map[netip.Prefix]uint16), + Routes: make(map[netip.Prefix]state.SelRoute), + Sources: make(map[state.Source]state.FD), + Neighbours: MakeNeighbours("A", "B"), + Advertised: map[netip.Prefix]state.Advertisement{nodeToPrefix("S"): {NodeId: state.NodeId("S"), Expiry: maxTime}}, } AS := AddLink(rs, NewMockEndpoint("A", 1)) @@ -248,7 +252,7 @@ func TestRouterNet2S_SolveStarvation(t *testing.T) { } func TestRouterNet3A_HandleRetraction(t *testing.T) { - ConfigureConstants() + tunables := ConfigureConstants() // This test is for the following network with our router being A: // 2 // B ---- D @@ -261,12 +265,13 @@ func TestRouterNet3A_HandleRetraction(t *testing.T) { h := &RouterHarness{} rs := &state.RouterState{ - Id: "A", - SelfSeqno: make(map[netip.Prefix]uint16), - Routes: make(map[netip.Prefix]state.SelRoute), - Sources: make(map[state.Source]state.FD), - Neighbours: MakeNeighbours("B", "C"), - Advertised: map[netip.Prefix]state.Advertisement{nodeToPrefix("A"): {NodeId: state.NodeId("A"), Expiry: maxTime}}, + RouterTunables: tunables, + Id: "A", + SelfSeqno: make(map[netip.Prefix]uint16), + Routes: make(map[netip.Prefix]state.SelRoute), + Sources: make(map[state.Source]state.FD), + Neighbours: MakeNeighbours("B", "C"), + Advertised: map[netip.Prefix]state.Advertisement{nodeToPrefix("A"): {NodeId: state.NodeId("A"), Expiry: maxTime}}, } _ = AddLink(rs, NewMockEndpoint("B", 1)) @@ -326,7 +331,7 @@ func TestRouterNet3A_HandleRetraction(t *testing.T) { } func TestRouterNet4A_OverlappingServiceHoldLoop(t *testing.T) { - ConfigureConstants() + tunables := ConfigureConstants() // This test is for the following network with our router being A: // Note, X is a service that both S and D advertise @@ -337,12 +342,13 @@ func TestRouterNet4A_OverlappingServiceHoldLoop(t *testing.T) { h := &RouterHarness{} rs := &state.RouterState{ - Id: "A", - SelfSeqno: make(map[netip.Prefix]uint16), - Routes: make(map[netip.Prefix]state.SelRoute), - Sources: make(map[state.Source]state.FD), - Neighbours: MakeNeighbours("S", "B", "C"), - Advertised: map[netip.Prefix]state.Advertisement{nodeToPrefix("A"): {NodeId: state.NodeId("A"), Expiry: maxTime}}, + RouterTunables: tunables, + Id: "A", + SelfSeqno: make(map[netip.Prefix]uint16), + Routes: make(map[netip.Prefix]state.SelRoute), + Sources: make(map[state.Source]state.FD), + Neighbours: MakeNeighbours("S", "B", "C"), + Advertised: map[netip.Prefix]state.Advertisement{nodeToPrefix("A"): {NodeId: state.NodeId("A"), Expiry: maxTime}}, } SA := AddLink(rs, NewMockEndpoint("S", 1)) @@ -410,7 +416,7 @@ func TestRouterNet4A_OverlappingServiceHoldLoop(t *testing.T) { } func TestRouterNet4A_OverlappingServiceMetricIncrease(t *testing.T) { - ConfigureConstants() + tunables := ConfigureConstants() // This test is for the following network with our router being A: // Note, X is a service that both S and D advertise @@ -421,12 +427,13 @@ func TestRouterNet4A_OverlappingServiceMetricIncrease(t *testing.T) { h := &RouterHarness{} rs := &state.RouterState{ - Id: "A", - SelfSeqno: make(map[netip.Prefix]uint16), - Routes: make(map[netip.Prefix]state.SelRoute), - Sources: make(map[state.Source]state.FD), - Neighbours: MakeNeighbours("S", "B", "C"), - Advertised: map[netip.Prefix]state.Advertisement{nodeToPrefix("A"): {NodeId: state.NodeId("A"), Expiry: maxTime}}, + RouterTunables: tunables, + Id: "A", + SelfSeqno: make(map[netip.Prefix]uint16), + Routes: make(map[netip.Prefix]state.SelRoute), + Sources: make(map[state.Source]state.FD), + Neighbours: MakeNeighbours("S", "B", "C"), + Advertised: map[netip.Prefix]state.Advertisement{nodeToPrefix("A"): {NodeId: state.NodeId("A"), Expiry: maxTime}}, } SA := AddLink(rs, NewMockEndpoint("S", 1)) @@ -506,7 +513,7 @@ func TestRouterNet4A_OverlappingServiceMetricIncrease(t *testing.T) { } func TestRouter_SeqnoRequestNotForwardedBackToRequester(t *testing.T) { - ConfigureConstants() + tunables := ConfigureConstants() // This test is for the following network with our router being A: // A has a stale selected route to S through requester B. In A's neighbour // table, B is still the only feasible route for S, while C has an older @@ -527,12 +534,13 @@ func TestRouter_SeqnoRequestNotForwardedBackToRequester(t *testing.T) { h := &RouterHarness{} sPrefix := nodeToPrefix("S") rs := &state.RouterState{ - Id: "A", - SelfSeqno: make(map[netip.Prefix]uint16), - Routes: make(map[netip.Prefix]state.SelRoute), - Sources: make(map[state.Source]state.FD), - Neighbours: MakeNeighbours("B", "C"), - Advertised: map[netip.Prefix]state.Advertisement{nodeToPrefix("A"): {NodeId: state.NodeId("A"), Expiry: maxTime}}, + RouterTunables: tunables, + Id: "A", + SelfSeqno: make(map[netip.Prefix]uint16), + Routes: make(map[netip.Prefix]state.SelRoute), + Sources: make(map[state.Source]state.FD), + Neighbours: MakeNeighbours("B", "C"), + Advertised: map[netip.Prefix]state.Advertisement{nodeToPrefix("A"): {NodeId: state.NodeId("A"), Expiry: maxTime}}, } _ = AddLink(rs, NewMockEndpoint("B", 1)) @@ -552,7 +560,7 @@ func TestRouter_SeqnoRequestNotForwardedBackToRequester(t *testing.T) { } func TestRouterNet5A_SelectedUnfeasibleUpdate(t *testing.T) { - ConfigureConstants() + tunables := ConfigureConstants() // This test is for the following network with our router being A: // 2 // B ---- D @@ -565,12 +573,13 @@ func TestRouterNet5A_SelectedUnfeasibleUpdate(t *testing.T) { h := &RouterHarness{} rs := &state.RouterState{ - Id: "A", - SelfSeqno: make(map[netip.Prefix]uint16), - Routes: make(map[netip.Prefix]state.SelRoute), - Sources: make(map[state.Source]state.FD), - Neighbours: MakeNeighbours("B", "C"), - Advertised: map[netip.Prefix]state.Advertisement{nodeToPrefix("A"): {NodeId: state.NodeId("A"), Expiry: maxTime}}, + RouterTunables: tunables, + Id: "A", + SelfSeqno: make(map[netip.Prefix]uint16), + Routes: make(map[netip.Prefix]state.SelRoute), + Sources: make(map[state.Source]state.FD), + Neighbours: MakeNeighbours("B", "C"), + Advertised: map[netip.Prefix]state.Advertisement{nodeToPrefix("A"): {NodeId: state.NodeId("A"), Expiry: maxTime}}, } _ = AddLink(rs, NewMockEndpoint("B", 1)) @@ -634,7 +643,7 @@ func TestRouterNet5A_SelectedUnfeasibleUpdate(t *testing.T) { } func TestRouter_BackupRouteOverridesHeldRoute(t *testing.T) { - ConfigureConstants() + tunables := ConfigureConstants() // Topology: // A --(1)-- C // A --(1)-- B --(10)-- C @@ -644,12 +653,13 @@ func TestRouter_BackupRouteOverridesHeldRoute(t *testing.T) { h := &RouterHarness{} cPrefix := nodeToPrefix("C") rs := &state.RouterState{ - Id: "A", - SelfSeqno: make(map[netip.Prefix]uint16), - Routes: make(map[netip.Prefix]state.SelRoute), - Sources: make(map[state.Source]state.FD), - Neighbours: MakeNeighbours("B", "C"), - Advertised: map[netip.Prefix]state.Advertisement{nodeToPrefix("A"): {NodeId: state.NodeId("A"), Expiry: maxTime}}, + RouterTunables: tunables, + Id: "A", + SelfSeqno: make(map[netip.Prefix]uint16), + Routes: make(map[netip.Prefix]state.SelRoute), + Sources: make(map[state.Source]state.FD), + Neighbours: MakeNeighbours("B", "C"), + Advertised: map[netip.Prefix]state.Advertisement{nodeToPrefix("A"): {NodeId: state.NodeId("A"), Expiry: maxTime}}, } AC := AddLink(rs, NewMockEndpoint("C", 1)) @@ -687,7 +697,7 @@ func TestRouter_BackupRouteOverridesHeldRoute(t *testing.T) { } func TestRouter_RetractedByClearedWhenHeldRouteRecovers(t *testing.T) { - ConfigureConstants() + tunables := ConfigureConstants() // Topology: // A --(1)-- C // A --(1)-- B --(10)-- C @@ -697,12 +707,13 @@ func TestRouter_RetractedByClearedWhenHeldRouteRecovers(t *testing.T) { h := &RouterHarness{} cPrefix := nodeToPrefix("C") rs := &state.RouterState{ - Id: "A", - SelfSeqno: make(map[netip.Prefix]uint16), - Routes: make(map[netip.Prefix]state.SelRoute), - Sources: make(map[state.Source]state.FD), - Neighbours: MakeNeighbours("B", "C", "D"), - Advertised: map[netip.Prefix]state.Advertisement{nodeToPrefix("A"): {NodeId: state.NodeId("A"), Expiry: maxTime}}, + RouterTunables: tunables, + Id: "A", + SelfSeqno: make(map[netip.Prefix]uint16), + Routes: make(map[netip.Prefix]state.SelRoute), + Sources: make(map[state.Source]state.FD), + Neighbours: MakeNeighbours("B", "C", "D"), + Advertised: map[netip.Prefix]state.Advertisement{nodeToPrefix("A"): {NodeId: state.NodeId("A"), Expiry: maxTime}}, } AC := AddLink(rs, NewMockEndpoint("C", 1)) @@ -732,17 +743,18 @@ func TestRouter_RetractedByClearedWhenHeldRouteRecovers(t *testing.T) { } func TestRouter_AckRetractIgnoredForFiniteRoute(t *testing.T) { - ConfigureConstants() + tunables := ConfigureConstants() h := &RouterHarness{} cPrefix := nodeToPrefix("C") rs := &state.RouterState{ - Id: "A", - SelfSeqno: make(map[netip.Prefix]uint16), - Routes: make(map[netip.Prefix]state.SelRoute), - Sources: make(map[state.Source]state.FD), - Neighbours: MakeNeighbours("B", "C"), - Advertised: map[netip.Prefix]state.Advertisement{nodeToPrefix("A"): {NodeId: state.NodeId("A"), Expiry: maxTime}}, + RouterTunables: tunables, + Id: "A", + SelfSeqno: make(map[netip.Prefix]uint16), + Routes: make(map[netip.Prefix]state.SelRoute), + Sources: make(map[state.Source]state.FD), + Neighbours: MakeNeighbours("B", "C"), + Advertised: map[netip.Prefix]state.Advertisement{nodeToPrefix("A"): {NodeId: state.NodeId("A"), Expiry: maxTime}}, } _ = AddLink(rs, NewMockEndpoint("C", 1)) @@ -759,8 +771,8 @@ func TestRouter_AckRetractIgnoredForFiniteRoute(t *testing.T) { } func TestRouter_UnfeasibleUpdatePreferenceUsesTotalMetric(t *testing.T) { - ConfigureConstants() - state.HopCost = 5 + tunables := ConfigureConstants() + tunables.HopCost = 5 // Topology: // A --(5, then 20)-- C // A --(5)-- B --(10, then 20)-- C @@ -771,12 +783,13 @@ func TestRouter_UnfeasibleUpdatePreferenceUsesTotalMetric(t *testing.T) { h := &RouterHarness{} cPrefix := nodeToPrefix("C") rs := &state.RouterState{ - Id: "A", - SelfSeqno: make(map[netip.Prefix]uint16), - Routes: make(map[netip.Prefix]state.SelRoute), - Sources: make(map[state.Source]state.FD), - Neighbours: MakeNeighbours("B", "C"), - Advertised: map[netip.Prefix]state.Advertisement{nodeToPrefix("A"): {NodeId: state.NodeId("A"), Expiry: maxTime}}, + RouterTunables: tunables, + Id: "A", + SelfSeqno: make(map[netip.Prefix]uint16), + Routes: make(map[netip.Prefix]state.SelRoute), + Sources: make(map[state.Source]state.FD), + Neighbours: MakeNeighbours("B", "C"), + Advertised: map[netip.Prefix]state.Advertisement{nodeToPrefix("A"): {NodeId: state.NodeId("A"), Expiry: maxTime}}, } AC := AddLink(rs, NewMockEndpoint("C", 5)) @@ -801,7 +814,7 @@ func TestRouter_UnfeasibleUpdatePreferenceUsesTotalMetric(t *testing.T) { } func TestRouter_KeepsSelectedRouteOnEqualMetric(t *testing.T) { - ConfigureConstants() + tunables := ConfigureConstants() // A has two equal-cost paths to S. Once A selects B, a later recompute // should not switch to C only because C appears later in the neighbour list. // @@ -816,12 +829,13 @@ func TestRouter_KeepsSelectedRouteOnEqualMetric(t *testing.T) { h := &RouterHarness{} sPrefix := nodeToPrefix("S") rs := &state.RouterState{ - Id: "A", - SelfSeqno: make(map[netip.Prefix]uint16), - Routes: make(map[netip.Prefix]state.SelRoute), - Sources: make(map[state.Source]state.FD), - Neighbours: MakeNeighbours("B", "C"), - Advertised: map[netip.Prefix]state.Advertisement{nodeToPrefix("A"): {NodeId: state.NodeId("A"), Expiry: maxTime}}, + RouterTunables: tunables, + Id: "A", + SelfSeqno: make(map[netip.Prefix]uint16), + Routes: make(map[netip.Prefix]state.SelRoute), + Sources: make(map[state.Source]state.FD), + Neighbours: MakeNeighbours("B", "C"), + Advertised: map[netip.Prefix]state.Advertisement{nodeToPrefix("A"): {NodeId: state.NodeId("A"), Expiry: maxTime}}, } _ = AddLink(rs, NewMockEndpoint("B", 1)) @@ -841,7 +855,7 @@ func TestRouter_KeepsSelectedRouteOnEqualMetric(t *testing.T) { } func TestRouter_HeldRouteInstallsBlackhole(t *testing.T) { - ConfigureConstants() + tunables := ConfigureConstants() // A has a covering aggregate through B and a more specific route through C. // When C disappears, the specific route must become an exact blackhole while // it is held; deleting only the specific route would allow the aggregate to @@ -857,12 +871,13 @@ func TestRouter_HeldRouteInstallsBlackhole(t *testing.T) { aggregate := netip.MustParsePrefix("10.0.0.0/24") specific := nodeToPrefix("C") rs := &state.RouterState{ - Id: "A", - SelfSeqno: make(map[netip.Prefix]uint16), - Routes: make(map[netip.Prefix]state.SelRoute), - Sources: make(map[state.Source]state.FD), - Neighbours: MakeNeighbours("B", "C"), - Advertised: map[netip.Prefix]state.Advertisement{nodeToPrefix("A"): {NodeId: state.NodeId("A"), Expiry: maxTime}}, + RouterTunables: tunables, + Id: "A", + SelfSeqno: make(map[netip.Prefix]uint16), + Routes: make(map[netip.Prefix]state.SelRoute), + Sources: make(map[state.Source]state.FD), + Neighbours: MakeNeighbours("B", "C"), + Advertised: map[netip.Prefix]state.Advertisement{nodeToPrefix("A"): {NodeId: state.NodeId("A"), Expiry: maxTime}}, } _ = AddLink(rs, NewMockEndpoint("B", 1)) @@ -884,7 +899,7 @@ func TestRouter_HeldRouteInstallsBlackhole(t *testing.T) { } func TestRouter_SelectedNeighbourUnfeasibleSourceChangeIsUnselected(t *testing.T) { - ConfigureConstants() + tunables := ConfigureConstants() // A selected B's route to P with source S. B then changes the source for // the same prefix to T, but that update is unfeasible. Since the source no // longer matches the selected route, A must not ignore the update as if the @@ -911,12 +926,13 @@ func TestRouter_SelectedNeighbourUnfeasibleSourceChangeIsUnselected(t *testing.T oldSrc := state.Source{NodeId: "S", Prefix: prefix} newSrc := state.Source{NodeId: "T", Prefix: prefix} rs := &state.RouterState{ - Id: "A", - SelfSeqno: make(map[netip.Prefix]uint16), - Routes: make(map[netip.Prefix]state.SelRoute), - Sources: make(map[state.Source]state.FD), - Neighbours: MakeNeighbours("B"), - Advertised: map[netip.Prefix]state.Advertisement{nodeToPrefix("A"): {NodeId: state.NodeId("A"), Expiry: maxTime}}, + RouterTunables: tunables, + Id: "A", + SelfSeqno: make(map[netip.Prefix]uint16), + Routes: make(map[netip.Prefix]state.SelRoute), + Sources: make(map[state.Source]state.FD), + Neighbours: MakeNeighbours("B"), + Advertised: map[netip.Prefix]state.Advertisement{nodeToPrefix("A"): {NodeId: state.NodeId("A"), Expiry: maxTime}}, } _ = AddLink(rs, NewMockEndpoint("B", 1)) @@ -934,7 +950,7 @@ func TestRouter_SelectedNeighbourUnfeasibleSourceChangeIsUnselected(t *testing.T } func TestRouter_DoesNotSelectInactiveEndpointRoute(t *testing.T) { - ConfigureConstants() + tunables := ConfigureConstants() // A has learned a route to S from B, but its only endpoint to B is inactive. // An inactive endpoint should be treated as no usable link, so A must not // select or install the route. @@ -950,12 +966,13 @@ func TestRouter_DoesNotSelectInactiveEndpointRoute(t *testing.T) { h := &RouterHarness{} sPrefix := nodeToPrefix("S") rs := &state.RouterState{ - Id: "A", - SelfSeqno: make(map[netip.Prefix]uint16), - Routes: make(map[netip.Prefix]state.SelRoute), - Sources: make(map[state.Source]state.FD), - Neighbours: MakeNeighbours("B"), - Advertised: map[netip.Prefix]state.Advertisement{nodeToPrefix("A"): {NodeId: state.NodeId("A"), Expiry: maxTime}}, + RouterTunables: tunables, + Id: "A", + SelfSeqno: make(map[netip.Prefix]uint16), + Routes: make(map[netip.Prefix]state.SelRoute), + Sources: make(map[state.Source]state.FD), + Neighbours: MakeNeighbours("B"), + Advertised: map[netip.Prefix]state.Advertisement{nodeToPrefix("A"): {NodeId: state.NodeId("A"), Expiry: maxTime}}, } ep := NewMockEndpoint("B", 1) @@ -970,7 +987,7 @@ func TestRouter_DoesNotSelectInactiveEndpointRoute(t *testing.T) { } func TestRouter_SeqnoRequestSkipsInactiveForwardingNeighbour(t *testing.T) { - ConfigureConstants() + tunables := ConfigureConstants() // A receives a seqno request from B for S. C and D both have neighbour-table // routes for S, but C's endpoint is inactive. A must skip C and forward the // request to the reachable neighbour D. @@ -986,12 +1003,13 @@ func TestRouter_SeqnoRequestSkipsInactiveForwardingNeighbour(t *testing.T) { h := &RouterHarness{} sPrefix := nodeToPrefix("S") rs := &state.RouterState{ - Id: "A", - SelfSeqno: make(map[netip.Prefix]uint16), - Routes: make(map[netip.Prefix]state.SelRoute), - Sources: make(map[state.Source]state.FD), - Neighbours: MakeNeighbours("B", "C", "D"), - Advertised: map[netip.Prefix]state.Advertisement{nodeToPrefix("A"): {NodeId: state.NodeId("A"), Expiry: maxTime}}, + RouterTunables: tunables, + Id: "A", + SelfSeqno: make(map[netip.Prefix]uint16), + Routes: make(map[netip.Prefix]state.SelRoute), + Sources: make(map[state.Source]state.FD), + Neighbours: MakeNeighbours("B", "C", "D"), + Advertised: map[netip.Prefix]state.Advertisement{nodeToPrefix("A"): {NodeId: state.NodeId("A"), Expiry: maxTime}}, } _ = AddLink(rs, NewMockEndpoint("B", 1)) @@ -1013,7 +1031,7 @@ func TestRouter_SeqnoRequestSkipsInactiveForwardingNeighbour(t *testing.T) { } func TestRouter_FullTableUpdateDoesNotUpdateFeasibilityForRetraction(t *testing.T) { - ConfigureConstants() + tunables := ConfigureConstants() // A is holding a retraction for S and must continue advertising that INF // route. Sending the retraction should not update A's feasibility distance: // FD updates are for finite updates, not retractions. @@ -1032,8 +1050,9 @@ func TestRouter_FullTableUpdateDoesNotUpdateFeasibilityForRetraction(t *testing. prefix := nodeToPrefix("S") src := state.Source{NodeId: "S", Prefix: prefix} rs := &state.RouterState{ - Id: "A", - SelfSeqno: make(map[netip.Prefix]uint16), + RouterTunables: tunables, + Id: "A", + SelfSeqno: make(map[netip.Prefix]uint16), Routes: map[netip.Prefix]state.SelRoute{ prefix: { PubRoute: MakePubRoute("S", prefix, 1, state.INF), @@ -1055,7 +1074,7 @@ func TestRouter_FullTableUpdateDoesNotUpdateFeasibilityForRetraction(t *testing. } func TestRouter_SolveStarvationIgnoresRetractedNeighbourRoute(t *testing.T) { - ConfigureConstants() + tunables := ConfigureConstants() // A has lost its feasible route to S. The only remaining neighbour-table // entry is B's retraction, so it must not satisfy the "has a feasible // route" check that suppresses starvation seqno requests. @@ -1070,12 +1089,13 @@ func TestRouter_SolveStarvationIgnoresRetractedNeighbourRoute(t *testing.T) { prefix := nodeToPrefix("S") src := state.Source{NodeId: "S", Prefix: prefix} rs := &state.RouterState{ - Id: "A", - SelfSeqno: make(map[netip.Prefix]uint16), - Routes: make(map[netip.Prefix]state.SelRoute), - Sources: map[state.Source]state.FD{src: {Seqno: 0, Metric: 1}}, - Neighbours: MakeNeighbours("B"), - Advertised: map[netip.Prefix]state.Advertisement{nodeToPrefix("A"): {NodeId: state.NodeId("A"), Expiry: maxTime}}, + RouterTunables: tunables, + Id: "A", + SelfSeqno: make(map[netip.Prefix]uint16), + Routes: make(map[netip.Prefix]state.SelRoute), + Sources: map[state.Source]state.FD{src: {Seqno: 0, Metric: 1}}, + Neighbours: MakeNeighbours("B"), + Advertised: map[netip.Prefix]state.Advertisement{nodeToPrefix("A"): {NodeId: state.NodeId("A"), Expiry: maxTime}}, } _ = AddLink(rs, NewMockEndpoint("B", 1)) @@ -1086,11 +1106,11 @@ func TestRouter_SolveStarvationIgnoresRetractedNeighbourRoute(t *testing.T) { SolveStarvation(rs, h) - h.GetActions().AssertContains(t, BroadcastRequestSeqno(src, 1, state.SeqnoRequestHopCount)) + h.GetActions().AssertContains(t, BroadcastRequestSeqno(src, 1, tunables.SeqnoRequestHopCount)) } func TestRouter_SeqnoRequestDoesNotForwardToRetractedRoute(t *testing.T) { - ConfigureConstants() + tunables := ConfigureConstants() // B asks A for a newer seqno for S. A has a held selected route for S and // two other neighbour entries: C has only a retraction, while D has a real // finite route. A must not forward the request to C just because INF passes @@ -1108,8 +1128,9 @@ func TestRouter_SeqnoRequestDoesNotForwardToRetractedRoute(t *testing.T) { prefix := nodeToPrefix("S") src := state.Source{NodeId: "S", Prefix: prefix} rs := &state.RouterState{ - Id: "A", - SelfSeqno: make(map[netip.Prefix]uint16), + RouterTunables: tunables, + Id: "A", + SelfSeqno: make(map[netip.Prefix]uint16), Routes: map[netip.Prefix]state.SelRoute{ prefix: { PubRoute: MakePubRoute("S", prefix, 0, state.INF), @@ -1141,8 +1162,8 @@ func TestRouter_SeqnoRequestDoesNotForwardToRetractedRoute(t *testing.T) { } func TestRouter_UnfeasibleEqualMetricUpdateDoesNotRequestSeqno(t *testing.T) { - ConfigureConstants() - state.HopCost = 5 + tunables := ConfigureConstants() + tunables.HopCost = 5 // A selected C's route to S at total metric 10. The A-C link later worsens, // so the selected route's current total metric is 25. B then sends an // unfeasible update that would also total 25 if accepted. Equal cost is not @@ -1155,12 +1176,13 @@ func TestRouter_UnfeasibleEqualMetricUpdateDoesNotRequestSeqno(t *testing.T) { prefix := nodeToPrefix("S") src := state.Source{NodeId: "S", Prefix: prefix} rs := &state.RouterState{ - Id: "A", - SelfSeqno: make(map[netip.Prefix]uint16), - Routes: make(map[netip.Prefix]state.SelRoute), - Sources: make(map[state.Source]state.FD), - Neighbours: MakeNeighbours("B", "C"), - Advertised: map[netip.Prefix]state.Advertisement{nodeToPrefix("A"): {NodeId: state.NodeId("A"), Expiry: maxTime}}, + RouterTunables: tunables, + Id: "A", + SelfSeqno: make(map[netip.Prefix]uint16), + Routes: make(map[netip.Prefix]state.SelRoute), + Sources: make(map[state.Source]state.FD), + Neighbours: MakeNeighbours("B", "C"), + Advertised: map[netip.Prefix]state.Advertisement{nodeToPrefix("A"): {NodeId: state.NodeId("A"), Expiry: maxTime}}, } AC := AddLink(rs, NewMockEndpoint("C", 5)) @@ -1181,11 +1203,11 @@ func TestRouter_UnfeasibleEqualMetricUpdateDoesNotRequestSeqno(t *testing.T) { h.NeighUpdate(rs, "B", "S", prefix, 0, 15) - h.GetActions().AssertNotContains(t, RequestSeqno("B", src, 1, state.SeqnoRequestHopCount)) + h.GetActions().AssertNotContains(t, RequestSeqno("B", src, 1, tunables.SeqnoRequestHopCount)) } func TestRouter_BroadcastUsesConfiguredNeighbours(t *testing.T) { - ConfigureConstants() + tunables := ConfigureConstants() // broadcast must fan out over RouterState.Neighbours. The IO map // is only a pending-output cache; if it starts empty, broadcasting over IO // silently drops the update/request. @@ -1193,13 +1215,15 @@ func TestRouter_BroadcastUsesConfiguredNeighbours(t *testing.T) { prefix := nodeToPrefix("S") src := state.Source{NodeId: "S", Prefix: prefix} n := &Nylon{ + RouterTunables: *tunables, RouterState: &state.RouterState{ - Id: "A", - SelfSeqno: make(map[netip.Prefix]uint16), - Routes: make(map[netip.Prefix]state.SelRoute), - Sources: make(map[state.Source]state.FD), - Neighbours: MakeNeighbours("B", "C"), - Advertised: make(map[netip.Prefix]state.Advertisement), + RouterTunables: tunables, + Id: "A", + SelfSeqno: make(map[netip.Prefix]uint16), + Routes: make(map[netip.Prefix]state.SelRoute), + Sources: make(map[state.Source]state.FD), + Neighbours: MakeNeighbours("B", "C"), + Advertised: make(map[netip.Prefix]state.Advertisement), }, } n.router.IO = make(map[state.NodeId]*IOPending) @@ -1223,7 +1247,7 @@ func TestRouter_BroadcastUsesConfiguredNeighbours(t *testing.T) { } func TestRouter_HeldRouteDoesNotReinstallBlackholeOnNoopRecompute(t *testing.T) { - ConfigureConstants() + tunables := ConfigureConstants() // A holds S as an INF route after losing C. The first transition to held // state installs an exact-prefix blackhole; a later recompute with no state // change should not reinstall that same blackhole again. @@ -1237,12 +1261,13 @@ func TestRouter_HeldRouteDoesNotReinstallBlackholeOnNoopRecompute(t *testing.T) h := &RouterHarness{} prefix := nodeToPrefix("S") rs := &state.RouterState{ - Id: "A", - SelfSeqno: make(map[netip.Prefix]uint16), - Routes: make(map[netip.Prefix]state.SelRoute), - Sources: make(map[state.Source]state.FD), - Neighbours: MakeNeighbours("B", "C"), - Advertised: map[netip.Prefix]state.Advertisement{nodeToPrefix("A"): {NodeId: state.NodeId("A"), Expiry: maxTime}}, + RouterTunables: tunables, + Id: "A", + SelfSeqno: make(map[netip.Prefix]uint16), + Routes: make(map[netip.Prefix]state.SelRoute), + Sources: make(map[state.Source]state.FD), + Neighbours: MakeNeighbours("B", "C"), + Advertised: map[netip.Prefix]state.Advertisement{nodeToPrefix("A"): {NodeId: state.NodeId("A"), Expiry: maxTime}}, } _ = AddLink(rs, NewMockEndpoint("B", 1)) @@ -1264,8 +1289,8 @@ func TestRouter_HeldRouteDoesNotReinstallBlackholeOnNoopRecompute(t *testing.T) } func TestRouter5A_GCRoutes(t *testing.T) { - ConfigureConstants() - state.RouteExpiryTime = -1 // for testing, we want routes to expire immediately + tunables := ConfigureConstants() + tunables.RouteExpiryTime = -1 // for testing, we want routes to expire immediately // This test is for the following network with our router being A: // 3 // B ---- D @@ -1278,12 +1303,13 @@ func TestRouter5A_GCRoutes(t *testing.T) { h := &RouterHarness{} rs := &state.RouterState{ - Id: "A", - SelfSeqno: make(map[netip.Prefix]uint16), - Routes: make(map[netip.Prefix]state.SelRoute), - Sources: make(map[state.Source]state.FD), - Neighbours: MakeNeighbours("B", "C"), - Advertised: map[netip.Prefix]state.Advertisement{nodeToPrefix("A"): {NodeId: state.NodeId("A"), Expiry: maxTime}}, + RouterTunables: tunables, + Id: "A", + SelfSeqno: make(map[netip.Prefix]uint16), + Routes: make(map[netip.Prefix]state.SelRoute), + Sources: make(map[state.Source]state.FD), + Neighbours: MakeNeighbours("B", "C"), + Advertised: map[netip.Prefix]state.Advertisement{nodeToPrefix("A"): {NodeId: state.NodeId("A"), Expiry: maxTime}}, } _ = AddLink(rs, NewMockEndpoint("B", 1)) @@ -1330,7 +1356,7 @@ func TestRouter5A_GCRoutes(t *testing.T) { } func TestRouterNet6A_ConvergeOptimal(t *testing.T) { - ConfigureConstants() + tunables := ConfigureConstants() // This test is for the following network with our router being A: // 3 // B ---- D @@ -1343,12 +1369,13 @@ func TestRouterNet6A_ConvergeOptimal(t *testing.T) { h := &RouterHarness{} rs := &state.RouterState{ - Id: "A", - SelfSeqno: make(map[netip.Prefix]uint16), - Routes: make(map[netip.Prefix]state.SelRoute), - Sources: make(map[state.Source]state.FD), - Neighbours: MakeNeighbours("B", "C"), - Advertised: map[netip.Prefix]state.Advertisement{nodeToPrefix("A"): {NodeId: state.NodeId("A"), Expiry: maxTime}}, + RouterTunables: tunables, + Id: "A", + SelfSeqno: make(map[netip.Prefix]uint16), + Routes: make(map[netip.Prefix]state.SelRoute), + Sources: make(map[state.Source]state.FD), + Neighbours: MakeNeighbours("B", "C"), + Advertised: map[netip.Prefix]state.Advertisement{nodeToPrefix("A"): {NodeId: state.NodeId("A"), Expiry: maxTime}}, } AB := AddLink(rs, NewMockEndpoint("B", 1)) diff --git a/core/sys_physical.go b/core/sys_physical.go index e07d1cb..f8b09f8 100644 --- a/core/sys_physical.go +++ b/core/sys_physical.go @@ -11,7 +11,6 @@ import ( "github.com/encodeous/nylon/polyamide/conn" "github.com/encodeous/nylon/polyamide/device" "github.com/encodeous/nylon/polyamide/tun" - "github.com/encodeous/nylon/state" ) func NewWireGuardDevice(n *Nylon) (dev *device.Device, tunDevice tun.Device, realItf string, err error) { @@ -35,7 +34,7 @@ func NewWireGuardDevice(n *Nylon) (dev *device.Device, tunDevice tun.Device, rea // setup WireGuard dev = device.NewDevice(tdev, conn.NewDefaultBind(), &device.Logger{ Verbosef: func(format string, args ...any) { - if state.DBG_log_wireguard { + if n.DBG_log_wireguard { wgLog.Debug(fmt.Sprintf(format, args...)) } }, diff --git a/core/sys_virtual.go b/core/sys_virtual.go index ea1cf4d..0e5b661 100644 --- a/core/sys_virtual.go +++ b/core/sys_virtual.go @@ -35,7 +35,7 @@ func NewWireGuardDevice(n *Nylon) (dev *device.Device, tunDevice tun.Device, rea // setup WireGuard dev = device.NewDevice(tdev, bind, &device.Logger{ Verbosef: func(format string, args ...any) { - if state.DBG_log_wireguard { + if n.DBG_log_wireguard { wgLog.Debug(fmt.Sprintf(format, args...)) } }, diff --git a/integration/basic_test.go b/integration/basic_test.go index eae0f09..9c6e448 100644 --- a/integration/basic_test.go +++ b/integration/basic_test.go @@ -11,11 +11,6 @@ import ( "go.uber.org/goleak" ) -func TestMain(m *testing.M) { - state.DBG_log_wireguard = true - m.Run() -} - func TestStartStop(t *testing.T) { defer goleak.VerifyNone(t) vh := &VirtualHarness{} @@ -87,10 +82,12 @@ func TestSimplePing(t *testing.T) { func TestSimpleRoutedPing(t *testing.T) { defer goleak.VerifyNone(t) - state.ProbeDelay /= 3 // 3x faster - state.RouteUpdateDelay /= 3 + tunables := state.DefaultRouterTunables() + tunables.ProbeDelay /= 3 // 3x faster + tunables.RouteUpdateDelay /= 3 vh := &VirtualHarness{} + vh.Tunables = &tunables a1 := "192.168.1.1:1234" vh.NewNode("a", "10.0.0.1/32") b1 := "192.168.1.2:1234" diff --git a/integration/convergence_test.go b/integration/convergence_test.go index 6e7d93c..8b1c193 100644 --- a/integration/convergence_test.go +++ b/integration/convergence_test.go @@ -14,11 +14,13 @@ import ( func TestOptimalConvergence(t *testing.T) { defer goleak.VerifyNone(t) - state.ProbeDelay /= 3 // 3x faster - state.RouteUpdateDelay /= 3 - state.MinimumConfidenceWindow /= 5 + tunables := state.DefaultRouterTunables() + tunables.ProbeDelay /= 3 // 3x faster + tunables.RouteUpdateDelay /= 3 + tunables.MinimumConfidenceWindow /= 5 vh := &VirtualHarness{} + vh.Tunables = &tunables a1 := "192.168.1.1:1234" vh.NewNode("a", "10.0.0.1/32") b1 := "192.168.1.2:1234" diff --git a/integration/harness.go b/integration/harness.go index 39475ed..54f4d72 100644 --- a/integration/harness.go +++ b/integration/harness.go @@ -113,6 +113,7 @@ type VirtualHarness struct { Endpoints map[string]state.NodeId UntrackedRouting bool LogLevel *slog.Level + Tunables *state.RouterTunables } func (v *VirtualHarness) IndexOf(id state.NodeId) int { @@ -197,7 +198,7 @@ func (v *VirtualHarness) Start() chan error { labels := pprof.Labels("nylon node", string(rt.Id)) n, err := core.NewNylon(v.Central, v.Local[idx], *v.LogLevel, "", map[string]any{ "vnet": vn, - }) + }, state.NylonOptions{DBG_log_wireguard: true}, v.Tunables) if err != nil { errChan <- err return diff --git a/state/constants.go b/state/constants.go index 0c33db0..c700ef1 100644 --- a/state/constants.go +++ b/state/constants.go @@ -1,50 +1,10 @@ package state -import "time" - const ( INF = ^(uint32)(0) // INFM is the maximum value for a metric that is not a retraction. INFM = INF - 1 -) - -var ( - HopCost = (uint32)(5) // add a 5 microsecond hop cost to prevent loops on ultra-fast networks. - LargeChangeThreshold = (uint32)(100 * 1000) // 100 milliseconds change - SeqnoRequestHopCount = (uint8)(64) - RouteUpdateDelay = time.Second * 5 - ProbeDelay = time.Millisecond * 1000 - ProbeRecoveryDelay = time.Millisecond * 1500 - ProbeDiscoveryDelay = time.Second * 10 - StarvationDelay = time.Millisecond * 100 - SeqnoDedupTTL = time.Second * 3 - NeighbourIOFlushDelay = time.Millisecond * 500 - SafeMTU = 1200 - - // WindowSamples is the sliding window size - WindowSamples = int((time.Second * 60) / ProbeDelay) - OutlierPercentage = 0.05 - // minimum number of samples before we lower the ping - MinimumConfidenceWindow = int(time.Second * 15 / ProbeDelay) - - GcDelay = time.Millisecond * 1000 - LinkDeadThreshold = 5 * ProbeDelay - RouteExpiryTime = 5 * RouteUpdateDelay - - // client configuration - ClientKeepaliveInterval = 3 * ProbeDelay - ClientDeadThreshold = 2 * ClientKeepaliveInterval - - // central updates - CentralUpdateDelay = time.Second * 10 - - // healthcheck defaults - HealthCheckDelay = time.Second * 15 - HealthCheckMaxFailures = 3 // default port DefaultPort = 57175 - - EndpointResolveExpiry = time.Minute * 1 - EndpointResolveDelay = time.Second * 15 ) diff --git a/state/debug.go b/state/debug.go deleted file mode 100644 index 9b12027..0000000 --- a/state/debug.go +++ /dev/null @@ -1,9 +0,0 @@ -package state - -var DBG_log_probe = false -var DBG_log_wireguard = false -var DBG_log_repo_updates = false -var DBG_log_json = false -var DBG_debug = false -var DBG_trace = false -var DBG_trace_tc = false diff --git a/state/endpoint.go b/state/endpoint.go index 7b509e3..2bdd3a1 100644 --- a/state/endpoint.go +++ b/state/endpoint.go @@ -65,7 +65,7 @@ func (ep *DynamicEndpoint) Parse() (host string, port uint16, err error) { } } -func (ep *DynamicEndpoint) Refresh() (netip.AddrPort, error) { +func (ep *DynamicEndpoint) Refresh(resolveExpiry time.Duration) (netip.AddrPort, error) { // 1. Try to parse as AddrPort directly if ap, err := netip.ParseAddrPort(ep.Value); err == nil { return ap, nil @@ -73,7 +73,7 @@ func (ep *DynamicEndpoint) Refresh() (netip.AddrPort, error) { ep.rw.RLock() // if this endpoint is down, we will refresh every EndpointResolveDelay - if time.Since(ep.lastUpdate) < EndpointResolveExpiry && ep.lastValue != (netip.AddrPort{}) { + if time.Since(ep.lastUpdate) < resolveExpiry && ep.lastValue != (netip.AddrPort{}) { ep.rw.RUnlock() return ep.lastValue, nil } @@ -154,6 +154,7 @@ func (ep *DynamicEndpoint) MarshalYAML() (interface{}, error) { type NylonEndpoint struct { sync.RWMutex // this mutex is for rtt smoothing and metric calculation + t *RouterTunables history []time.Duration histSort []time.Duration dirty bool @@ -200,7 +201,7 @@ func (n *Neighbour) BestEndpoint() Endpoint { } func (u *NylonEndpoint) isActiveUnlocked() bool { - return time.Since(u.lastHeardBack) <= LinkDeadThreshold + return time.Since(u.lastHeardBack) <= u.t.LinkDeadThreshold } func (u *NylonEndpoint) IsActive() bool { @@ -224,8 +225,9 @@ func (u *NylonEndpoint) IsAlive() bool { return u.IsActive() || !u.remoteInit // we never gc endpoints that we have in our config } -func NewEndpoint(endpoint *DynamicEndpoint, remoteInit bool, wgEndpoint conn.Endpoint) *NylonEndpoint { +func NewEndpoint(endpoint *DynamicEndpoint, remoteInit bool, wgEndpoint conn.Endpoint, t *RouterTunables) *NylonEndpoint { return &NylonEndpoint{ + t: t, remoteInit: remoteInit, WgEndpoint: wgEndpoint, DynEP: endpoint, @@ -237,7 +239,7 @@ func NewEndpoint(endpoint *DynamicEndpoint, remoteInit bool, wgEndpoint conn.End func (u *NylonEndpoint) calcR() (time.Duration, time.Duration, time.Duration) { u.Lock() defer u.Unlock() - if len(u.history) < MinimumConfidenceWindow { + if len(u.history) < u.t.MinimumConfidenceWindow { return time.Second * 1, time.Second * 1, time.Second * 1 } if u.dirty { @@ -246,8 +248,8 @@ func (u *NylonEndpoint) calcR() (time.Duration, time.Duration, time.Duration) { u.dirty = false } le := len(u.histSort) - low := u.histSort[int(float64(le)*OutlierPercentage)] - high := u.histSort[int(float64(le)*(1-OutlierPercentage))] + low := u.histSort[int(float64(le)*u.t.OutlierPercentage)] + high := u.histSort[int(float64(le)*(1-u.t.OutlierPercentage))] med := u.histSort[le/2] return low, med, high } @@ -290,7 +292,7 @@ func (u *NylonEndpoint) UpdatePing(ping time.Duration) { } u.expRTT = alpha*f + (1-alpha)*u.expRTT u.history = append(u.history, time.Duration(int64(u.expRTT))) - if len(u.history) > WindowSamples { + if len(u.history) > u.t.WindowSamples { u.history = u.history[1:] } u.dirty = true diff --git a/state/endpoint_test.go b/state/endpoint_test.go index b8b2a61..b7ad2a7 100644 --- a/state/endpoint_test.go +++ b/state/endpoint_test.go @@ -52,7 +52,8 @@ type DataSource struct { func runTests(t *testing.T, ping func(i int) float64, dura time.Duration, fn string) (DataSource, DataSource) { t.Helper() - dep := NewEndpoint(NewDynamicEndpoint("127.0.0.1:0"), false, nil) + tunables := DefaultRouterTunables() + dep := NewEndpoint(NewDynamicEndpoint("127.0.0.1:0"), false, nil, &tunables) truth := DataSource{ Name: "Truth", @@ -79,11 +80,11 @@ func runTests(t *testing.T, ping func(i int) float64, dura time.Duration, fn str Data: []time.Duration{}, } - samples := int(dura / ProbeDelay) + samples := int(dura / tunables.ProbeDelay) for i := 0; i < samples; i++ { nping := time.Duration(ping(i) * float64(time.Millisecond)) dep.UpdatePing(nping) - if i > MinimumConfidenceWindow { + if i > tunables.MinimumConfidenceWindow { truth.Data = append(truth.Data, nping) high.Data = append(high.Data, dep.HighRange()) low.Data = append(low.Data, dep.LowRange()) diff --git a/state/prefix_health.go b/state/prefix_health.go index 9c30eea..11a7012 100644 --- a/state/prefix_health.go +++ b/state/prefix_health.go @@ -15,7 +15,7 @@ import ( type PrefixHealth interface { GetMetric() uint32 // Metric does not block, and returns the advertised metric for this prefix GetPrefix() netip.Prefix - Start(log *slog.Logger) // Start begins any background monitoring required for this prefix + Start(log *slog.Logger, t *RouterTunables) // Start begins any background monitoring required for this prefix Stop() } @@ -35,7 +35,7 @@ func (s *StaticPrefixHealth) GetMetric() uint32 { func (s *StaticPrefixHealth) GetPrefix() netip.Prefix { return s.Prefix } -func (s *StaticPrefixHealth) Start(log *slog.Logger) { +func (s *StaticPrefixHealth) Start(log *slog.Logger, t *RouterTunables) { // do nothing } @@ -86,15 +86,15 @@ func (p *PingPrefixHealth) GetMetric() uint32 { func (p *PingPrefixHealth) GetPrefix() netip.Prefix { return p.Prefix } -func (p *PingPrefixHealth) Start(log *slog.Logger) { +func (p *PingPrefixHealth) Start(log *slog.Logger, t *RouterTunables) { if p.running.Swap(true) { return } if p.Delay == nil { - p.Delay = &HealthCheckDelay + p.Delay = &t.HealthCheckDelay } if p.MaxFailures == nil { - p.MaxFailures = &HealthCheckMaxFailures + p.MaxFailures = &t.HealthCheckMaxFailures } go func() { ticker := time.NewTicker(*p.Delay) @@ -168,13 +168,13 @@ func (h *HTTPPrefixHealth) GetMetric() uint32 { func (h *HTTPPrefixHealth) GetPrefix() netip.Prefix { return h.Prefix } -func (h *HTTPPrefixHealth) Start(log *slog.Logger) { +func (h *HTTPPrefixHealth) Start(log *slog.Logger, t *RouterTunables) { if h.running.Swap(true) { return } h.lastMetric = INF if h.Delay == nil { - h.Delay = &HealthCheckDelay + h.Delay = &t.HealthCheckDelay } go func() { ticker := time.NewTicker(*h.Delay) diff --git a/state/routing.go b/state/routing.go index 1effcd1..2de499f 100644 --- a/state/routing.go +++ b/state/routing.go @@ -36,6 +36,7 @@ type Advertisement struct { ExpiryFn func() } type RouterState struct { + *RouterTunables Id NodeId SelfSeqno map[netip.Prefix]uint16 Routes map[netip.Prefix]SelRoute diff --git a/state/tunables.go b/state/tunables.go new file mode 100644 index 0000000..688242b --- /dev/null +++ b/state/tunables.go @@ -0,0 +1,95 @@ +package state + +import "time" + +// RouterTunables contains all timing and algorithm parameters for the router. +// These are set once at startup and should not be mutated after the Nylon instance starts. +type RouterTunables struct { + HopCost uint32 // add a 5 microsecond hop cost to prevent loops on ultra-fast networks. + LargeChangeThreshold uint32 // 100 milliseconds change + SeqnoRequestHopCount uint8 + RouteUpdateDelay time.Duration + ProbeDelay time.Duration + ProbeRecoveryDelay time.Duration + ProbeDiscoveryDelay time.Duration + StarvationDelay time.Duration + SeqnoDedupTTL time.Duration + NeighbourIOFlushDelay time.Duration + SafeMTU int + + // WindowSamples is the sliding window size + WindowSamples int + OutlierPercentage float64 + // MinimumConfidenceWindow is the minimum number of samples before we lower the ping + MinimumConfidenceWindow int + + GcDelay time.Duration + LinkDeadThreshold time.Duration + RouteExpiryTime time.Duration + + // client configuration + ClientKeepaliveInterval time.Duration + ClientDeadThreshold time.Duration + + // central updates + CentralUpdateDelay time.Duration + + // healthcheck defaults + HealthCheckDelay time.Duration + HealthCheckMaxFailures int + + EndpointResolveExpiry time.Duration + EndpointResolveDelay time.Duration +} + +// NylonOptions contains runtime flags set at startup (typically from CLI flags). +type NylonOptions struct { + DBG_log_probe bool + DBG_log_wireguard bool + DBG_log_repo_updates bool + DBG_log_json bool + DBG_debug bool + DBG_trace bool + DBG_trace_tc bool +} + +func DefaultRouterTunables() RouterTunables { + probeDelay := time.Millisecond * 1000 + routeUpdateDelay := time.Second * 5 + return RouterTunables{ + HopCost: 5, + LargeChangeThreshold: 100 * 1000, + SeqnoRequestHopCount: 64, + RouteUpdateDelay: routeUpdateDelay, + ProbeDelay: probeDelay, + ProbeRecoveryDelay: time.Millisecond * 1500, + ProbeDiscoveryDelay: time.Second * 10, + StarvationDelay: time.Millisecond * 100, + SeqnoDedupTTL: time.Second * 3, + NeighbourIOFlushDelay: time.Millisecond * 500, + SafeMTU: 1200, + + WindowSamples: int((time.Second * 60) / probeDelay), + OutlierPercentage: 0.05, + MinimumConfidenceWindow: int(time.Second * 15 / probeDelay), + + GcDelay: time.Millisecond * 1000, + LinkDeadThreshold: 5 * probeDelay, + RouteExpiryTime: 5 * routeUpdateDelay, + + ClientKeepaliveInterval: 3 * probeDelay, + ClientDeadThreshold: 6 * probeDelay, // 2 * ClientKeepaliveInterval + + CentralUpdateDelay: time.Second * 10, + + HealthCheckDelay: time.Second * 15, + HealthCheckMaxFailures: 3, + + EndpointResolveExpiry: time.Minute * 1, + EndpointResolveDelay: time.Second * 15, + } +} + +func DefaultNylonOptions() NylonOptions { + return NylonOptions{} +}