diff --git a/.dockerignore b/.dockerignore index feaae4b..f626eba 100644 --- a/.dockerignore +++ b/.dockerignore @@ -32,7 +32,6 @@ central.nybundle distribute.sh distribute-conf.sh -log log.csv diff --git a/.github/workflows/go-test.yml b/.github/workflows/go-test.yml index 2277161..7fdccbd 100644 --- a/.github/workflows/go-test.yml +++ b/.github/workflows/go-test.yml @@ -44,5 +44,11 @@ jobs: uses: actions/setup-go@v4 with: go-version: ${{ env.GO_VERSION }} + - name: Get CPU core count + id: cpu + run: echo "cores=$(nproc)" >> $GITHUB_OUTPUT - name: Run e2e - run: go run gotest.tools/gotestsum@latest -- -tags=e2e ./e2e/... + run: | + go run gotest.tools/gotestsum@latest -- \ + -tags=e2e ./e2e/... \ + -parallel ${{ steps.cpu.outputs.cores }} diff --git a/.gitignore b/.gitignore index 4adf9f9..2d53849 100644 --- a/.gitignore +++ b/.gitignore @@ -32,10 +32,9 @@ central.nybundle distribute.sh distribute-conf.sh -log log.csv vendor -workdir \ No newline at end of file +workdir diff --git a/Dockerfile b/Dockerfile index 9dc9667..687a3aa 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.25.4 AS builder +FROM golang:1.26.2 AS builder WORKDIR /src COPY . . diff --git a/core/entrypoint.go b/core/entrypoint.go index b297f88..662d4fd 100644 --- a/core/entrypoint.go +++ b/core/entrypoint.go @@ -146,10 +146,10 @@ func Bootstrap(centralPath, nodePath, logPath string, verbose bool) { } } -func Start(ccfg state.CentralCfg, ncfg state.LocalCfg, logLevel slog.Level, configPath string, aux map[string]any, initState **state.State) (bool, error) { +func Start(ccfg state.CentralCfg, ncfg state.LocalCfg, logLevel slog.Level, configPath string, aux map[string]any, initNylon **Nylon) (bool, error) { ctx, cancel := context.WithCancelCause(context.Background()) - dispatch := make(chan func(env *state.State) error, 128) + dispatch := make(chan func() error, 128) handlers := make([]slog.Handler, 0) if state.DBG_log_json { @@ -193,7 +193,6 @@ func Start(ccfg state.CentralCfg, ncfg state.LocalCfg, logLevel slog.Level, conf } s := state.State{ - Modules: make(map[string]state.NyModule), Env: &state.Env{ Context: ctx, Cancel: cancel, @@ -205,12 +204,19 @@ func Start(ccfg state.CentralCfg, ncfg state.LocalCfg, logLevel slog.Level, conf AuxConfig: aux, }, } - if initState != nil { - *initState = &s - } + s.Log.Info("init modules") - err := initModules(&s) + + n := &Nylon{ + State: &s, + Trace: &NylonTrace{}, + Router: &NylonRouter{}, + } + if initNylon != nil { + *initNylon = n + } + err := n.Init() if err != nil { return false, err } @@ -229,7 +235,7 @@ func Start(ccfg state.CentralCfg, ncfg state.LocalCfg, logLevel slog.Level, conf } }() - err = MainLoop(&s, dispatch) + err = MainLoop(n, dispatch) if err != nil { return false, err } @@ -240,22 +246,9 @@ func Start(ccfg state.CentralCfg, ncfg state.LocalCfg, logLevel slog.Level, conf return false, nil } -func initModules(s *state.State) error { - var modules []state.NyModule - modules = append(modules, &NylonTrace{}) - modules = append(modules, &NylonRouter{}) - modules = append(modules, &Nylon{}) - - for _, module := range modules { - s.Modules[reflect.TypeOf(module).String()] = module - if err := module.Init(s); err != nil { - return err - } - } - return nil -} -func MainLoop(s *state.State, dispatch <-chan func(*state.State) error) error { +func MainLoop(n *Nylon, dispatch <-chan func() error) error { + s := n.State s.Log.Debug("started main loop") s.Started.Store(true) for { @@ -266,7 +259,7 @@ func MainLoop(s *state.State, dispatch <-chan func(*state.State) error) error { } //s.Log.Debug("start") start := time.Now() - err := fun(s) + err := fun() if err != nil { s.Log.Error("error occurred during dispatch: ", "error", err) s.Cancel(err) @@ -283,11 +276,12 @@ func MainLoop(s *state.State, dispatch <-chan func(*state.State) error) error { } endLoop: s.Log.Info("stopped main loop", "reason", context.Cause(s.Context).Error()) - Stop(s) + Stop(n) return nil } -func Stop(s *state.State) { +func Stop(n *Nylon) { + s := n.State if s.Stopping.Swap(true) { return // don't stop twice } @@ -297,11 +291,9 @@ func Stop(s *state.State) { s.DispatchChannel = nil } s.Log.Info("cleaning up modules") - for moduleName, module := range s.Modules { - err := module.Cleanup(s) - if err != nil { - s.Log.Error("error occurred during Stop: ", "module", moduleName, "error", err) - } + err := n.Cleanup() + if err != nil { + s.Log.Error("error occurred during Stop: ", "error", err) } s.Log.Info("stopped") } diff --git a/core/ipc.go b/core/ipc.go index 7fab06f..aeb4a6c 100644 --- a/core/ipc.go +++ b/core/ipc.go @@ -73,7 +73,8 @@ func IPCTrace(itf string) error { } } -func HandleNylonIPCGet(s *state.State, rw *bufio.ReadWriter) error { +func HandleNylonIPCGet(n *Nylon, rw *bufio.ReadWriter) error { + s := n.State cmd, err := rw.ReadString('\n') if err != nil { return err @@ -147,7 +148,7 @@ func HandleNylonIPCGet(s *state.State, rw *bufio.ReadWriter) error { // print forward table sb.WriteString("\n\nForward Table:\n") rt = make([]string, 0) - for prefix, route := range Get[*NylonRouter](s).ForwardTable.All() { + for prefix, route := range n.Router.ForwardTable.All() { rt = append(rt, fmt.Sprintf(" - %s via %s", prefix, route.Nh)) } slices.Sort(rt) @@ -156,7 +157,7 @@ func HandleNylonIPCGet(s *state.State, rw *bufio.ReadWriter) error { // print exit table sb.WriteString("\n\nExit Table:\n") rt = make([]string, 0) - for prefix, route := range Get[*NylonRouter](s).ExitTable.All() { + for prefix, route := range n.Router.ExitTable.All() { rt = append(rt, fmt.Sprintf(" - %s via %s", prefix, route.Nh)) } slices.Sort(rt) @@ -176,7 +177,7 @@ func HandleNylonIPCGet(s *state.State, rw *bufio.ReadWriter) error { return fmt.Errorf("trace mode is not enabled") } ctx, cancel := context.WithCancel(context.Background()) - t := Get[*NylonTrace](s) + t := n.Trace go func() { _, _ = rw.ReadByte() // wait for EOF cancel() diff --git a/core/nylon.go b/core/nylon.go index 25b4dfc..9ffafd2 100644 --- a/core/nylon.go +++ b/core/nylon.go @@ -13,6 +13,9 @@ import ( // Nylon struct must be thread safe, since it can receive packets through PolyReceiver type Nylon struct { + State *state.State + Trace *NylonTrace + Router *NylonRouter PingBuf *ttlcache.Cache[uint64, EpPing] Device *device.Device Tun tun.Device @@ -22,11 +25,21 @@ type Nylon struct { prevInstalledRoutes []netip.Prefix } -func (n *Nylon) Init(s *state.State) error { +func (n *Nylon) Init() error { + s := n.State n.env = s.Env s.Log.Debug("init nylon") + err := n.Trace.Init(n) + if err != nil { + return err + } + err = n.Router.Init(n) + if err != nil { + return err + } + state.SetResolvers(s.DnsResolvers) // add neighbours @@ -53,19 +66,21 @@ func (n *Nylon) Init(s *state.State) error { ) go n.PingBuf.Start() - s.Env.RepeatTask(nylonGc, state.GcDelay) + s.Env.RepeatTask(func() error { + return nylonGc(n) + }, state.GcDelay) // wireguard configuration - err := n.initWireGuard(s) + err = n.initWireGuard() if err != nil { return err } // endpoint probing - s.Env.RepeatTask(func(s *state.State) error { + s.Env.RepeatTask(func() error { return n.probeLinks(s, true) }, state.ProbeDelay) - s.Env.RepeatTask(func(s *state.State) error { + s.Env.RepeatTask(func() error { // refresh dynamic endpoints for _, neigh := range s.Neighbours { for _, ep := range neigh.Eps { @@ -81,10 +96,10 @@ func (n *Nylon) Init(s *state.State) error { } return nil }, state.EndpointResolveDelay) - s.Env.RepeatTask(func(s *state.State) error { + s.Env.RepeatTask(func() error { return n.probeLinks(s, false) }, state.ProbeRecoveryDelay) - s.Env.RepeatTask(func(s *state.State) error { + s.Env.RepeatTask(func() error { return n.probeNew(s) }, state.ProbeDiscoveryDelay) @@ -104,16 +119,20 @@ func (n *Nylon) Init(s *state.State) error { for _, repo := range s.CentralCfg.Dist.Repos { s.Log.Info("config source", "repo", repo) } - s.Env.RepeatTask(checkForConfigUpdates, state.CentralUpdateDelay) + s.Env.RepeatTask(func() error { return checkForConfigUpdates(n) }, state.CentralUpdateDelay) } return nil } -func (n *Nylon) Cleanup(s *state.State) error { +func (n *Nylon) Cleanup() error { + s := n.State n.PingBuf.Stop() for _, ph := range s.GetNode(s.Id).Prefixes { ph.Stop() } - return n.cleanupWireGuard(s) + n.Router.Cleanup() + n.Trace.Cleanup() + + return n.cleanupWireGuard() } diff --git a/core/nylon_distribution.go b/core/nylon_distribution.go index 9da7a13..d1cf646 100644 --- a/core/nylon_distribution.go +++ b/core/nylon_distribution.go @@ -74,7 +74,8 @@ func FetchConfig(repoStr string, key state.NyPublicKey) (*state.CentralCfg, erro } // responsible for central config distribution -func checkForConfigUpdates(s *state.State) error { +func checkForConfigUpdates(n *Nylon) error { + s := n.State if s.CentralCfg.Dist == nil { return errors.New("nylon is not configured for automatic config distribution") } diff --git a/core/nylon_endpoints.go b/core/nylon_endpoints.go index 1c26e2a..ea04315 100644 --- a/core/nylon_endpoints.go +++ b/core/nylon_endpoints.go @@ -48,8 +48,7 @@ func handleProbe(n *Nylon, pkt *protocol.Ny_Probe, endpoint conn.Endpoint, peer // ping // build pong response res := pkt - token := rand.Uint64() - res.ResponseToken = &token + res.ResponseToken = new(rand.Uint64()) // send pong err := n.SendNylon(&protocol.Ny{Type: &protocol.Ny_ProbeOp{ProbeOp: pkt}}, endpoint, peer) @@ -58,24 +57,25 @@ func handleProbe(n *Nylon, pkt *protocol.Ny_Probe, endpoint conn.Endpoint, peer return } - e.Dispatch(func(s *state.State) error { - handleProbePing(s, node, endpoint) + e.Dispatch(func() error { + handleProbePing(n, node, endpoint) return nil }) } else { // pong - e.Dispatch(func(s *state.State) error { - handleProbePong(s, node, pkt.Token, endpoint) + e.Dispatch(func() error { + handleProbePong(n, node, pkt.Token, endpoint) return nil }) } } -func handleProbePing(s *state.State, node state.NodeId, ep conn.Endpoint) { +func handleProbePing(n *Nylon, node state.NodeId, ep conn.Endpoint) { + s := n.State if node == s.Id { return } - r := Get[*NylonRouter](s) + r := n.Router // check if link exists for _, neigh := range s.Neighbours { for _, dep := range neigh.Eps { @@ -112,9 +112,9 @@ func handleProbePing(s *state.State, node state.NodeId, ep conn.Endpoint) { } } -func handleProbePong(s *state.State, node state.NodeId, token uint64, ep conn.Endpoint) { - n := Get[*Nylon](s) - r := Get[*NylonRouter](s) +func handleProbePong(n *Nylon, node state.NodeId, token uint64, ep conn.Endpoint) { + s := n.State + r := n.Router // check if link exists for _, neigh := range s.Neighbours { for _, dpLink := range neigh.Eps { diff --git a/core/nylon_gc.go b/core/nylon_gc.go index 0662330..034928b 100644 --- a/core/nylon_gc.go +++ b/core/nylon_gc.go @@ -1,10 +1,8 @@ package core -import ( - "github.com/encodeous/nylon/state" -) -func nylonGc(s *state.State) error { +func nylonGc(n *Nylon) error { + s := n.State // scan for dead links for _, neigh := range s.Neighbours { // filter dplinks @@ -24,8 +22,7 @@ func nylonGc(s *state.State) error { neigh.Eps = neigh.Eps[:n] } - r := Get[*NylonRouter](s) - err := r.GcRouter(s) + err := n.Router.GcRouter() if err != nil { return err } diff --git a/core/nylon_passive.go b/core/nylon_passive.go index b7bb18e..37ee2ec 100644 --- a/core/nylon_passive.go +++ b/core/nylon_passive.go @@ -7,13 +7,15 @@ import ( ) func (n *Nylon) initPassiveClient(s *state.State) error { - s.Env.RepeatTask(scanPassivePeers, state.ProbeDelay) + s.Env.RepeatTask(func() error { + return scanPassivePeers(n) + }, state.ProbeDelay) return nil } -func scanPassivePeers(s *state.State) error { - n := Get[*Nylon](s) - r := Get[*NylonRouter](s) +func scanPassivePeers(n *Nylon) error { + s := n.State + r := n.Router for _, peer := range n.Device.GetPeers() { nid := s.FindNodeBy(state.NyPublicKey(peer.GetPublicKey())) diff --git a/core/nylon_tc.go b/core/nylon_tc.go index 79bed43..4dd6b5d 100644 --- a/core/nylon_tc.go +++ b/core/nylon_tc.go @@ -17,9 +17,10 @@ const ( // polyamide traffic control for nylon -func (n *Nylon) InstallTC(s *state.State) { - r := Get[*NylonRouter](s) - t := Get[*NylonTrace](s) +func (n *Nylon) InstallTC() { + s := n.State + r := n.Router + t := n.Trace if state.DBG_trace_tc { n.Device.InstallFilter(func(dev *device.Device, packet *device.TCElement) (device.TCAction, error) { @@ -175,23 +176,23 @@ func (n *Nylon) handleNylonPacket(packet []byte, endpoint conn.Endpoint, peer *d defer func() { err := recover() if err != nil { - n.env.Log.Error("panic while handling poly socket: %v", err) + n.env.Log.Error("panic while handling poly socket", "err", err) } }() for _, pkt := range bundle.Packets { switch pkt.Type.(type) { case *protocol.Ny_SeqnoRequestOp: - e.Dispatch(func(s *state.State) error { - return routerHandleSeqnoRequest(s, *neigh, pkt.GetSeqnoRequestOp()) + e.Dispatch(func() error { + return n.Router.routerHandleSeqnoRequest(*neigh, pkt.GetSeqnoRequestOp()) }) case *protocol.Ny_RouteOp: - e.Dispatch(func(s *state.State) error { - return routerHandleRouteUpdate(s, *neigh, pkt.GetRouteOp()) + e.Dispatch(func() error { + return n.Router.routerHandleRouteUpdate(*neigh, pkt.GetRouteOp()) }) case *protocol.Ny_AckRetractOp: - e.Dispatch(func(s *state.State) error { - return routerHandleAckRetract(s, *neigh, pkt.GetAckRetractOp()) + e.Dispatch(func() error { + return n.Router.routerHandleAckRetract(*neigh, pkt.GetAckRetractOp()) }) case *protocol.Ny_ProbeOp: handleProbe(n, pkt.GetProbeOp(), endpoint, peer, *neigh) diff --git a/core/nylon_trace.go b/core/nylon_trace.go index 511fe8b..3d0fd2e 100644 --- a/core/nylon_trace.go +++ b/core/nylon_trace.go @@ -2,18 +2,16 @@ package core import ( "github.com/dustin/go-broadcast" - "github.com/encodeous/nylon/state" ) - type NylonTrace struct { broadcast.Broadcaster } -func (n *NylonTrace) Init(s *state.State) error { +func (n *NylonTrace) Init(core *Nylon) error { n.Broadcaster = broadcast.NewBroadcaster(1024) return nil } -func (n *NylonTrace) Cleanup(s *state.State) error { +func (n *NylonTrace) Cleanup() error { return n.Broadcaster.Close() } diff --git a/core/nylon_wireguard.go b/core/nylon_wireguard.go index de95983..688a815 100644 --- a/core/nylon_wireguard.go +++ b/core/nylon_wireguard.go @@ -12,7 +12,8 @@ import ( "github.com/encodeous/nylon/state" ) -func (n *Nylon) initWireGuard(s *state.State) error { +func (n *Nylon) initWireGuard() error { + s := n.State dev, tdev, itfName, err := NewWireGuardDevice(s, n) if err != nil { return err @@ -27,11 +28,11 @@ func (n *Nylon) initWireGuard(s *state.State) error { n.Tun = tdev n.itfName = itfName - n.InstallTC(s) + n.InstallTC() s.Log.Info("installed nylon traffic control filter for polysock") dev.IpcHandler["get=nylon\n"] = func(writer *bufio.ReadWriter) error { - return HandleNylonIPCGet(s, writer) + return HandleNylonIPCGet(n, writer) } // TODO: fully convert to code-based api @@ -117,12 +118,15 @@ listen_port=%d } // init wireguard related tasks - s.RepeatTask(UpdateWireGuard, state.ProbeDelay) + s.RepeatTask(func() error { + return UpdateWireGuard(n) + }, state.ProbeDelay) return nil } -func (n *Nylon) cleanupWireGuard(s *state.State) error { +func (n *Nylon) cleanupWireGuard() error { + s := n.State // remove routes for _, route := range n.prevInstalledRoutes { err := RemoveRoute(s.Log, n.Tun, n.itfName, route) @@ -151,8 +155,8 @@ func (n *Nylon) cleanupWireGuard(s *state.State) error { return nil } -func UpdateWireGuard(s *state.State) error { - n := Get[*Nylon](s) +func UpdateWireGuard(n *Nylon) error { + s := n.State dev := n.Device // configure endpoints @@ -201,7 +205,7 @@ func UpdateWireGuard(s *state.State) error { // configure changed route table entries if !s.NoNetConfigure { - router := Get[*NylonRouter](s) + router := n.Router newEntries := router.ComputeSysRouteTable() oldEntries := n.prevInstalledRoutes for _, oldEntry := range oldEntries { diff --git a/core/router.go b/core/router.go index aad2db3..2d2aa8d 100644 --- a/core/router.go +++ b/core/router.go @@ -1,14 +1,15 @@ package core import ( - "fmt" "net/netip" "github.com/encodeous/nylon/polyamide/device" "github.com/gaissmai/bart" "google.golang.org/protobuf/proto" - //"errors" + "log/slog" + + "github.com/encodeous/nylon/log" "github.com/encodeous/nylon/protocol" "github.com/encodeous/nylon/state" "github.com/jellydator/ttlcache/v3" @@ -18,6 +19,8 @@ import ( type NylonRouter struct { *state.State + Core *Nylon + logger *slog.Logger LastStarvationRequest time.Time IO map[state.NodeId]*IOPending // ForwardTable contains the full routing table @@ -96,11 +99,11 @@ func (r *NylonRouter) BroadcastRequestSeqno(src state.Source, seqno uint16, hopC } } -func (r *NylonRouter) Log(event RouterEvent, desc string, args ...any) { - if event == NoEpToNeighbour { +func (r *NylonRouter) Log(event string, desc string, args ...any) { + if event == log.EventNoEndpointToNeigh { return // ignored } - r.Env.Log.Debug(fmt.Sprintf("%s %s", event.String(), desc), args...) + r.logger.Debug(desc, append([]any{"event", event}, args...)...) } func (r *NylonRouter) UpdateNeighbour(neigh state.NodeId) { @@ -108,7 +111,7 @@ func (r *NylonRouter) UpdateNeighbour(neigh state.NodeId) { } func (r *NylonRouter) TableInsertRoute(prefix netip.Prefix, route state.SelRoute) { - n := Get[*Nylon](r.State) + n := r.Core nh := route.Nh peer := n.Device.LookupPeer(device.NoisePublicKey(r.GetNode(nh).PubKey)) r.ForwardTable.Insert(prefix, RouteTableEntry{ @@ -138,13 +141,15 @@ type IOPending struct { Updates map[netip.Prefix]*protocol.Ny_Update } -func (r *NylonRouter) Cleanup(s *state.State) error { +func (r *NylonRouter) Cleanup() error { r.State = nil + r.logger = nil r.IO = nil return nil } -func (r *NylonRouter) GcRouter(s *state.State) error { +func (r *NylonRouter) GcRouter() error { + s := r.State RunGC(s.RouterState, r) for id, _ := range r.IO { if s.GetNeighbour(id) == nil { @@ -158,9 +163,12 @@ func (r *NylonRouter) GcRouter(s *state.State) error { return nil } -func (r *NylonRouter) Init(s *state.State) error { - s.Log.Debug("init router") +func (r *NylonRouter) Init(n *Nylon) error { + s := n.State + r.Core = n r.State = s + r.logger = s.Log.With("module", log.ScopeRouter) + r.logger.Debug("init router") r.IO = make(map[state.NodeId]*IOPending) r.ForwardTable = bart.Table[RouteTableEntry]{} s.RouterState = &state.RouterState{ @@ -181,18 +189,20 @@ func (r *NylonRouter) Init(s *state.State) error { } } - s.Log.Debug("schedule router tasks") + r.logger.Debug("schedule router tasks") - s.Env.RepeatTask(func(s *state.State) error { + s.Env.RepeatTask(func() error { FullTableUpdate(s.RouterState, r) return nil }, state.RouteUpdateDelay) - s.Env.RepeatTask(func(s *state.State) error { + s.Env.RepeatTask(func() error { SolveStarvation(s.RouterState, r) return nil }, state.StarvationDelay) - s.Env.RepeatTask(flushIO, state.NeighbourIOFlushDelay) + s.Env.RepeatTask(func() error { + return n.flushIO() + }, state.NeighbourIOFlushDelay) return nil } @@ -250,46 +260,46 @@ func (r *NylonRouter) hasRecentlyAdvertised(prefix netip.Prefix) bool { return time.Now().Before(adv.Expiry) } -func checkNeigh(s *state.State, id state.NodeId) bool { +func (r *NylonRouter) checkNeigh(s *state.State, id state.NodeId) bool { for _, n := range s.Neighbours { if n.Id == id { return true } } - s.Log.Warn("received packet from unknown neighbour", "from", id) + r.logger.Warn("received packet from unknown neighbour", "from", id) return false } -func checkPrefix(s *state.State, prefix netip.Prefix) bool { +func (r *NylonRouter) checkPrefix(s *state.State, prefix netip.Prefix) bool { for _, p := range s.GetPrefixes() { if p == prefix { return true } } - s.Log.Warn("received packet for unknown prefix", "prefix", prefix) + r.logger.Warn("received packet for unknown prefix", "prefix", prefix) return false } -func checkNode(s *state.State, id state.NodeId) bool { +func (r *NylonRouter) checkNode(s *state.State, id state.NodeId) bool { ncfg := s.TryGetNode(id) if ncfg == nil { - s.Log.Warn("received packet from unknown node", "from", id) + r.logger.Warn("received packet from unknown node", "from", id) } return ncfg != nil } // packet handlers -func routerHandleRouteUpdate(s *state.State, node state.NodeId, update *protocol.Ny_Update) error { - r := Get[*NylonRouter](s) +func (r *NylonRouter) routerHandleRouteUpdate(node state.NodeId, update *protocol.Ny_Update) error { + s := r.State prefix := netip.Prefix{} err := prefix.UnmarshalBinary(update.Prefix) if err != nil { - s.Log.Warn("received update with invalid prefix", "prefix", update.Prefix, "err", err) + r.logger.Warn("received update with invalid prefix", "prefix", update.Prefix, "err", err) return nil } - if !checkNeigh(s, node) || - !checkPrefix(s, prefix) || - !checkNode(s, state.NodeId(update.RouterId)) { + if !r.checkNeigh(s, node) || + !r.checkPrefix(s, prefix) || + !r.checkNode(s, state.NodeId(update.RouterId)) { return nil } HandleNeighbourUpdate(s.RouterState, r, node, state.PubRoute{ @@ -305,33 +315,33 @@ func routerHandleRouteUpdate(s *state.State, node state.NodeId, update *protocol return nil } -func routerHandleAckRetract(s *state.State, neigh state.NodeId, update *protocol.Ny_AckRetract) error { - r := Get[*NylonRouter](s) +func (r *NylonRouter) routerHandleAckRetract(neigh state.NodeId, update *protocol.Ny_AckRetract) error { + s := r.State prefix := netip.Prefix{} err := prefix.UnmarshalBinary(update.Prefix) if err != nil { - s.Log.Warn("received ack retract with invalid prefix", "prefix", update.Prefix, "err", err) + r.logger.Warn("received ack retract with invalid prefix", "prefix", update.Prefix, "err", err) return nil } - if !checkPrefix(s, prefix) || - !checkNeigh(s, neigh) { + if !r.checkPrefix(s, prefix) || + !r.checkNeigh(s, neigh) { return nil } HandleAckRetract(s.RouterState, r, neigh, prefix) return nil } -func routerHandleSeqnoRequest(s *state.State, neigh state.NodeId, pkt *protocol.Ny_SeqnoRequest) error { - r := Get[*NylonRouter](s) +func (r *NylonRouter) routerHandleSeqnoRequest(neigh state.NodeId, pkt *protocol.Ny_SeqnoRequest) error { + s := r.State prefix := netip.Prefix{} err := prefix.UnmarshalBinary(pkt.Prefix) if err != nil { - s.Log.Warn("received seqno request with invalid prefix", "prefix", pkt.Prefix, "err", err) + r.logger.Warn("received seqno request with invalid prefix", "prefix", pkt.Prefix, "err", err) return nil } - if !checkNeigh(s, neigh) || - !checkPrefix(s, prefix) || - !checkNode(s, state.NodeId(pkt.RouterId)) { + if !r.checkNeigh(s, neigh) || + !r.checkPrefix(s, prefix) || + !r.checkNode(s, state.NodeId(pkt.RouterId)) { return nil } HandleSeqnoRequest(s.RouterState, r, neigh, state.Source{ @@ -341,9 +351,9 @@ func routerHandleSeqnoRequest(s *state.State, neigh state.NodeId, pkt *protocol. return nil } -func flushIO(s *state.State) error { - n := Get[*Nylon](s) - r := Get[*NylonRouter](s) +func (n *Nylon) flushIO() error { + s := n.State + r := n.Router for _, neigh := range s.Neighbours { // TODO, investigate effect of packet loss on control messages best := neigh.BestEndpoint() diff --git a/core/router_algo.go b/core/router_algo.go index 98f1e9f..531ea37 100644 --- a/core/router_algo.go +++ b/core/router_algo.go @@ -8,27 +8,10 @@ import ( "slices" "time" + "github.com/encodeous/nylon/log" "github.com/encodeous/nylon/state" ) -//go:generate stringer -type RouterEvent -type RouterEvent int - -// warn events - -const ( - InconsistentState RouterEvent = iota + 1000 - NoEpToNeighbour -) - -// info events -const ( - RouteChanged RouterEvent = iota + 2000 - RouteExpired - RoutePushed - RouteStarved -) - // Router is an interface that defines the underlying router operations type Router interface { SendRouteUpdate(neigh state.NodeId, advRoute state.PubRoute) @@ -38,7 +21,7 @@ type Router interface { BroadcastRequestSeqno(src state.Source, seqno uint16, hopCnt uint8) TableInsertRoute(prefix netip.Prefix, route state.SelRoute) TableDeleteRoute(prefix netip.Prefix) - Log(event RouterEvent, desc string, args ...any) + Log(event string, desc string, args ...any) } func updateFeasibility(router *state.RouterState, advRoute state.PubRoute) { @@ -127,13 +110,13 @@ func RunGC(s *state.RouterState, r Router) { if route.Metric == state.INF { // route expired and is INF, delete it delete(neigh.Routes, src) - r.Log(RouteExpired, "expired and removed", "neigh", neigh.Id, "src", src) + r.Log(log.EventRouteExpired, "expired and removed", "neigh", neigh.Id, "src", src) } else { // route expired, set metric to INF route.Metric = state.INF route.ExpireAt = time.Now().Add(state.RouteExpiryTime) // reset expiry time neigh.Routes[src] = route // update the route - r.Log(RouteExpired, "expired and marked", "neigh", neigh.Id, "src", src) + r.Log(log.EventRouteExpired, "expired and marked", "neigh", neigh.Id, "src", src) } } } @@ -180,7 +163,7 @@ func RunGC(s *state.RouterState, r Router) { func retract(s *state.RouterState, r Router, prefix netip.Prefix) { tblEntry, ok := s.Routes[prefix] if !ok { - r.Log(InconsistentState, "attempted to retract non-existent route", "prefix", prefix) + r.Log(log.EventInconsistentState, "attempted to retract non-existent route", "prefix", prefix) return // route does not exist } tblEntry.Metric = state.INF @@ -268,7 +251,7 @@ func HandleSeqnoRequest(s *state.RouterState, r Router, fromNeigh state.NodeId, func HandleAckRetract(s *state.RouterState, r Router, neighId state.NodeId, prefix netip.Prefix) { rt, ok := s.Routes[prefix] if !ok { - r.Log(InconsistentState, "attempted to ack the retraction of a non-existent route", "prefix", prefix) + r.Log(log.EventInconsistentState, "attempted to ack the retraction of a non-existent route", "prefix", prefix) return // route does not exist } if !slices.Contains(rt.RetractedBy, neighId) { @@ -489,7 +472,7 @@ func ComputeRoutes(s *state.RouterState, r Router) { for _, neigh := range s.Neighbours { bestEp := neigh.BestEndpoint() if bestEp == nil { - r.Log(NoEpToNeighbour, "no endpoint to neighbour", "neigh", neigh.Id) + r.Log(log.EventNoEndpointToNeigh, "no endpoint to neighbour", "neigh", neigh.Id) } // We refer to our current node as A, our neighbour as B, and S as our source. @@ -569,10 +552,10 @@ func ComputeRoutes(s *state.RouterState, r Router) { oldRoute, exists := s.Routes[prefix] if !exists || oldRoute.Metric == state.INF { r.TableInsertRoute(prefix, newRoute) - r.Log(RouteChanged, "inserted", "prefix", prefix, "new", newRoute) + r.Log(log.EventRouteInserted, "inserted", "prefix", prefix, "new", newRoute) } else if oldRoute.Nh != newRoute.Nh { r.TableInsertRoute(prefix, newRoute) - r.Log(RouteChanged, "updated", "prefix", prefix, "old", oldRoute, "new", newRoute) + r.Log(log.EventRouteUpdated, "updated", "prefix", prefix, "old", oldRoute, "new", newRoute) } if !exists || oldRoute.Source.NodeId != newRoute.Source.NodeId || @@ -580,7 +563,7 @@ func ComputeRoutes(s *state.RouterState, r Router) { abs(int(newRoute.Metric)-int(oldRoute.Metric)) > int(state.LargeChangeThreshold) && newRoute.Metric != state.INF { // criteria met, send update updateFeasibility(s, newRoute.PubRoute) - r.Log(RoutePushed, "major change", "prefix", prefix, "old", oldRoute, "new", newRoute) + r.Log(log.EventMajorRouteChange, "major change", "prefix", prefix, "old", oldRoute, "new", newRoute) r.BroadcastSendRouteUpdate(newRoute.PubRoute) } } @@ -593,7 +576,7 @@ func ComputeRoutes(s *state.RouterState, r Router) { if oldRoute.Metric != state.INF { retract(s, r, prefix) r.TableDeleteRoute(prefix) - r.Log(RouteChanged, "retracted", "prefix", prefix, "old", oldRoute) + r.Log(log.EventRouteRetracted, "retracted", "prefix", prefix, "old", oldRoute) // Add the retracted route back as INF so it can be held oldRoute.Metric = state.INF newTable[prefix] = oldRoute @@ -648,7 +631,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.Log(RouteStarved, "requested seqno", "src", src, "seqno", router.Sources[src].Seqno+1) + r.Log(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 726fbd8..8e1ea4f 100644 --- a/core/router_harness.go +++ b/core/router_harness.go @@ -104,7 +104,7 @@ func (h *RouterHarness) BroadcastRequestSeqno(src state.Source, seqno uint16, ho h.actions = append(h.actions, MakeEvent("BROADCAST_REQUEST_SEQNO", src, seqno, hopCnt)) } -func (h *RouterHarness) Log(event RouterEvent, desc string, args ...any) { +func (h *RouterHarness) Log(event string, desc string, args ...any) { x := make([]any, 0) x = append(x, event) x = append(x, desc) diff --git a/core/routerevent_string.go b/core/routerevent_string.go deleted file mode 100644 index baf79de..0000000 --- a/core/routerevent_string.go +++ /dev/null @@ -1,40 +0,0 @@ -// Code generated by "stringer -type RouterEvent"; DO NOT EDIT. - -package core - -import "strconv" - -func _() { - // An "invalid array index" compiler error signifies that the constant values have changed. - // Re-run the stringer command to generate them again. - var x [1]struct{} - _ = x[InconsistentState-1000] - _ = x[NoEpToNeighbour-1001] - _ = x[RouteChanged-2000] - _ = x[RouteExpired-2001] - _ = x[RoutePushed-2002] - _ = x[RouteStarved-2003] -} - -const ( - _RouterEvent_name_0 = "InconsistentStateNoEpToNeighbour" - _RouterEvent_name_1 = "RouteChangedRouteExpiredRoutePushedRouteStarved" -) - -var ( - _RouterEvent_index_0 = [...]uint8{0, 17, 32} - _RouterEvent_index_1 = [...]uint8{0, 12, 24, 35, 47} -) - -func (i RouterEvent) String() string { - switch { - case 1000 <= i && i <= 1001: - i -= 1000 - return _RouterEvent_name_0[_RouterEvent_index_0[i]:_RouterEvent_index_0[i+1]] - case 2000 <= i && i <= 2003: - i -= 2000 - return _RouterEvent_name_1[_RouterEvent_index_1[i]:_RouterEvent_index_1[i+1]] - default: - return "RouterEvent(" + strconv.FormatInt(int64(i), 10) + ")" - } -} diff --git a/core/sys_physical.go b/core/sys_physical.go index d356924..e584cfa 100644 --- a/core/sys_physical.go +++ b/core/sys_physical.go @@ -7,6 +7,7 @@ import ( "runtime" "strings" + "github.com/encodeous/nylon/log" "github.com/encodeous/nylon/polyamide/conn" "github.com/encodeous/nylon/polyamide/device" "github.com/encodeous/nylon/polyamide/tun" @@ -29,18 +30,20 @@ func NewWireGuardDevice(s *state.State, n *Nylon) (dev *device.Device, tunDevice itfName = realInterfaceName } + wgLog := s.Log.With("module", log.ScopePolyamide) + // setup WireGuard dev = device.NewDevice(tdev, conn.NewDefaultBind(), &device.Logger{ Verbosef: func(format string, args ...any) { if state.DBG_log_wireguard { - s.Log.Debug(fmt.Sprintf(format, args...)) + wgLog.Debug(fmt.Sprintf(format, args...)) } }, Errorf: func(format string, args ...any) { if strings.Contains(format, "Failed to send PolySock packets") { return } - s.Log.Error(fmt.Sprintf(format, args...)) + wgLog.Error(fmt.Sprintf(format, args...)) }, }) diff --git a/core/sys_virtual.go b/core/sys_virtual.go index 5d465af..284ee20 100644 --- a/core/sys_virtual.go +++ b/core/sys_virtual.go @@ -4,11 +4,13 @@ package core import ( "fmt" + "strings" + + "github.com/encodeous/nylon/log" "github.com/encodeous/nylon/polyamide/conn" "github.com/encodeous/nylon/polyamide/device" "github.com/encodeous/nylon/polyamide/tun" "github.com/encodeous/nylon/state" - "strings" ) type VirtualNet interface { @@ -28,18 +30,20 @@ func NewWireGuardDevice(s *state.State, n *Nylon) (dev *device.Device, tunDevice bind := vn.Bind(s.Id) tdev := vn.Tun(s.Id) + wgLog := s.Log.With("module", log.ScopePolyamide) + // setup WireGuard dev = device.NewDevice(tdev, bind, &device.Logger{ Verbosef: func(format string, args ...any) { if state.DBG_log_wireguard { - s.Log.Debug(fmt.Sprintf(format, args...)) + wgLog.Debug(fmt.Sprintf(format, args...)) } }, Errorf: func(format string, args ...any) { if strings.Contains(format, "Failed to send PolySock packets") { return } - s.Log.Error(fmt.Sprintf(format, args...)) + wgLog.Error(fmt.Sprintf(format, args...)) }, }) diff --git a/core/utils.go b/core/utils.go index fc8f609..bcb302b 100644 --- a/core/utils.go +++ b/core/utils.go @@ -1,7 +1,7 @@ package core import ( - "reflect" + "github.com/encodeous/nylon/state" ) @@ -29,10 +29,6 @@ func SeqnoGe(a, b uint16) bool { return !SeqnoLt(a, b) } -func Get[T state.NyModule](s *state.State) T { - t := reflect.TypeFor[T]() - return s.Modules[t.String()].(T) -} func abs(a int) int { if a < 0 { diff --git a/e2e/connectivity_test.go b/e2e/connectivity_test.go index f8aa355..4600dfc 100644 --- a/e2e/connectivity_test.go +++ b/e2e/connectivity_test.go @@ -10,6 +10,7 @@ import ( ) func TestConnectivity(t *testing.T) { + t.Parallel() // Use a specific subnet for this test to avoid conflicts h := NewHarness(t) diff --git a/e2e/distribution_test.go b/e2e/distribution_test.go index 93bf9cc..78f73679 100644 --- a/e2e/distribution_test.go +++ b/e2e/distribution_test.go @@ -16,6 +16,7 @@ import ( ) func TestDistribution(t *testing.T) { + t.Parallel() h := NewHarness(t) ctx := context.Background() diff --git a/e2e/harness.go b/e2e/harness.go index 23a13b8..8eaf2fc 100644 --- a/e2e/harness.go +++ b/e2e/harness.go @@ -17,20 +17,22 @@ import ( "time" "unsafe" - "github.com/docker/docker/api/types/container" - "github.com/docker/docker/api/types/network" - "github.com/docker/docker/pkg/stdcopy" "github.com/encodeous/nylon/state" "github.com/goccy/go-yaml" + "github.com/moby/moby/api/pkg/stdcopy" + "github.com/moby/moby/api/types/container" + "github.com/moby/moby/api/types/network" + "github.com/moby/moby/client" "github.com/testcontainers/testcontainers-go" tcnetwork "github.com/testcontainers/testcontainers-go/network" "github.com/testcontainers/testcontainers-go/wait" ) -const ( +var ( ImageName = "nylon-debug:latest" AppPort = "57175/udp" WaitTimeout = 2 * time.Minute + networkMu sync.Mutex ) type Harness struct { @@ -67,6 +69,9 @@ func NewHarness(t *testing.T) *Harness { rootDir = parent } + networkMu.Lock() + defer networkMu.Unlock() + subnet, gateway, err := AllocateDockerSubnet(ctx) if err != nil { t.Fatal(err) @@ -81,8 +86,8 @@ func NewHarness(t *testing.T) *Harness { Driver: "default", Config: []network.IPAMConfig{ { - Subnet: subnet, - Gateway: gateway, + Subnet: netip.MustParsePrefix(subnet), + Gateway: netip.MustParseAddr(gateway), }, }, })) @@ -159,7 +164,7 @@ func (h *Harness) StartNode(name string, ip string, centralConfigPath, nodeConfi if ip != "" { if s, ok := m[h.Network.Name]; ok { s.IPAMConfig = &network.EndpointIPAMConfig{ - IPv4Address: ip, + IPv4Address: netip.MustParseAddr(ip), } } } @@ -256,11 +261,11 @@ func (h *Harness) StartTrace(nodeName string) { go func() { time.Sleep(time.Second) - execOptions := container.ExecOptions{ + execOptions := client.ExecCreateOptions{ Cmd: []string{"nylon", "inspect", "nylon0", "--trace"}, AttachStdout: true, AttachStderr: true, - Tty: false, + TTY: false, } docker := cont.(*testcontainers.DockerContainer) @@ -269,12 +274,12 @@ func (h *Harness) StartTrace(nodeName string) { y := GetUnexportedField(v.Elem().FieldByName("provider")).(*testcontainers.DockerProvider) cli := y.Client() - execIDResp, err := cli.ContainerExecCreate(h.ctx, cont.GetContainerID(), execOptions) + execIDResp, err := cli.ExecCreate(h.ctx, cont.GetContainerID(), execOptions) if err != nil { h.t.Fatalf("Failed to start trace for %s: %v", nodeName, err) } - resp, err := cli.ContainerExecAttach(h.ctx, execIDResp.ID, container.ExecAttachOptions{}) + resp, err := cli.ExecAttach(h.ctx, execIDResp.ID, client.ExecAttachOptions{}) if err != nil { h.t.Fatalf("Failed to start trace for %s: %v", nodeName, err) } @@ -439,7 +444,7 @@ func (h *Harness) StartDNS(name string, ip string, corefile string, zones map[st if ip != "" { if s, ok := m[h.Network.Name]; ok { s.IPAMConfig = &network.EndpointIPAMConfig{ - IPv4Address: ip, + IPv4Address: netip.MustParseAddr(ip), } } } diff --git a/e2e/healthcheck_test.go b/e2e/healthcheck_test.go index 7821488..8cf3a44 100644 --- a/e2e/healthcheck_test.go +++ b/e2e/healthcheck_test.go @@ -15,6 +15,7 @@ import ( ) func TestHealthcheckPing(t *testing.T) { + t.Parallel() // Use a specific subnet for this test to avoid conflicts h := NewHarness(t) @@ -52,24 +53,22 @@ func TestHealthcheckPing(t *testing.T) { // make node 1 and node 2 both advertise 10.0.0.4/32 // 1 would be default - n1Metric := uint32(10) central.Routers[0].Prefixes = []state.PrefixHealthWrapper{ { &state.PingPrefixHealth{ Prefix: netip.MustParsePrefix("10.0.1.4/32"), Addr: netip.MustParseAddr("10.0.1.4"), - Metric: &n1Metric, + Metric: new(uint32(10)), }, }, } // 2 would be fallback - n2Metric := uint32(1000) central.Routers[1].Prefixes = []state.PrefixHealthWrapper{ { &state.PingPrefixHealth{ Prefix: netip.MustParsePrefix("10.0.1.4/32"), Addr: netip.MustParseAddr("10.0.1.4"), - Metric: &n2Metric, + Metric: new(uint32(1000)), }, }, } @@ -127,6 +126,7 @@ func TestHealthcheckPing(t *testing.T) { } func TestHealthcheckHTTP(t *testing.T) { + t.Parallel() h := NewHarness(t) // IPs @@ -162,13 +162,12 @@ func TestHealthcheckHTTP(t *testing.T) { // Configure Primary with HTTP check (Metric 10) // primMetric := uint32(10) - checkDelay := 1 * time.Second central.Routers[1].Prefixes = []state.PrefixHealthWrapper{ { &state.HTTPPrefixHealth{ Prefix: servicePrefix, URL: fmt.Sprintf("http://%s:8080/health", serviceIP), - Delay: &checkDelay, + Delay: new(1 * time.Second), // Metric: &primMetric, // Remove override to use dynamic metric (RTT or INF) }, }, diff --git a/e2e/json_logging_test.go b/e2e/json_logging_test.go index 2f13f2d..41ba33e 100644 --- a/e2e/json_logging_test.go +++ b/e2e/json_logging_test.go @@ -10,6 +10,7 @@ import ( ) func TestJSONLogging(t *testing.T) { + t.Parallel() h := NewHarness(t) node1Key := state.GenerateKey() diff --git a/e2e/main_test.go b/e2e/main_test.go index f71935f..9e636a2 100644 --- a/e2e/main_test.go +++ b/e2e/main_test.go @@ -9,7 +9,7 @@ import ( "path/filepath" "testing" - "github.com/docker/docker/api/types/build" + "github.com/moby/moby/client" "github.com/testcontainers/testcontainers-go" ) @@ -49,7 +49,7 @@ func buildImage() error { KeepImage: true, Repo: "nylon-debug", Tag: "latest", - BuildOptionsModifier: func(buildOptions *build.ImageBuildOptions) { + BuildOptionsModifier: func(buildOptions *client.ImageBuildOptions) { buildOptions.Target = "debug" }, }, diff --git a/e2e/network_allocator.go b/e2e/network_allocator.go index 980cf6f..7f522b6 100644 --- a/e2e/network_allocator.go +++ b/e2e/network_allocator.go @@ -5,7 +5,7 @@ import ( "fmt" "net/netip" - dockernetwork "github.com/docker/docker/api/types/network" + "github.com/moby/moby/client" "github.com/testcontainers/testcontainers-go" ) @@ -18,23 +18,19 @@ func AllocateDockerSubnet(ctx context.Context) (string, string, error) { } defer provider.Close() - networks, err := provider.Client().NetworkList(ctx, dockernetwork.ListOptions{}) + networks, err := provider.Client().NetworkList(ctx, client.NetworkListOptions{}) if err != nil { return "", "", err } var existing []netip.Prefix - for _, nw := range networks { + for _, nw := range networks.Items { for _, cfg := range nw.IPAM.Config { - if cfg.Subnet == "" { + if !cfg.Subnet.IsValid() { continue } - prefix, err := netip.ParsePrefix(cfg.Subnet) - if err != nil { - continue - } - if prefix.Addr().Is4() { - existing = append(existing, prefix.Masked()) + if cfg.Subnet.Addr().Is4() { + existing = append(existing, cfg.Subnet.Masked()) } } } diff --git a/e2e/passive_roaming_test.go b/e2e/passive_roaming_test.go index 43c73f2..e352046 100644 --- a/e2e/passive_roaming_test.go +++ b/e2e/passive_roaming_test.go @@ -11,15 +11,16 @@ import ( "testing" "time" - "github.com/docker/docker/api/types/container" - "github.com/docker/docker/api/types/network" "github.com/encodeous/nylon/state" "github.com/goccy/go-yaml" + "github.com/moby/moby/api/types/container" + "github.com/moby/moby/api/types/network" "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/wait" ) func TestPassiveRoaming(t *testing.T) { + t.Parallel() h := NewHarness(t) ctx := context.Background() @@ -144,7 +145,7 @@ func TestPassiveRoaming(t *testing.T) { EndpointSettingsModifier: func(m map[string]*network.EndpointSettings) { if s, ok := m[h.Network.Name]; ok { s.IPAMConfig = &network.EndpointIPAMConfig{ - IPv4Address: clientContainerIP, + IPv4Address: netip.MustParseAddr(clientContainerIP), } } }, diff --git a/e2e/recovery_test.go b/e2e/recovery_test.go index 4780a3b..ac08fe2 100644 --- a/e2e/recovery_test.go +++ b/e2e/recovery_test.go @@ -11,6 +11,7 @@ import ( ) func TestRecoveryExample(t *testing.T) { + t.Parallel() h := NewHarness(t) // Node names diff --git a/e2e/resolution_test.go b/e2e/resolution_test.go index decf349..1c530c2 100644 --- a/e2e/resolution_test.go +++ b/e2e/resolution_test.go @@ -15,6 +15,7 @@ import ( ) func TestEndpointResolution(t *testing.T) { + t.Parallel() h := NewHarness(t) dnsIP := GetIP(h.Subnet, 100) @@ -115,6 +116,7 @@ _nylon._udp.srv.example.com. 0 IN SRV 10 10 57175 node2.example.com. } func TestDynamicResolution(t *testing.T) { + t.Parallel() h := NewHarness(t) dnsIP := GetIP(h.Subnet, 100) diff --git a/go.mod b/go.mod index 2ab3e7c..0b4e2f5 100644 --- a/go.mod +++ b/go.mod @@ -1,11 +1,10 @@ module github.com/encodeous/nylon -go 1.25.4 +go 1.26.2 require ( github.com/cilium/cilium v1.18.9 github.com/digineo/go-ping v1.2.0 - github.com/docker/docker v28.5.1+incompatible github.com/dustin/go-broadcast v0.0.0-20211018055107-71439988bd91 github.com/encodeous/metric v0.0.0-20251111175231-f339c2f7c4bd github.com/encodeous/tint v1.2.0 @@ -14,14 +13,16 @@ require ( github.com/google/go-cmp v0.7.0 github.com/jellydator/ttlcache/v3 v3.4.0 github.com/kmahyyg/go-network-compo v0.2.10 + github.com/moby/moby/api v1.54.2 + github.com/moby/moby/client v0.4.1 github.com/samber/slog-multi v1.5.0 github.com/spf13/cobra v1.10.1 github.com/stretchr/testify v1.11.1 - github.com/testcontainers/testcontainers-go v0.40.0 + github.com/testcontainers/testcontainers-go v0.42.0 go.step.sm/crypto v0.70.0 go.uber.org/goleak v1.3.0 - golang.org/x/crypto v0.46.0 - golang.org/x/net v0.48.0 + golang.org/x/crypto v0.48.0 + golang.org/x/net v0.49.0 golang.org/x/sys v0.42.0 golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 google.golang.org/protobuf v1.36.10 @@ -43,9 +44,9 @@ require ( github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/digineo/go-logwrap v0.0.0-20181106161722-a178c58ea3f0 // indirect github.com/distribution/reference v0.6.0 // indirect - github.com/docker/go-connections v0.6.0 // indirect + github.com/docker/go-connections v0.7.0 // indirect github.com/docker/go-units v0.5.0 // indirect - github.com/ebitengine/purego v0.8.4 // indirect + github.com/ebitengine/purego v0.10.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect @@ -53,44 +54,40 @@ require ( github.com/google/btree v1.1.3 // indirect github.com/google/uuid v1.6.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect - github.com/klauspost/compress v1.18.0 // indirect + github.com/klauspost/compress v1.18.5 // indirect github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect github.com/magiconair/properties v1.8.10 // indirect github.com/moby/docker-image-spec v1.3.1 // indirect - github.com/moby/go-archive v0.1.0 // indirect - github.com/moby/patternmatcher v0.6.0 // indirect + github.com/moby/go-archive v0.2.0 // indirect + github.com/moby/patternmatcher v0.6.1 // indirect github.com/moby/sys/sequential v0.6.0 // indirect github.com/moby/sys/user v0.4.0 // indirect github.com/moby/sys/userns v0.1.0 // indirect github.com/moby/term v0.5.2 // indirect - github.com/morikuni/aec v1.0.0 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.1 // indirect - github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect - github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect + github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect github.com/samber/lo v1.51.0 // indirect github.com/samber/slog-common v0.19.0 // indirect - github.com/shirou/gopsutil/v4 v4.25.6 // indirect - github.com/sirupsen/logrus v1.9.3 // indirect + github.com/shirou/gopsutil/v4 v4.26.3 // indirect + github.com/sirupsen/logrus v1.9.4 // indirect github.com/spf13/pflag v1.0.10 // indirect - github.com/tklauser/go-sysconf v0.3.12 // indirect - github.com/tklauser/numcpus v0.6.1 // indirect + github.com/tklauser/go-sysconf v0.3.16 // indirect + github.com/tklauser/numcpus v0.11.0 // indirect github.com/vishvananda/netlink v1.3.2-0.20250926155043-cd3cb2e12c97 // indirect github.com/vishvananda/netns v0.0.5 // indirect github.com/yusufpapurcu/wmi v1.2.4 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect go.opentelemetry.io/otel v1.43.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0 // indirect go.opentelemetry.io/otel/metric v1.43.0 // indirect go.opentelemetry.io/otel/sdk v1.43.0 // indirect go.opentelemetry.io/otel/sdk/metric v1.43.0 // indirect go.opentelemetry.io/otel/trace v1.43.0 // indirect - go.opentelemetry.io/proto/otlp v1.9.0 // indirect go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect golang.org/x/sync v0.19.0 // indirect - golang.org/x/text v0.32.0 // indirect + golang.org/x/text v0.34.0 // indirect golang.org/x/time v0.12.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 5b835bd..70954d5 100644 --- a/go.sum +++ b/go.sum @@ -25,10 +25,8 @@ github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7np github.com/cpuguy83/dockercfg v0.3.2 h1:DlJTyZGBDlXqUZ2Dk2Q3xHs/FtnooJJVaad2S9GKorA= github.com/cpuguy83/dockercfg v0.3.2/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= -github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= -github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s= +github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/digineo/go-logwrap v0.0.0-20181106161722-a178c58ea3f0 h1:OT/LKmj81wMymnWXaKaKBR9n1vPlu+GC0VVKaZP6kzs= @@ -37,16 +35,16 @@ github.com/digineo/go-ping v1.2.0 h1:/9vEsoCRtQvol5vRMA2pE8guuhUVDSJB2ok2nKuJJbA github.com/digineo/go-ping v1.2.0/go.mod h1:cXJTVTs7mthQ41c/nWykPYuhlDQwCN0ba5LyOnyYv8Y= github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= -github.com/docker/docker v28.5.1+incompatible h1:Bm8DchhSD2J6PsFzxC35TZo4TLGR2PdW/E69rU45NhM= -github.com/docker/docker v28.5.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/go-connections v0.6.0 h1:LlMG9azAe1TqfR7sO+NJttz1gy6KO7VJBh+pMmjSD94= -github.com/docker/go-connections v0.6.0/go.mod h1:AahvXYshr6JgfUJGdDCs2b5EZG/vmaMAntpSFH5BFKE= +github.com/docker/docker v28.3.3+incompatible h1:Dypm25kh4rmk49v1eiVbsAtpAsYURjYkaKubwuBdxEI= +github.com/docker/docker v28.3.3+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/go-connections v0.7.0 h1:6SsRfJddP22WMrCkj19x9WKjEDTB+ahsdiGYf0mN39c= +github.com/docker/go-connections v0.7.0/go.mod h1:no1qkHdjq7kLMGUXYAduOhYPSJxxvgWBh7ogVvptn3Q= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/dustin/go-broadcast v0.0.0-20211018055107-71439988bd91 h1:jAUM3D1KIrJmwx60DKB+a/qqM69yHnu6otDGVa2t0vs= github.com/dustin/go-broadcast v0.0.0-20211018055107-71439988bd91/go.mod h1:8rK6Kbo1Jd6sK22b24aPVgAm3jlNy1q1ft+lBALdIqA= -github.com/ebitengine/purego v0.8.4 h1:CF7LEKg5FFOsASUj0+QwaXf8Ht6TlFxg09+S9wz0omw= -github.com/ebitengine/purego v0.8.4/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= +github.com/ebitengine/purego v0.10.0 h1:QIw4xfpWT6GWTzaW5XEKy3HXoqrJGx1ijYHzTF0/ISU= +github.com/ebitengine/purego v0.10.0/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= github.com/encodeous/metric v0.0.0-20251111175231-f339c2f7c4bd h1:B32Ob80QTv5MomcVt709TsiWyD0QrpUYtnwW1jQFNlE= github.com/encodeous/metric v0.0.0-20251111175231-f339c2f7c4bd/go.mod h1:DiXCPJtfZYioejF9zv9wfs3TXqWWglKGQ20DsBNVWVw= github.com/encodeous/tint v1.2.0 h1:1Y+32Iu+C8MXBoNjsM4YDf6iAkcks7csAI9f7b4fr8k= @@ -71,14 +69,12 @@ github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 h1:8Tjv8EJ+pM1xP8mK6egEbD1OgnVTyacbefKhmbLhIhU= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2/go.mod h1:pkJQ2tZHJ0aFOVEEot6oZmaVEZcRme73eIFmhiVuRWs= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jellydator/ttlcache/v3 v3.4.0 h1:YS4P125qQS0tNhtL6aeYkheEaB/m8HCqdMMP4mnWdTY= github.com/jellydator/ttlcache/v3 v3.4.0/go.mod h1:Hw9EgjymziQD3yGsQdf1FqFdpp7YjFMd4Srg5EJlgD4= -github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= -github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= +github.com/klauspost/compress v1.18.5 h1:/h1gH5Ce+VWNLSWqPzOVn6XBO+vJbCNGvjoaGBFW2IE= +github.com/klauspost/compress v1.18.5/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ= github.com/kmahyyg/go-network-compo v0.2.10 h1:H5rZ59xxmaZsIucz5FGV56ww+nzR3k1RFaKEN2bYRog= github.com/kmahyyg/go-network-compo v0.2.10/go.mod h1:LN1qGQuWqk6v27GXWWiXGIaYmV8TUPZrPGgm0LFwmbU= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= @@ -91,12 +87,14 @@ github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8S github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= -github.com/moby/go-archive v0.1.0 h1:Kk/5rdW/g+H8NHdJW2gsXyZ7UnzvJNOy6VKJqueWdcQ= -github.com/moby/go-archive v0.1.0/go.mod h1:G9B+YoujNohJmrIYFBpSd54GTUB4lt9S+xVQvsJyFuo= -github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk= -github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= -github.com/moby/sys/atomicwriter v0.1.0 h1:kw5D/EqkBwsBFi0ss9v1VG3wIkVhzGvLklJ+w3A14Sw= -github.com/moby/sys/atomicwriter v0.1.0/go.mod h1:Ul8oqv2ZMNHOceF643P6FKPXeCmYtlQMvpizfsSoaWs= +github.com/moby/go-archive v0.2.0 h1:zg5QDUM2mi0JIM9fdQZWC7U8+2ZfixfTYoHL7rWUcP8= +github.com/moby/go-archive v0.2.0/go.mod h1:mNeivT14o8xU+5q1YnNrkQVpK+dnNe/K6fHqnTg4qPU= +github.com/moby/moby/api v1.54.2 h1:wiat9QAhnDQjA7wk1kh/TqHz2I1uUA7M7t9SAl/JNXg= +github.com/moby/moby/api v1.54.2/go.mod h1:+RQ6wluLwtYaTd1WnPLykIDPekkuyD/ROWQClE83pzs= +github.com/moby/moby/client v0.4.1 h1:DMQgisVoMkmMs7fp3ROSdiBnoAu8+vo3GggFl06M/wY= +github.com/moby/moby/client v0.4.1/go.mod h1:z52C9O2POPOsnxZAy//WtKcQ32P+jT/NGeXu/7nfjGQ= +github.com/moby/patternmatcher v0.6.1 h1:qlhtafmr6kgMIJjKJMDmMWq7WLkKIo23hsrpR3x084U= +github.com/moby/patternmatcher v0.6.1/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU= github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko= github.com/moby/sys/user v0.4.0 h1:jhcMKit7SA80hivmFJcbB1vqmw//wU61Zdui2eQXuMs= @@ -105,19 +103,14 @@ github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28= github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ= github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc= -github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= -github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040= github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw= -github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= +github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU= +github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= @@ -127,27 +120,25 @@ github.com/samber/slog-common v0.19.0 h1:fNcZb8B2uOLooeYwFpAlKjkQTUafdjfqKcwcC89 github.com/samber/slog-common v0.19.0/go.mod h1:dTz+YOU76aH007YUU0DffsXNsGFQRQllPQh9XyNoA3M= github.com/samber/slog-multi v1.5.0 h1:UDRJdsdb0R5vFQFy3l26rpX3rL3FEPJTJ2yKVjoiT1I= github.com/samber/slog-multi v1.5.0/go.mod h1:im2Zi3mH/ivSY5XDj6LFcKToRIWPw1OcjSVSdXt+2d0= -github.com/shirou/gopsutil/v4 v4.25.6 h1:kLysI2JsKorfaFPcYmcJqbzROzsBWEOAtw6A7dIfqXs= -github.com/shirou/gopsutil/v4 v4.25.6/go.mod h1:PfybzyydfZcN+JMMjkF6Zb8Mq1A/VcogFFg7hj50W9c= -github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= -github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/shirou/gopsutil/v4 v4.26.3 h1:2ESdQt90yU3oXF/CdOlRCJxrP+Am1aBYubTMTfxJ1qc= +github.com/shirou/gopsutil/v4 v4.26.3/go.mod h1:LZ6ewCSkBqUpvSOf+LsTGnRinC6iaNUNMGBtDkJBaLQ= +github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w= +github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g= github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s= github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0= github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= -github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/objx v0.5.3 h1:jmXUvGomnU1o3W/V5h2VEradbpJDwGrzugQQvL0POH4= +github.com/stretchr/objx v0.5.3/go.mod h1:rDQraq+vQZU7Fde9LOZLr8Tax6zZvy4kuNKF+QYS+U0= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= -github.com/testcontainers/testcontainers-go v0.40.0 h1:pSdJYLOVgLE8YdUY2FHQ1Fxu+aMnb6JfVz1mxk7OeMU= -github.com/testcontainers/testcontainers-go v0.40.0/go.mod h1:FSXV5KQtX2HAMlm7U3APNyLkkap35zNLxukw9oBi/MY= -github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU= -github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= -github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= -github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= +github.com/testcontainers/testcontainers-go v0.42.0 h1:He3IhTzTZOygSXLJPMX7n44XtK+qhjat1nI9cneBbUY= +github.com/testcontainers/testcontainers-go v0.42.0/go.mod h1:vZjdY1YmUA1qEForxOIOazfsrdyORJAbhi0bp8plN30= +github.com/tklauser/go-sysconf v0.3.16 h1:frioLaCQSsF5Cy1jgRBrzr6t502KIIwQ0MArYICU0nA= +github.com/tklauser/go-sysconf v0.3.16/go.mod h1:/qNL9xxDhc7tx3HSRsLWNnuzbVfh3e7gh/BmM179nYI= +github.com/tklauser/numcpus v0.11.0 h1:nSTwhKH5e1dMNsCdVBukSZrURJRoHbSEQjdEbY+9RXw= +github.com/tklauser/numcpus v0.11.0/go.mod h1:z+LwcLq54uWZTX0u/bGobaV34u6V7KNlTZejzM6/3MQ= github.com/vishvananda/netlink v1.3.2-0.20250926155043-cd3cb2e12c97 h1:fLr9AqDqfl9FLLOlPPPs9QujTXDvehe9fH1hR7JY0jY= github.com/vishvananda/netlink v1.3.2-0.20250926155043-cd3cb2e12c97/go.mod h1:lEui7SPMd9fgxzHVGRAvTxsBGCF6PRH81o2kLWLWHgw= github.com/vishvananda/netns v0.0.5 h1:DfiHV+j8bA32MFM7bfEunvT8IAqQ/NzSJHtcmW5zdEY= @@ -160,10 +151,6 @@ go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6h go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q= go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I= go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0 h1:f0cb2XPmrqn4XMy9PNliTgRKJgS5WcL/u0/WRYGz4t0= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0/go.mod h1:vnakAaFckOMiMtOIhFI2MNH4FYrZzXCYxmb1LlhoGz8= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.32.0 h1:cMyu9O88joYEaI47CnQkxO1XZdpoTF9fEnW2duIddhw= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.32.0/go.mod h1:6Am3rn7P9TVVeXYG+wtcGE7IE1tsQ+bP3AuWcKt/gOI= go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM= go.opentelemetry.io/otel/metric v1.43.0/go.mod h1:RDnPtIxvqlgO8GRW18W6Z/4P462ldprJtfxHxyKd2PY= go.opentelemetry.io/otel/sdk v1.43.0 h1:pi5mE86i5rTeLXqoF/hhiBtUNcrAGHLKQdhg4h4V9Dg= @@ -172,53 +159,42 @@ go.opentelemetry.io/otel/sdk/metric v1.43.0 h1:S88dyqXjJkuBNLeMcVPRFXpRw2fuwdvfC go.opentelemetry.io/otel/sdk/metric v1.43.0/go.mod h1:C/RJtwSEJ5hzTiUz5pXF1kILHStzb9zFlIEe85bhj6A= go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A= go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0= -go.opentelemetry.io/proto/otlp v1.9.0 h1:l706jCMITVouPOqEnii2fIAuO3IVGBRPV5ICjceRb/A= -go.opentelemetry.io/proto/otlp v1.9.0/go.mod h1:xE+Cx5E/eEHw+ISFkwPLwCZefwVjY+pqKg1qcK03+/4= go.step.sm/crypto v0.70.0 h1:Q9Ft7N637mucyZcHZd1+0VVQJVwDCKqcb9CYcYi7cds= go.step.sm/crypto v0.70.0/go.mod h1:pzfUhS5/ue7ev64PLlEgXvhx1opwbhFCjkvlhsxVds0= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M= go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y= -golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU= -golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0= -golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU= -golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY= +golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts= +golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos= +golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o= +golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8= golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo= golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= -golang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q= -golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg= -golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU= -golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY= +golang.org/x/term v0.40.0 h1:36e4zGLqU4yhjlmxEaagx2KuYbJq3EwY8K943ZsHcvg= +golang.org/x/term v0.40.0/go.mod h1:w2P8uVp06p2iyKKuvXIm7N/y0UCRt3UfJTfZ7oOpglM= +golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk= +golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA= golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg= golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI= -google.golang.org/genproto v0.0.0-20250603155806-513f23925822 h1:rHWScKit0gvAPuOnu87KpaYtjK5zBMLcULh7gxkCXu4= -google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 h1:fCvbg86sFXwdrl5LgVcTEvNC+2txB5mgROGmRL5mrls= -google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:+rXWjjaukWZun3mLfjmVnQi18E1AsFbDN9QdJ5YXLto= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 h1:gRkg/vSppuSQoDjxyiGfN4Upv/h/DQmIR10ZU8dh4Ww= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= -google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE= -google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q= gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA= gvisor.dev/gvisor v0.0.0-20250503011706-39ed1f5ac29c h1:m/r7OM+Y2Ty1sgBQ7Qb27VgIMBW8ZZhT4gLnUyDIhzI= gvisor.dev/gvisor v0.0.0-20250503011706-39ed1f5ac29c/go.mod h1:3r5CMtNQMKIvBlrmM9xWUNamjKBYPOWyXOjmg5Kts3g= +pgregory.net/rapid v1.2.0 h1:keKAYRcjm+e1F0oAuU5F5+YPAWcyxNNRK2wud503Gnk= +pgregory.net/rapid v1.2.0/go.mod h1:PY5XlDGj0+V1FCq0o192FdRhpKHGTRIWBgqjDBTrq04= diff --git a/integration/harness.go b/integration/harness.go index 55901bb..ffd75e6 100644 --- a/integration/harness.go +++ b/integration/harness.go @@ -106,7 +106,7 @@ type VirtualHarness struct { Cancel context.CancelCauseFunc Local []state.LocalCfg Net *InMemoryNetwork - States []*state.State + Nylons []*core.Nylon Links []*VirtualLink Endpoints map[string]state.NodeId UntrackedRouting bool @@ -159,7 +159,7 @@ func (v *VirtualHarness) Start() chan error { ctx, cancel := context.WithCancelCause(context.Background()) v.Context = ctx v.Cancel = cancel - v.States = make([]*state.State, len(v.Central.Routers)) + v.Nylons = make([]*core.Nylon, len(v.Central.Routers)) errChan := make(chan error, 128) // a large number so we dont get blocked vn := &InMemoryNetwork{} v.Net = vn @@ -190,7 +190,7 @@ func (v *VirtualHarness) Start() chan error { pprof.Do(context.Background(), labels, func(_ context.Context) { restart, cErr := core.Start(v.Central, v.Local[idx], slog.LevelDebug, "", map[string]any{ "vnet": vn, - }, &v.States[idx]) + }, &v.Nylons[idx]) if cErr != nil { errChan <- cErr return @@ -206,7 +206,7 @@ func (v *VirtualHarness) Start() chan error { for { started := true for idx, _ := range v.Central.Routers { - if v.States[idx] == nil || !v.States[idx].Started.Load() { + if v.Nylons[idx] == nil || !v.Nylons[idx].State.Started.Load() { started = false break } @@ -231,7 +231,7 @@ func (v *VirtualHarness) Stop() { println("Stopping VirtualHarness") v.Cancel(fmt.Errorf("stopping harness")) for idx, _ := range v.Central.Routers { - core.Stop(v.States[idx]) + core.Stop(v.Nylons[idx]) } v.Net.Stop() println("Stopped VirtualHarness") diff --git a/log/events.go b/log/events.go new file mode 100644 index 0000000..10ea6e7 --- /dev/null +++ b/log/events.go @@ -0,0 +1,19 @@ +package log + +const ( + // Scopes + ScopeRouter = "router" + ScopePolyamide = "polyamide" +) + +const ( + // Router Events + EventRouteInserted = "route_inserted" + EventRouteUpdated = "route_updated" + EventRouteRetracted = "route_retracted" + EventRouteExpired = "route_expired" + EventMajorRouteChange = "major_route_change" + EventInconsistentState = "inconsistent_state" + EventNoEndpointToNeigh = "no_endpoint_to_neighbour" + EventSeqnoRequested = "seqno_requested" +) diff --git a/state/prefix_health_test.go b/state/prefix_health_test.go index 3954386..658c95a 100644 --- a/state/prefix_health_test.go +++ b/state/prefix_health_test.go @@ -10,9 +10,6 @@ import ( ) func TestPrefixHealthSerialization(t *testing.T) { - threeFails := 3 - tenSecond := 10 * time.Second - fiveSecond := 5 * time.Second tests := []struct { name string wrapper PrefixHealthWrapper @@ -37,8 +34,8 @@ metric: 100 PrefixHealth: &PingPrefixHealth{ Prefix: netip.MustParsePrefix("192.168.1.0/24"), Addr: netip.MustParseAddr("8.8.8.8"), - MaxFailures: &threeFails, - Delay: &tenSecond, + MaxFailures: new(3), + Delay: new(10 * time.Second), }, }, yamlStr: `type: ping @@ -54,7 +51,7 @@ delay: 10s PrefixHealth: &HTTPPrefixHealth{ Prefix: netip.MustParsePrefix("172.16.0.0/16"), URL: "http://example.com/health", - Delay: &fiveSecond, + Delay: new(5 * time.Second), }, }, yamlStr: `type: http diff --git a/state/routing.go b/state/routing.go index eb18a90..1effcd1 100644 --- a/state/routing.go +++ b/state/routing.go @@ -77,6 +77,13 @@ type FD struct { Metric uint32 } +func (fd FD) LogValue() slog.Value { + return slog.GroupValue( + slog.Uint64("seqno", uint64(fd.Seqno)), + slog.Uint64("metric", uint64(fd.Metric)), + ) +} + type PubRoute struct { Source // FD will depend on which table the route is in. In the neighbour table, diff --git a/state/scheduler.go b/state/scheduler.go index 0f0fae7..08e3d1e 100644 --- a/state/scheduler.go +++ b/state/scheduler.go @@ -8,7 +8,7 @@ import ( ) // Dispatch Dispatches the function to run on the main thread without waiting for it to complete -func (e *Env) Dispatch(fun func(*State) error) { +func (e *Env) Dispatch(fun func() error) { defer func() { if r := recover(); r != nil { e.Cancel(fmt.Errorf("dispatch panic: %v", r)) @@ -25,13 +25,13 @@ func (e *Env) Dispatch(fun func(*State) error) { } } -func (e *Env) ScheduleTask(fun func(*State) error, delay time.Duration) { +func (e *Env) ScheduleTask(fun func() error, delay time.Duration) { time.AfterFunc(delay, func() { e.Dispatch(fun) }) } -func (e *Env) repeatedTask(fun func(*State) error, delay time.Duration) { +func (e *Env) repeatedTask(fun func() error, delay time.Duration) { // run immediately e.Dispatch(fun) ticker := time.NewTicker(delay) @@ -45,6 +45,6 @@ func (e *Env) repeatedTask(fun func(*State) error, delay time.Duration) { } } -func (e *Env) RepeatTask(fun func(*State) error, delay time.Duration) { +func (e *Env) RepeatTask(fun func() error, delay time.Duration) { go e.repeatedTask(fun, delay) } diff --git a/state/scheduler_test.go b/state/scheduler_test.go index 58a0015..b885750 100644 --- a/state/scheduler_test.go +++ b/state/scheduler_test.go @@ -11,7 +11,7 @@ func TestDispatch(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - dispatchChan := make(chan func(*State) error, 10) + dispatchChan := make(chan func() error, 10) env := &Env{ DispatchChannel: dispatchChan, Context: ctx, @@ -19,16 +19,13 @@ func TestDispatch(t *testing.T) { cancel() }, } - state := &State{ - Env: env, - } var called bool go func() { select { case f := <-dispatchChan: - if err := f(state); err != nil { + if err := f(); err != nil { t.Errorf("Dispatch error: %v", err) } case <-time.After(100 * time.Millisecond): @@ -36,7 +33,7 @@ func TestDispatch(t *testing.T) { } }() - env.Dispatch(func(s *State) error { + env.Dispatch(func() error { called = true return nil }) @@ -52,7 +49,7 @@ func TestScheduleTask(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - dispatchChan := make(chan func(*State) error, 10) + dispatchChan := make(chan func() error, 10) env := &Env{ DispatchChannel: dispatchChan, Context: ctx, @@ -60,13 +57,10 @@ func TestScheduleTask(t *testing.T) { cancel() }, } - state := &State{ - Env: env, - } var taskCalled bool - env.ScheduleTask(func(s *State) error { + env.ScheduleTask(func() error { taskCalled = true return nil }, 50*time.Millisecond) @@ -75,7 +69,7 @@ func TestScheduleTask(t *testing.T) { time.Sleep(100 * time.Millisecond) select { case f := <-dispatchChan: - if err := f(state); err != nil { + if err := f(); err != nil { t.Errorf("Scheduled task error: %v", err) } default: @@ -91,7 +85,7 @@ func TestRepeatTask(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - dispatchChan := make(chan func(*State) error, 10) + dispatchChan := make(chan func() error, 10) env := &Env{ DispatchChannel: dispatchChan, Context: ctx, @@ -99,15 +93,12 @@ func TestRepeatTask(t *testing.T) { cancel() }, } - state := &State{ - Env: env, - } var wg sync.WaitGroup wg.Add(3) var count int - env.RepeatTask(func(s *State) error { + env.RepeatTask(func() error { count++ wg.Done() if count >= 3 { @@ -121,7 +112,7 @@ loop: for { select { case f := <-dispatchChan: - err := f(state) + err := f() if err != nil { t.Fatalf("RepeatTask error: %v", err) } diff --git a/state/state.go b/state/state.go index 954abb4..f9293db 100644 --- a/state/state.go +++ b/state/state.go @@ -6,21 +6,15 @@ import ( "sync/atomic" ) -type NyModule interface { - Init(s *State) error - Cleanup(s *State) error -} - // State access must be done only on a single Goroutine type State struct { *Env - Modules map[string]NyModule *RouterState } // Env can be read from any Goroutine type Env struct { - DispatchChannel chan func(s *State) error + DispatchChannel chan func() error CentralCfg LocalCfg Context context.Context