diff --git a/core/ipc.go b/core/ipc.go index 3036a9ad..5dc08aad 100644 --- a/core/ipc.go +++ b/core/ipc.go @@ -78,16 +78,15 @@ func HandleNylonIPCGet(s *state.State, rw *bufio.ReadWriter) error { slices.Sort(rt) sb.WriteString(strings.Join(rt, "\n") + "\n") - // print services - sb.WriteString("\n\nAdvertised Services:\n") + // print advertised prefixes + sb.WriteString("\n\nAdvertised Prefixes:\n") rt = make([]string, 0) - for sid, adv := range s.Advertised { - prefix := s.GetSvcPrefix(sid) + for prefix, adv := range s.Advertised { timeRem := adv.Expiry.Sub(time.Now()) if timeRem > time.Hour*24 { - rt = append(rt, fmt.Sprintf(" - %s as %s expires never nh %s", sid, prefix, adv.NodeId)) + rt = append(rt, fmt.Sprintf(" - %s expires never nh %s", prefix, adv.NodeId)) } else { - rt = append(rt, fmt.Sprintf(" - %s as %s expires %.2fs nh %s", sid, prefix, timeRem.Seconds(), adv.NodeId)) + rt = append(rt, fmt.Sprintf(" - %s expires %.2fs nh %s", prefix, timeRem.Seconds(), adv.NodeId)) } } slices.Sort(rt) diff --git a/core/nylon_passive.go b/core/nylon_passive.go index 2d498633..fc7d1022 100644 --- a/core/nylon_passive.go +++ b/core/nylon_passive.go @@ -24,26 +24,29 @@ func scanPassivePeers(s *state.State) error { // If this device switches to another nylon node, that node will start advertising the client, and we will stop holding the route hasOtherAdvertisers := false - for _, neigh := range s.Neighbours { - for _, route := range neigh.Routes { - if route.ServiceId == state.ServiceId(*nid) && route.NodeId != s.Id && route.FD.Metric != state.INF { - hasOtherAdvertisers = true - break + ncfg := s.GetNode(*nid) + for _, prefix := range ncfg.Prefixes { + for _, neigh := range s.Neighbours { + for _, route := range neigh.Routes { + if route.Prefix == prefix && route.NodeId != s.Id && route.FD.Metric != state.INF { + hasOtherAdvertisers = true + goto foundAdvertiser + } } } } + foundAdvertiser: // 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.Now().Sub(peer.LastReceivedPacket()) < state.ClientDeadThreshold - recentlyAdvertised := r.hasRecentlyAdvertised(state.ServiceId(*nid)) - - if s.IsClient(*nid) && (recentlyUpdated || !hasOtherAdvertisers && recentlyAdvertised) { + if s.IsClient(*nid) { // we have a passive client - ncfg := s.GetNode(*nid) - - for _, newSvc := range ncfg.Services { - r.updatePassiveClient(s, newSvc, *nid, !recentlyUpdated) + for _, newPrefix := range ncfg.Prefixes { + recentlyAdvertised := r.hasRecentlyAdvertised(newPrefix) + if recentlyUpdated || !hasOtherAdvertisers && recentlyAdvertised { + r.updatePassiveClient(s, newPrefix, *nid, !recentlyUpdated) + } } } } diff --git a/core/nylon_tc.go b/core/nylon_tc.go index 26dd1420..fe1c6ffe 100644 --- a/core/nylon_tc.go +++ b/core/nylon_tc.go @@ -69,7 +69,7 @@ func (n *Nylon) InstallTC(s *state.State) { // bounce back packets destined for the current node n.Device.InstallFilter(func(dev *device.Device, packet *device.TCElement) (device.TCAction, error) { - entry, ok := r.LoopbackTable.Lookup(packet.GetDst()) + entry, ok := r.ExitTable.Lookup(packet.GetDst()) // we should only accept packets destined to us, but not our passive clients if ok && entry.Nh == s.Id { //dev.Log.Verbosef("BounceCur packet: %v -> %v", packet.GetSrc(), packet.GetDst()) diff --git a/core/nylon_wireguard.go b/core/nylon_wireguard.go index 90214c83..b159ba1e 100644 --- a/core/nylon_wireguard.go +++ b/core/nylon_wireguard.go @@ -83,36 +83,54 @@ listen_port=%d // configure system networking if !s.NoNetConfigure { - // configure self - selfSvc := make(map[state.ServiceId]struct{}) - - for _, svc := range s.GetRouter(s.Id).Services { - prefix := s.GetSvcPrefix(svc) - selfSvc[svc] = struct{}{} - err = ConfigureAlias(itfName, prefix) + // run pre-up commands + for _, cmd := range s.PreUp { + err = ExecSplit(s.Log, cmd) if err != nil { - return err + s.Log.Error("failed to run pre-up command", "err", err) } } - if len(s.GetRouter(s.Id).Services) == 0 { - return fmt.Errorf("no address configured for self") + for _, addr := range s.GetRouter(s.Id).Addresses { + err := ConfigureAlias(s.Log, itfName, addr) + if err != nil { + s.Log.Error("failed to configure alias", "err", err) + } } - err = InitInterface(itfName) + err = InitInterface(s.Log, itfName) if err != nil { return err } - // configure services - for svc, prefix := range s.Services { - if _, ok := selfSvc[svc]; ok { - continue + // configure prefixes + include := append(s.GetPrefixes(), s.IncludeIPs...) + if len(s.IncludeIPs) != 0 { + include = s.IncludeIPs + } + for _, inc := range include { + s.Log.Debug("Include Prefix", "prefix", inc.String()) + } + for _, excl := range s.ExcludeIPs { + s.Log.Debug("Exclude Prefix", "prefix", excl.String()) + } + computed := state.ComputeSplitTunnel(include, s.ExcludeIPs) + for _, pre := range computed { + s.Log.Debug("Computed Prefix", "prefix", pre.String()) + } + for _, prefix := range computed { + err := ConfigureRoute(s.Log, n.Tun, itfName, prefix) + if err != nil { + s.Log.Error("failed to configure route", "err", err) } - err = ConfigureRoute(n.Tun, itfName, prefix) + } + + // run post-up commands + for _, cmd := range s.PostUp { + err = ExecSplit(s.Log, cmd) if err != nil { - return err + s.Log.Error("failed to run post-up command", "err", err) } } } @@ -125,7 +143,25 @@ listen_port=%d } func (n *Nylon) cleanupWireGuard(s *state.State) error { - return CleanupWireGuardDevice(s, n) + // run pre-down commands + for _, cmd := range s.PreUp { + err := ExecSplit(s.Log, cmd) + if err != nil { + s.Log.Error("failed to run pre-down command", "err", err) + } + } + err := CleanupWireGuardDevice(s, n) + if err != nil { + return err + } + // run post-down commands + for _, cmd := range s.PostDown { + err = ExecSplit(s.Log, cmd) + if err != nil { + s.Log.Error("failed to run post-down command", "err", err) + } + } + return nil } func UpdateWireGuard(s *state.State) error { diff --git a/core/router.go b/core/router.go index dac6fc06..8b6a6868 100644 --- a/core/router.go +++ b/core/router.go @@ -2,6 +2,7 @@ package core import ( "fmt" + "net/netip" "github.com/encodeous/nylon/polyamide/device" "github.com/gaissmai/bart" @@ -21,8 +22,8 @@ type NylonRouter struct { IO map[state.NodeId]*IOPending // ForwardTable contains the full routing table ForwardTable bart.Table[RouteTableEntry] - // LoopbackTable contains only routes to services hosted on this node - LoopbackTable bart.Table[RouteTableEntry] + // ExitTable contains only routes to services hosted on this node + ExitTable bart.Table[RouteTableEntry] } type RouteTableEntry struct { @@ -36,8 +37,8 @@ func (r *NylonRouter) GetNeighIO(neigh state.NodeId) *IOPending { 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]()), - Acks: make(map[state.ServiceId]struct{}), - Updates: make(map[state.ServiceId]*protocol.Ny_Update), + Acks: make(map[netip.Prefix]struct{}), + Updates: make(map[netip.Prefix]*protocol.Ny_Update), } r.IO[neigh] = nio } @@ -47,17 +48,18 @@ func (r *NylonRouter) GetNeighIO(neigh state.NodeId) *IOPending { func (r *NylonRouter) SendRouteUpdate(neigh state.NodeId, advRoute state.PubRoute) { nio := r.GetNeighIO(neigh) - nio.Updates[advRoute.ServiceId] = &protocol.Ny_Update{ - RouterId: string(advRoute.NodeId), - ServiceId: string(advRoute.ServiceId), - Seqno: uint32(advRoute.Seqno), - Metric: advRoute.Metric, + prefix, _ := advRoute.Prefix.MarshalBinary() + nio.Updates[advRoute.Prefix] = &protocol.Ny_Update{ + RouterId: string(advRoute.NodeId), + Prefix: prefix, + Seqno: uint32(advRoute.Seqno), + Metric: advRoute.Metric, } } -func (r *NylonRouter) SendAckRetract(neigh state.NodeId, svc state.ServiceId) { +func (r *NylonRouter) SendAckRetract(neigh state.NodeId, prefix netip.Prefix) { nio := r.GetNeighIO(neigh) - nio.Acks[svc] = struct{}{} + nio.Acks[prefix] = struct{}{} } func (r *NylonRouter) BroadcastSendRouteUpdate(advRoute state.PubRoute) { @@ -102,8 +104,7 @@ func (r *NylonRouter) UpdateNeighbour(neigh state.NodeId) { PushFullTable(r.RouterState, r, neigh) } -func (r *NylonRouter) TableInsertRoute(svc state.ServiceId, route state.SelRoute) { - prefix := r.GetSvcPrefix(svc) +func (r *NylonRouter) TableInsertRoute(prefix netip.Prefix, route state.SelRoute) { n := Get[*Nylon](r.State) nh := route.Nh peer := n.Device.LookupPeer(device.NoisePublicKey(r.GetNode(nh).PubKey)) @@ -112,27 +113,26 @@ func (r *NylonRouter) TableInsertRoute(svc state.ServiceId, route state.SelRoute Peer: peer, }) if route.Nh == r.Id { - r.LoopbackTable.Insert(prefix, RouteTableEntry{ + r.ExitTable.Insert(prefix, RouteTableEntry{ Nh: nh, Peer: peer, }) } else { - r.LoopbackTable.Delete(prefix) + r.ExitTable.Delete(prefix) } } -func (r *NylonRouter) TableDeleteRoute(svc state.ServiceId) { - prefix := r.GetSvcPrefix(svc) +func (r *NylonRouter) TableDeleteRoute(prefix netip.Prefix) { r.ForwardTable.Delete(prefix) - r.LoopbackTable.Delete(prefix) + r.ExitTable.Delete(prefix) } type IOPending struct { // SeqnoReq values represent a pair of (seqno, hop count) SeqnoReq map[state.Source]state.Pair[uint16, uint8] SeqnoDedup *ttlcache.Cache[state.Source, uint16] - Acks map[state.ServiceId]struct{} - Updates map[state.ServiceId]*protocol.Ny_Update + Acks map[netip.Prefix]struct{} + Updates map[netip.Prefix]*protocol.Ny_Update } func (r *NylonRouter) Cleanup(s *state.State) error { @@ -161,17 +161,16 @@ func (r *NylonRouter) Init(s *state.State) error { r.IO = make(map[state.NodeId]*IOPending) r.ForwardTable = bart.Table[RouteTableEntry]{} s.RouterState = &state.RouterState{ - Id: s.Env.LocalCfg.Id, - SelfSeqno: make(map[state.ServiceId]uint16), - Routes: make(map[state.ServiceId]state.SelRoute), - Sources: make(map[state.Source]state.FD), - Neighbours: make([]*state.Neighbour, 0), - Advertised: make(map[state.ServiceId]state.Advertisement), - DisableRouting: s.Env.LocalCfg.DisableRouting, + Id: s.Env.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 _, svc := range s.Env.GetRouter(s.Id).Services { - s.RouterState.Advertised[svc] = state.Advertisement{NodeId: s.Id, Expiry: maxTime, IsPassiveHold: false} + for _, prefix := range s.Env.GetRouter(s.Id).Prefixes { + s.RouterState.Advertised[prefix] = state.Advertisement{NodeId: s.Id, Expiry: maxTime, IsPassiveHold: false} } s.Log.Debug("schedule router tasks") @@ -189,11 +188,11 @@ func (r *NylonRouter) Init(s *state.State) error { return nil } -func (r *NylonRouter) updatePassiveClient(s *state.State, client state.ServiceId, node state.NodeId, passiveHold bool) { +func (r *NylonRouter) updatePassiveClient(s *state.State, prefix netip.Prefix, node state.NodeId, passiveHold bool) { // inserts an artificial route into the table hasPassiveHold := false - old, ok := s.RouterState.Advertised[client] + old, ok := s.RouterState.Advertised[prefix] if ok && old.NodeId == node { hasPassiveHold = old.IsPassiveHold } @@ -201,18 +200,18 @@ func (r *NylonRouter) updatePassiveClient(s *state.State, client state.ServiceId if passiveHold && !hasPassiveHold { // the first time we enter passive hold, we should increment the seqno to prevent other nodes from switching away from the route // this reduces a lot of route flapping when the client wakes up, sends some traffic and then goes back to sleep - r.SetSeqno(client, s.RouterState.GetSeqno(client)+1) + r.SetSeqno(prefix, s.RouterState.GetSeqno(prefix)+1) } - s.Advertised[client] = state.Advertisement{ + s.Advertised[prefix] = state.Advertisement{ NodeId: node, Expiry: time.Now().Add(state.ClientKeepaliveInterval), IsPassiveHold: passiveHold, } } -func (r *NylonRouter) hasRecentlyAdvertised(svc state.ServiceId) bool { - adv, ok := r.RouterState.Advertised[svc] +func (r *NylonRouter) hasRecentlyAdvertised(prefix netip.Prefix) bool { + adv, ok := r.RouterState.Advertised[prefix] if !ok { return false } @@ -229,12 +228,14 @@ func checkNeigh(s *state.State, id state.NodeId) bool { return false } -func checkService(s *state.State, svc state.ServiceId) bool { - _, ok := s.Services[svc] - if !ok { - s.Log.Warn("received packet for unknown service", "service", svc) +func checkPrefix(s *state.State, prefix netip.Prefix) bool { + for _, p := range s.GetPrefixes() { + if p == prefix { + return true + } } - return ok + s.Log.Warn("received packet for unknown prefix", "prefix", prefix) + return false } func checkNode(s *state.State, id state.NodeId) bool { @@ -248,15 +249,21 @@ func checkNode(s *state.State, id state.NodeId) bool { // packet handlers func routerHandleRouteUpdate(s *state.State, node state.NodeId, update *protocol.Ny_Update) error { r := Get[*NylonRouter](s) + prefix := netip.Prefix{} + err := prefix.UnmarshalBinary(update.Prefix) + if err != nil { + s.Log.Warn("received update with invalid prefix", "prefix", update.Prefix, "err", err) + return nil + } if !checkNeigh(s, node) || - !checkService(s, state.ServiceId(update.ServiceId)) || + !checkPrefix(s, prefix) || !checkNode(s, state.NodeId(update.RouterId)) { return nil } HandleNeighbourUpdate(s.RouterState, r, node, state.PubRoute{ Source: state.Source{ - NodeId: state.NodeId(update.RouterId), - ServiceId: state.ServiceId(update.ServiceId), + NodeId: state.NodeId(update.RouterId), + Prefix: prefix, }, FD: state.FD{ Seqno: uint16(update.Seqno), @@ -268,24 +275,36 @@ func routerHandleRouteUpdate(s *state.State, node state.NodeId, update *protocol func routerHandleAckRetract(s *state.State, neigh state.NodeId, update *protocol.Ny_AckRetract) error { r := Get[*NylonRouter](s) - if !checkService(s, state.ServiceId(update.ServiceId)) || + 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) + return nil + } + if !checkPrefix(s, prefix) || !checkNeigh(s, neigh) { return nil } - HandleAckRetract(s.RouterState, r, neigh, state.ServiceId(update.ServiceId)) + 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) + 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) + return nil + } if !checkNeigh(s, neigh) || - !checkService(s, state.ServiceId(pkt.ServiceId)) || + !checkPrefix(s, prefix) || !checkNode(s, state.NodeId(pkt.RouterId)) { return nil } HandleSeqnoRequest(s.RouterState, r, neigh, state.Source{ - NodeId: state.NodeId(pkt.RouterId), - ServiceId: state.ServiceId(pkt.ServiceId), + NodeId: state.NodeId(pkt.RouterId), + Prefix: prefix, }, uint16(pkt.Seqno), uint8(pkt.HopCount)) return nil } @@ -309,12 +328,13 @@ func flushIO(s *state.State) error { // we can coalesce messages, but we need to make sure we don't fragment our UDP packet for seqR, _ := range nio.SeqnoReq { + prefixBytes, _ := seqR.Prefix.MarshalBinary() req := &protocol.Ny{Type: &protocol.Ny_SeqnoRequestOp{ SeqnoRequestOp: &protocol.Ny_SeqnoRequest{ - RouterId: string(seqR.NodeId), - ServiceId: string(seqR.ServiceId), - Seqno: uint32(nio.SeqnoReq[seqR].V1), - HopCount: uint32(nio.SeqnoReq[seqR].V2), + RouterId: string(seqR.NodeId), + Prefix: prefixBytes, + Seqno: uint32(nio.SeqnoReq[seqR].V1), + HopCount: uint32(nio.SeqnoReq[seqR].V2), }, }} if tLength+proto.Size(req) >= state.SafeMTU { diff --git a/core/router_algo.go b/core/router_algo.go index 2dd84646..8101ffad 100644 --- a/core/router_algo.go +++ b/core/router_algo.go @@ -4,6 +4,7 @@ package core // https://datatracker.ietf.org/doc/html/rfc8966 import ( + "net/netip" "slices" "time" @@ -31,12 +32,12 @@ const ( // Router is an interface that defines the underlying router operations type Router interface { SendRouteUpdate(neigh state.NodeId, advRoute state.PubRoute) - SendAckRetract(neigh state.NodeId, svc state.ServiceId) + SendAckRetract(neigh state.NodeId, prefix netip.Prefix) BroadcastSendRouteUpdate(advRoute state.PubRoute) RequestSeqno(neigh state.NodeId, src state.Source, seqno uint16, hopCnt uint8) BroadcastRequestSeqno(src state.Source, seqno uint16, hopCnt uint8) - TableInsertRoute(svc state.ServiceId, route state.SelRoute) - TableDeleteRoute(svc state.ServiceId) + TableInsertRoute(prefix netip.Prefix, route state.SelRoute) + TableDeleteRoute(prefix netip.Prefix) Log(event RouterEvent, desc string, args ...any) } @@ -98,9 +99,6 @@ func checkFeasibility(router *state.RouterState, advRoute state.PubRoute) bool { func FullTableUpdate(s *state.RouterState, r Router) { // send a full table update to all neighbours for _, route := range s.Routes { - if s.DisableRouting && route.Nh != s.Id { - continue // skip routes that are not ours - } updateFeasibility(s, route.PubRoute) r.BroadcastSendRouteUpdate(route.PubRoute) } @@ -109,9 +107,6 @@ func FullTableUpdate(s *state.RouterState, r Router) { func PushFullTable(s *state.RouterState, r Router, neigh state.NodeId) { // send a full table update to one neighbour for _, route := range s.Routes { - if s.DisableRouting && route.Nh != s.Id { - continue // skip routes that are not ours - } updateFeasibility(s, route.PubRoute) r.SendRouteUpdate(neigh, route.PubRoute) } @@ -161,12 +156,12 @@ func RunGC(s *state.RouterState, r Router) { } } if !found { - if selRoute, ok := s.Routes[src.ServiceId]; ok && selRoute.Source == src { + if selRoute, ok := s.Routes[src.Prefix]; ok && selRoute.Source == src { found = true } } if !found { - if adv, ok := s.Advertised[src.ServiceId]; ok && adv.NodeId == src.NodeId { + if adv, ok := s.Advertised[src.Prefix]; ok && adv.NodeId == src.NodeId { found = true } } @@ -179,10 +174,10 @@ func RunGC(s *state.RouterState, r Router) { ComputeRoutes(s, r) } -func retract(s *state.RouterState, r Router, svc state.ServiceId) { - tblEntry, ok := s.Routes[svc] +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", "svc", svc) + r.Log(InconsistentState, "attempted to retract non-existent route", "prefix", prefix) return // route does not exist } tblEntry.Metric = state.INF @@ -196,7 +191,7 @@ func HandleSeqnoRequest(s *state.RouterState, r Router, fromNeigh state.NodeId, // sequence number, it checks whether its route table contains a // selected entry for that prefix. - if selRoute, ok := s.Routes[src.ServiceId]; ok { + if selRoute, ok := s.Routes[src.Prefix]; ok { // If a selected route for the given prefix exists and has finite metric, // and either the router-ids are different or the router-ids are equal // and the entry's sequence number is no smaller (modulo 2^(16)) than @@ -220,7 +215,7 @@ func HandleSeqnoRequest(s *state.RouterState, r Router, fromNeigh state.NodeId, // Nylon note: We increase seqno by more than one, as we do not persist our seqno // state, so we cannot guarantee that increasing by one is enough. - s.SetSeqno(selRoute.ServiceId, reqSeqno) + s.SetSeqno(selRoute.Prefix, reqSeqno) ComputeRoutes(s, r) // should generate an update } else { // Otherwise, if the requested router-id is not its own, the received @@ -229,14 +224,14 @@ func HandleSeqnoRequest(s *state.RouterState, r Router, fromNeigh state.NodeId, // neighbours, the node selects a neighbour to forward the request to as // follows: - _, isAdv := s.Routes[src.ServiceId] + _, isAdv := s.Routes[src.Prefix] if hopCnt >= 2 && isAdv { var nh *state.NodeId if NeighContainsFunc(s, func(neigh state.NodeId, route state.NeighRoute) bool { // * if the node has one or more feasible routes towards the requested // prefix with a next hop that is not the requesting node, then the // node MUST forward the request to the next hop of one such route; - if src.ServiceId == route.ServiceId && checkFeasibility(s, route.PubRoute) { + if src.Prefix == route.Prefix && checkFeasibility(s, route.PubRoute) { nh = &neigh return true // found a feasible route } @@ -246,7 +241,7 @@ func HandleSeqnoRequest(s *state.RouterState, r Router, fromNeigh state.NodeId, // the requested prefix with a next hop that is not the requesting // node, then the node SHOULD forward the request to the next hop of // one such route. - if src.ServiceId == route.ServiceId && neigh != fromNeigh { + if src.Prefix == route.Prefix && neigh != fromNeigh { nh = &neigh return true // found a route } @@ -267,15 +262,15 @@ func HandleSeqnoRequest(s *state.RouterState, r Router, fromNeigh state.NodeId, } -func HandleAckRetract(s *state.RouterState, r Router, neighId state.NodeId, svc state.ServiceId) { - rt, ok := s.Routes[svc] +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", "svc", svc) + r.Log(InconsistentState, "attempted to ack the retraction of a non-existent route", "prefix", prefix) return // route does not exist } if !slices.Contains(rt.RetractedBy, neighId) { rt.RetractedBy = append(rt.RetractedBy, neighId) - s.Routes[svc] = rt // update the route table + s.Routes[prefix] = rt // update the route table // recompute routes ComputeRoutes(s, r) } @@ -294,7 +289,7 @@ func HandleNeighbourUpdate(s *state.RouterState, r Router, neighId state.NodeId, _, ok := n.Routes[adv.Source] if adv.Metric == state.INF { - r.SendAckRetract(neighId, adv.Source.ServiceId) + r.SendAckRetract(neighId, adv.Source.Prefix) } if !ok { @@ -330,7 +325,7 @@ func HandleNeighbourUpdate(s *state.RouterState, r Router, neighId state.NodeId, // the router-id of the update is equal to the router-id of the // entry, then the update MAY be ignored; - selRoute, hasSelected := s.Routes[adv.Source.ServiceId] + selRoute, hasSelected := s.Routes[adv.Source.Prefix] isSelected := hasSelected && selRoute.Nh == neighId if !checkFeasibility(s, adv) { isMoreOptimal := hasSelected && ShouldSwitch(selRoute, state.SelRoute{ @@ -396,7 +391,7 @@ func isHeldRoute(s *state.RouterState, route state.SelRoute) bool { } func ComputeRoutes(s *state.RouterState, r Router) { - newTable := make(map[state.ServiceId]state.SelRoute) + newTable := make(map[netip.Prefix]state.SelRoute) // 3.5.4. Hold Time // @@ -434,27 +429,27 @@ func ComputeRoutes(s *state.RouterState, r Router) { // In order to avoid routing loops, we pick the second option. Here, we will re-introduce held routes - for svc, route := range s.Routes { + for prefix, route := range s.Routes { if isHeldRoute(s, route) { - newTable[svc] = route + newTable[prefix] = route } } // add our own routes to the route table, so that we can advertise them - for svc, adv := range s.Advertised { + for prefix, adv := range s.Advertised { advMetric := uint32(0) if adv.IsPassiveHold { // The metric should be high enough so that if the passive client connects to any other node, our route will be immediately unselected advMetric = state.INFM / 2 } - newTable[svc] = state.SelRoute{ + newTable[prefix] = state.SelRoute{ PubRoute: state.PubRoute{ Source: state.Source{ - NodeId: s.Id, - ServiceId: svc, + NodeId: s.Id, + Prefix: prefix, }, FD: state.FD{ - Seqno: s.GetSeqno(svc), + Seqno: s.GetSeqno(prefix), Metric: advMetric, }, }, @@ -503,15 +498,15 @@ func ComputeRoutes(s *state.RouterState, r Router) { // enumerate through neighbour advertisements for S, adv := range neigh.Routes { - if _, ok := s.Advertised[S.ServiceId]; ok { + if _, ok := s.Advertised[S.Prefix]; ok { continue // skip self routes } - svc := S.ServiceId + prefix := S.Prefix // Cost(A, B) + Cost(S, B) totalCost := AddMetric(CAB, adv.Metric) - oldRoute, exists := newTable[svc] + oldRoute, exists := newTable[prefix] retractedBy := make([]state.NodeId, 0) if exists { @@ -534,7 +529,7 @@ func ComputeRoutes(s *state.RouterState, r Router) { // update the route if it is currently selected if exists && oldRoute.Nh == newRoute.Nh { - newTable[svc] = newRoute + newTable[prefix] = newRoute } // * a route with infinite metric (a retracted route) is never @@ -550,11 +545,11 @@ func ComputeRoutes(s *state.RouterState, r Router) { if !exists { // create new route - newTable[svc] = newRoute + newTable[prefix] = newRoute } else { // check if we should switch to this route if ShouldSwitch(oldRoute, newRoute) && !isHeldRoute(s, oldRoute) { - newTable[svc] = newRoute + newTable[prefix] = newRoute } } } @@ -569,14 +564,14 @@ func ComputeRoutes(s *state.RouterState, r Router) { // Here, we also want to send updates for new seqno, and routes that changed drastically in metric - for svc, newRoute := range newTable { - oldRoute, exists := s.Routes[svc] + for prefix, newRoute := range newTable { + oldRoute, exists := s.Routes[prefix] if !exists { - r.TableInsertRoute(svc, newRoute) - r.Log(RouteChanged, "inserted", "svc", svc, "new", newRoute) + r.TableInsertRoute(prefix, newRoute) + r.Log(RouteChanged, "inserted", "prefix", prefix, "new", newRoute) } else if oldRoute.Nh != newRoute.Nh { - r.TableInsertRoute(svc, newRoute) - r.Log(RouteChanged, "updated", "svc", svc, "old", oldRoute, "new", newRoute) + r.TableInsertRoute(prefix, newRoute) + r.Log(RouteChanged, "updated", "prefix", prefix, "old", oldRoute, "new", newRoute) } if !exists || oldRoute.Source.NodeId != newRoute.Source.NodeId || @@ -584,20 +579,20 @@ 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", "svc", svc, "old", oldRoute, "new", newRoute) + r.Log(RoutePushed, "major change", "prefix", prefix, "old", oldRoute, "new", newRoute) r.BroadcastSendRouteUpdate(newRoute.PubRoute) } } // scan for retractions - for svc, oldRoute := range s.Routes { - route, exists := newTable[svc] + for prefix, oldRoute := range s.Routes { + route, exists := newTable[prefix] if !exists || route.Metric == state.INF { // route is no longer reachable, retract it if oldRoute.Metric != state.INF { - retract(s, r, svc) - r.TableDeleteRoute(svc) - r.Log(RouteChanged, "retracted", "svc", svc, "old", oldRoute) + retract(s, r, prefix) + r.TableDeleteRoute(prefix) + r.Log(RouteChanged, "retracted", "prefix", prefix, "old", oldRoute) } } } diff --git a/core/router_harness.go b/core/router_harness.go index c13322cd..e3cfec0b 100644 --- a/core/router_harness.go +++ b/core/router_harness.go @@ -4,6 +4,7 @@ package core import ( "fmt" + "net/netip" "slices" "strings" "testing" @@ -11,6 +12,7 @@ import ( "github.com/encodeous/nylon/state" "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" ) func ConfigureConstants() { @@ -74,16 +76,16 @@ type RouterHarness struct { actions []HarnessEvent } -func (h *RouterHarness) TableInsertRoute(svc state.ServiceId, route state.SelRoute) { +func (h *RouterHarness) TableInsertRoute(prefix netip.Prefix, route state.SelRoute) { } -func (h *RouterHarness) TableDeleteRoute(svc state.ServiceId) { +func (h *RouterHarness) TableDeleteRoute(prefix netip.Prefix) { } -func (h *RouterHarness) SendAckRetract(neigh state.NodeId, svc state.ServiceId) { - h.actions = append(h.actions, MakeEvent("ACK_RETRACT", neigh, svc)) +func (h *RouterHarness) SendAckRetract(neigh state.NodeId, prefix netip.Prefix) { + h.actions = append(h.actions, MakeEvent("ACK_RETRACT", neigh, prefix)) } func (h *RouterHarness) SendRouteUpdate(neigh state.NodeId, advRoute state.PubRoute) { @@ -143,7 +145,7 @@ func (e HarnessEvents) contains(msg string, args ...any) bool { if len(event.Args) >= len(args) { match := true for i, arg := range args { - if !cmp.Equal(event.Args[i], arg) { + if !cmp.Equal(event.Args[i], arg, cmpopts.EquateComparable(netip.Prefix{})) { match = false break } @@ -182,10 +184,11 @@ func MakeNeighbours(ids ...state.NodeId) []*state.Neighbour { return neighs } -func MakePubRoute(nodeId state.NodeId, svc state.ServiceId, seqno uint16, metric uint32) state.PubRoute { +func MakePubRoute(nodeId state.NodeId, prefix netip.Prefix, seqno uint16, metric uint32) state.PubRoute { return state.PubRoute{ Source: state.Source{ - nodeId, svc, + NodeId: nodeId, + Prefix: prefix, }, FD: state.FD{ Seqno: seqno, @@ -217,10 +220,10 @@ func RemoveLink(r *state.RouterState, ep state.Endpoint) { } } -func (h *RouterHarness) NeighUpdate(rs *state.RouterState, neighId state.NodeId, nodeId state.NodeId, seqno uint16, metric uint32) { - HandleNeighbourUpdate(rs, h, neighId, MakePubRoute(nodeId, state.ServiceId(nodeId), seqno, metric)) +func (h *RouterHarness) NeighUpdate(rs *state.RouterState, neighId state.NodeId, nodeId state.NodeId, prefix netip.Prefix, seqno uint16, metric uint32) { + HandleNeighbourUpdate(rs, h, neighId, MakePubRoute(nodeId, prefix, seqno, metric)) } -func (h *RouterHarness) NeighUpdateSvc(rs *state.RouterState, neighId state.NodeId, nodeId state.NodeId, svc state.ServiceId, seqno uint16, metric uint32) { - HandleNeighbourUpdate(rs, h, neighId, MakePubRoute(nodeId, svc, seqno, metric)) +func (h *RouterHarness) NeighUpdateSvc(rs *state.RouterState, neighId state.NodeId, nodeId state.NodeId, prefix netip.Prefix, seqno uint16, metric uint32) { + HandleNeighbourUpdate(rs, h, neighId, MakePubRoute(nodeId, prefix, seqno, metric)) } diff --git a/core/router_test.go b/core/router_test.go index 5cc1c008..2dd14399 100644 --- a/core/router_test.go +++ b/core/router_test.go @@ -3,6 +3,9 @@ package core import ( + "fmt" + "net/netip" + "strings" "testing" "time" @@ -14,26 +17,38 @@ var ( maxTime = time.Unix(1<<63-62135596801, 999999999) ) +// Helper function to convert test node IDs to prefixes +// Maps single letter IDs to IP addresses in 10.0.0.x/32 range +func nodeToPrefix(nodeId string) netip.Prefix { + var ipByte byte + if len(nodeId) > 0 { + ipByte = strings.ToLower(nodeId)[0] - 'a' + 1 + } + return netip.MustParsePrefix(fmt.Sprintf("10.0.0.%d/32", ipByte)) +} + func TestRouterBasicComputeRoutes(t *testing.T) { h := &RouterHarness{} + aPrefix := nodeToPrefix("a") rs := state.RouterState{ Id: "a", - SelfSeqno: make(map[state.ServiceId]uint16), - Routes: make(map[state.ServiceId]state.SelRoute), + 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[state.ServiceId]state.Advertisement{"a": {state.NodeId("a"), maxTime, false}}, + Advertised: map[netip.Prefix]state.Advertisement{aPrefix: {state.NodeId("a"), maxTime, false}}, } ComputeRoutes(&rs, h) // we should have only routes to ourselves if len(rs.Routes) != 1 { t.Errorf("Expected 1 route, got %d", len(rs.Routes)) } - if _, ok := rs.Routes[("a")]; !ok { + if _, ok := rs.Routes[aPrefix]; !ok { t.Errorf("Expected route to service 'a', but it was not found") } out := h.GetActions() - assert.Equal(t, `BROADCAST_UPDATE_ROUTE (router: a, svc: a, seqno: 0, metric: 0)`, out.String()) + assert.Contains(t, out.String(), "BROADCAST_UPDATE_ROUTE") + assert.Contains(t, out.String(), "router: a") } func TestRouterNet1A_BasicRetraction(t *testing.T) { @@ -48,13 +63,14 @@ func TestRouterNet1A_BasicRetraction(t *testing.T) { // C h := &RouterHarness{} + aPrefix := nodeToPrefix("A") rs := &state.RouterState{ Id: "A", - SelfSeqno: make(map[state.ServiceId]uint16), - Routes: make(map[state.ServiceId]state.SelRoute), + 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[state.ServiceId]state.Advertisement{"A": {state.NodeId("A"), maxTime, false}}, + Advertised: map[netip.Prefix]state.Advertisement{aPrefix: {state.NodeId("A"), maxTime, false}}, } sr := AddLink(rs, NewMockEndpoint("S", 1)) @@ -62,35 +78,35 @@ func TestRouterNet1A_BasicRetraction(t *testing.T) { _ = AddLink(rs, NewMockEndpoint("C", 1)) // S's advertised routes - h.NeighUpdate(rs, "S", "S", 0, 0) - h.NeighUpdate(rs, "S", "A", 0, 1) - h.NeighUpdate(rs, "S", "B", 0, 2) - h.NeighUpdate(rs, "S", "C", 0, 2) + h.NeighUpdate(rs, "S", "S", nodeToPrefix("S"), 0, 0) + h.NeighUpdate(rs, "S", "A", nodeToPrefix("A"), 0, 1) + h.NeighUpdate(rs, "S", "B", nodeToPrefix("B"), 0, 2) + h.NeighUpdate(rs, "S", "C", nodeToPrefix("C"), 0, 2) // B's advertised routes - h.NeighUpdate(rs, "B", "B", 0, 0) - h.NeighUpdate(rs, "B", "A", 0, 1) - h.NeighUpdate(rs, "B", "C", 0, 1) - h.NeighUpdate(rs, "B", "S", 0, 2) + h.NeighUpdate(rs, "B", "B", nodeToPrefix("B"), 0, 0) + h.NeighUpdate(rs, "B", "A", nodeToPrefix("A"), 0, 1) + h.NeighUpdate(rs, "B", "C", nodeToPrefix("C"), 0, 1) + h.NeighUpdate(rs, "B", "S", nodeToPrefix("S"), 0, 2) // C's advertised routes - h.NeighUpdate(rs, "C", "C", 0, 0) - h.NeighUpdate(rs, "C", "A", 0, 1) - h.NeighUpdate(rs, "C", "B", 0, 1) - h.NeighUpdate(rs, "C", "S", 0, 2) + h.NeighUpdate(rs, "C", "C", nodeToPrefix("C"), 0, 0) + h.NeighUpdate(rs, "C", "A", nodeToPrefix("A"), 0, 1) + h.NeighUpdate(rs, "C", "B", nodeToPrefix("B"), 0, 1) + h.NeighUpdate(rs, "C", "S", nodeToPrefix("S"), 0, 2) ComputeRoutes(rs, h) a := h.GetActions() assert.Equal(t, - `BROADCAST_UPDATE_ROUTE (router: A, svc: A, seqno: 0, metric: 0) -BROADCAST_UPDATE_ROUTE (router: B, svc: B, seqno: 0, metric: 1) -BROADCAST_UPDATE_ROUTE (router: C, svc: C, seqno: 0, metric: 1) -BROADCAST_UPDATE_ROUTE (router: S, svc: S, seqno: 0, metric: 1)`, + `BROADCAST_UPDATE_ROUTE (router: A, prefix: 10.0.0.1/32, seqno: 0, metric: 0) +BROADCAST_UPDATE_ROUTE (router: B, prefix: 10.0.0.2/32, seqno: 0, metric: 1) +BROADCAST_UPDATE_ROUTE (router: C, prefix: 10.0.0.3/32, seqno: 0, metric: 1) +BROADCAST_UPDATE_ROUTE (router: S, prefix: 10.0.0.19/32, seqno: 0, metric: 1)`, a.String()) - assert.Equal(t, `A via (nh: A, router: A, svc: A, seqno: 0, metric: 0) -B via (nh: B, router: B, svc: B, seqno: 0, metric: 1) -C via (nh: C, router: C, svc: C, seqno: 0, metric: 1) -S via (nh: S, router: S, svc: S, seqno: 0, metric: 1)`, rs.StringRoutes()) + assert.Equal(t, `10.0.0.1/32 via (nh: A, router: A, prefix: 10.0.0.1/32, seqno: 0, metric: 0) +10.0.0.19/32 via (nh: S, router: S, prefix: 10.0.0.19/32, seqno: 0, metric: 1) +10.0.0.2/32 via (nh: B, router: B, prefix: 10.0.0.2/32, seqno: 0, metric: 1) +10.0.0.3/32 via (nh: C, router: C, prefix: 10.0.0.3/32, seqno: 0, metric: 1)`, rs.StringRoutes()) // Suppose now the cost to S is increased to 10 // B @@ -103,10 +119,10 @@ S via (nh: S, router: S, svc: S, seqno: 0, metric: 1)`, rs.StringRoutes()) sr.metric = 10 ComputeRoutes(rs, h) // B advertises S to A - h.NeighUpdate(rs, "B", "S", 0, 2) + h.NeighUpdate(rs, "B", "S", nodeToPrefix("S"), 0, 2) a = h.GetActions() assert.Equal(t, - `REQUEST_SEQNO B (router: S, svc: S) 1 64`, + `REQUEST_SEQNO B (router: S, prefix: 10.0.0.19/32) 1 64`, a.String()) // Suppose now the link to S goes down @@ -123,8 +139,8 @@ S via (nh: S, router: S, svc: S, seqno: 0, metric: 1)`, rs.StringRoutes()) // We should retract our route to S a.AssertContains(t, "BROADCAST_UPDATE_ROUTE", state.PubRoute{ Source: state.Source{ - NodeId: "S", - ServiceId: "S", + NodeId: "S", + Prefix: nodeToPrefix("S"), }, FD: state.FD{ Seqno: 0, @@ -147,41 +163,41 @@ func TestRouterNet2S_SolveStarvation(t *testing.T) { h := &RouterHarness{} rs := &state.RouterState{ Id: "S", - SelfSeqno: make(map[state.ServiceId]uint16), - Routes: make(map[state.ServiceId]state.SelRoute), + 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[state.ServiceId]state.Advertisement{"S": {state.NodeId("S"), maxTime, false}}, + Advertised: map[netip.Prefix]state.Advertisement{nodeToPrefix("S"): {state.NodeId("S"), maxTime, false}}, } AS := AddLink(rs, NewMockEndpoint("A", 1)) _ = AddLink(rs, NewMockEndpoint("B", 2)) // A's advertised routes - h.NeighUpdate(rs, "A", "S", 0, 1) - h.NeighUpdate(rs, "A", "A", 0, 0) - h.NeighUpdate(rs, "A", "B", 0, 1) + h.NeighUpdate(rs, "A", "S", nodeToPrefix("S"), 0, 1) + h.NeighUpdate(rs, "A", "A", nodeToPrefix("A"), 0, 0) + h.NeighUpdate(rs, "A", "B", nodeToPrefix("B"), 0, 1) // B's advertised routes - h.NeighUpdate(rs, "B", "B", 0, 0) - h.NeighUpdate(rs, "B", "A", 0, 1) - h.NeighUpdate(rs, "B", "S", 0, 2) + h.NeighUpdate(rs, "B", "B", nodeToPrefix("B"), 0, 0) + h.NeighUpdate(rs, "B", "A", nodeToPrefix("A"), 0, 1) + h.NeighUpdate(rs, "B", "S", nodeToPrefix("S"), 0, 2) ComputeRoutes(rs, h) a := h.GetActions() assert.Equal(t, - `BROADCAST_UPDATE_ROUTE (router: A, svc: A, seqno: 0, metric: 1) -BROADCAST_UPDATE_ROUTE (router: B, svc: B, seqno: 0, metric: 2) -BROADCAST_UPDATE_ROUTE (router: S, svc: S, seqno: 0, metric: 0)`, + `BROADCAST_UPDATE_ROUTE (router: A, prefix: 10.0.0.1/32, seqno: 0, metric: 1) +BROADCAST_UPDATE_ROUTE (router: B, prefix: 10.0.0.2/32, seqno: 0, metric: 2) +BROADCAST_UPDATE_ROUTE (router: S, prefix: 10.0.0.19/32, seqno: 0, metric: 0)`, a.String()) - assert.Equal(t, `A via (nh: A, router: A, svc: A, seqno: 0, metric: 1) -B via (nh: B, router: B, svc: B, seqno: 0, metric: 2) -S via (nh: S, router: S, svc: S, seqno: 0, metric: 0)`, rs.StringRoutes()) + assert.Equal(t, `10.0.0.1/32 via (nh: A, router: A, prefix: 10.0.0.1/32, seqno: 0, metric: 1) +10.0.0.19/32 via (nh: S, router: S, prefix: 10.0.0.19/32, seqno: 0, metric: 0) +10.0.0.2/32 via (nh: B, router: B, prefix: 10.0.0.2/32, seqno: 0, metric: 2)`, rs.StringRoutes()) // check feasibility distances - assert.Equal(t, state.FD{Seqno: 0, Metric: 1}, rs.Sources[state.Source{NodeId: "A", ServiceId: "A"}]) - assert.Equal(t, state.FD{Seqno: 0, Metric: 2}, rs.Sources[state.Source{NodeId: "B", ServiceId: "B"}]) - assert.Equal(t, state.FD{Seqno: 0, Metric: 0}, rs.Sources[state.Source{NodeId: "S", ServiceId: "S"}]) + assert.Equal(t, state.FD{Seqno: 0, Metric: 1}, rs.Sources[state.Source{NodeId: "A", Prefix: nodeToPrefix("A")}]) + assert.Equal(t, state.FD{Seqno: 0, Metric: 2}, rs.Sources[state.Source{NodeId: "B", Prefix: nodeToPrefix("B")}]) + assert.Equal(t, state.FD{Seqno: 0, Metric: 0}, rs.Sources[state.Source{NodeId: "S", Prefix: nodeToPrefix("S")}]) // Suppose now that the link to A goes down // A @@ -198,8 +214,8 @@ S via (nh: S, router: S, svc: S, seqno: 0, metric: 0)`, rs.StringRoutes()) // We should retract our route to A a.AssertContains(t, "BROADCAST_UPDATE_ROUTE", state.PubRoute{ Source: state.Source{ - NodeId: "A", - ServiceId: "A", + NodeId: "A", + Prefix: nodeToPrefix("A"), }, FD: state.FD{ Seqno: 0, @@ -207,23 +223,23 @@ S via (nh: S, router: S, svc: S, seqno: 0, metric: 0)`, rs.StringRoutes()) }, }) // B acknowledges the retraction - HandleAckRetract(rs, h, "B", "A") + HandleAckRetract(rs, h, "B", nodeToPrefix("A")) ComputeRoutes(rs, h) a = h.GetActions() // check that we are indeed starved a.AssertNotContains(t, "BROADCAST_UPDATE_ROUTE") SolveStarvation(rs, h) a = h.GetActions() - a.AssertContains(t, "BROADCAST_REQUEST_SEQNO", state.Source{NodeId: "A", ServiceId: "A"}, uint16(1), uint8(64)) + a.AssertContains(t, "BROADCAST_REQUEST_SEQNO", state.Source{NodeId: "A", Prefix: nodeToPrefix("A")}, uint16(1), uint8(64)) // suppose now that A receives the seqno request, sends an update to B, and B sends it to S - h.NeighUpdate(rs, "B", "A", 1, 1) + h.NeighUpdate(rs, "B", "A", nodeToPrefix("A"), 1, 1) ComputeRoutes(rs, h) a = h.GetActions() pr := state.PubRoute{ Source: state.Source{ - NodeId: "A", - ServiceId: "A", + NodeId: "A", + Prefix: nodeToPrefix("A"), }, FD: state.FD{ Seqno: 1, @@ -231,7 +247,7 @@ S via (nh: S, router: S, svc: S, seqno: 0, metric: 0)`, rs.StringRoutes()) }, } a.AssertContains(t, "BROADCAST_UPDATE_ROUTE", pr) - assert.Equal(t, pr, rs.Routes[("A")].PubRoute) + assert.Equal(t, pr, rs.Routes[nodeToPrefix("A")].PubRoute) } func TestRouterNet3A_HandleRetraction(t *testing.T) { @@ -249,41 +265,41 @@ func TestRouterNet3A_HandleRetraction(t *testing.T) { h := &RouterHarness{} rs := &state.RouterState{ Id: "A", - SelfSeqno: make(map[state.ServiceId]uint16), - Routes: make(map[state.ServiceId]state.SelRoute), + 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[state.ServiceId]state.Advertisement{"A": {state.NodeId("A"), maxTime, false}}, + Advertised: map[netip.Prefix]state.Advertisement{nodeToPrefix("A"): {state.NodeId("A"), maxTime, false}}, } _ = AddLink(rs, NewMockEndpoint("B", 1)) _ = AddLink(rs, NewMockEndpoint("C", 3)) // B's advertised routes - h.NeighUpdate(rs, "B", "A", 0, 1) - h.NeighUpdate(rs, "B", "B", 0, 0) - h.NeighUpdate(rs, "B", "C", 0, 1) - h.NeighUpdate(rs, "B", "D", 0, 2) + h.NeighUpdate(rs, "B", "A", nodeToPrefix("A"), 0, 1) + h.NeighUpdate(rs, "B", "B", nodeToPrefix("B"), 0, 0) + h.NeighUpdate(rs, "B", "C", nodeToPrefix("C"), 0, 1) + h.NeighUpdate(rs, "B", "D", nodeToPrefix("D"), 0, 2) // C's advertised routes - h.NeighUpdate(rs, "C", "A", 0, 3) - h.NeighUpdate(rs, "C", "B", 0, 1) - h.NeighUpdate(rs, "C", "C", 0, 0) - h.NeighUpdate(rs, "C", "D", 0, 1) + h.NeighUpdate(rs, "C", "A", nodeToPrefix("A"), 0, 3) + h.NeighUpdate(rs, "C", "B", nodeToPrefix("B"), 0, 1) + h.NeighUpdate(rs, "C", "C", nodeToPrefix("C"), 0, 0) + h.NeighUpdate(rs, "C", "D", nodeToPrefix("D"), 0, 1) ComputeRoutes(rs, h) a := h.GetActions() // check that we converge to the correct table assert.Equal(t, - `BROADCAST_UPDATE_ROUTE (router: A, svc: A, seqno: 0, metric: 0) -BROADCAST_UPDATE_ROUTE (router: B, svc: B, seqno: 0, metric: 1) -BROADCAST_UPDATE_ROUTE (router: C, svc: C, seqno: 0, metric: 2) -BROADCAST_UPDATE_ROUTE (router: D, svc: D, seqno: 0, metric: 3)`, + `BROADCAST_UPDATE_ROUTE (router: A, prefix: 10.0.0.1/32, seqno: 0, metric: 0) +BROADCAST_UPDATE_ROUTE (router: B, prefix: 10.0.0.2/32, seqno: 0, metric: 1) +BROADCAST_UPDATE_ROUTE (router: C, prefix: 10.0.0.3/32, seqno: 0, metric: 2) +BROADCAST_UPDATE_ROUTE (router: D, prefix: 10.0.0.4/32, seqno: 0, metric: 3)`, a.String()) - assert.Equal(t, `A via (nh: A, router: A, svc: A, seqno: 0, metric: 0) -B via (nh: B, router: B, svc: B, seqno: 0, metric: 1) -C via (nh: B, router: C, svc: C, seqno: 0, metric: 2) -D via (nh: B, router: D, svc: D, seqno: 0, metric: 3)`, rs.StringRoutes()) + assert.Equal(t, `10.0.0.1/32 via (nh: A, router: A, prefix: 10.0.0.1/32, seqno: 0, metric: 0) +10.0.0.2/32 via (nh: B, router: B, prefix: 10.0.0.2/32, seqno: 0, metric: 1) +10.0.0.3/32 via (nh: B, router: C, prefix: 10.0.0.3/32, seqno: 0, metric: 2) +10.0.0.4/32 via (nh: B, router: D, prefix: 10.0.0.4/32, seqno: 0, metric: 3)`, rs.StringRoutes()) // Suppose now that the link between B and C goes down // 2 @@ -296,20 +312,20 @@ D via (nh: B, router: D, svc: D, seqno: 0, metric: 3)`, rs.StringRoutes()) // C // C will retract its route to B - h.NeighUpdate(rs, "C", "B", 0, state.INF) + h.NeighUpdate(rs, "C", "B", nodeToPrefix("B"), 0, state.INF) a = h.GetActions() - a.AssertContains(t, "ACK_RETRACT", state.NodeId("C"), state.ServiceId("B")) + a.AssertContains(t, "ACK_RETRACT", state.NodeId("C"), nodeToPrefix("B")) // B will retract its route to C and D - h.NeighUpdate(rs, "B", "C", 0, state.INF) - h.NeighUpdate(rs, "B", "D", 0, state.INF) + h.NeighUpdate(rs, "B", "C", nodeToPrefix("C"), 0, state.INF) + h.NeighUpdate(rs, "B", "D", nodeToPrefix("D"), 0, state.INF) ComputeRoutes(rs, h) a = h.GetActions() - a.AssertContains(t, "ACK_RETRACT", state.NodeId("B"), state.ServiceId("C")) - a.AssertContains(t, "ACK_RETRACT", state.NodeId("B"), state.ServiceId("D")) + a.AssertContains(t, "ACK_RETRACT", state.NodeId("B"), nodeToPrefix("C")) + a.AssertContains(t, "ACK_RETRACT", state.NodeId("B"), nodeToPrefix("D")) // D via C is feasible as C advertises D with a cost of 1, which is less than B's 2 - assert.Equal(t, uint32(4), rs.Routes["D"].Metric) + assert.Equal(t, uint32(4), rs.Routes[nodeToPrefix("D")].Metric) } func TestRouterNet4A_OverlappingServiceHoldLoop(t *testing.T) { @@ -325,11 +341,11 @@ func TestRouterNet4A_OverlappingServiceHoldLoop(t *testing.T) { h := &RouterHarness{} rs := &state.RouterState{ Id: "A", - SelfSeqno: make(map[state.ServiceId]uint16), - Routes: make(map[state.ServiceId]state.SelRoute), + 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[state.ServiceId]state.Advertisement{"A": {state.NodeId("A"), maxTime, false}}, + Advertised: map[netip.Prefix]state.Advertisement{nodeToPrefix("A"): {state.NodeId("A"), maxTime, false}}, } SA := AddLink(rs, NewMockEndpoint("S", 1)) @@ -337,28 +353,28 @@ func TestRouterNet4A_OverlappingServiceHoldLoop(t *testing.T) { _ = AddLink(rs, NewMockEndpoint("B", 1)) // S's advertised routes - h.NeighUpdate(rs, "S", "S", 0, 0) - h.NeighUpdateSvc(rs, "S", "S", "X", 0, 0) + h.NeighUpdate(rs, "S", "S", nodeToPrefix("S"), 0, 0) + h.NeighUpdateSvc(rs, "S", "S", nodeToPrefix("X"), 0, 0) // B's advertised routes - h.NeighUpdate(rs, "B", "B", 0, 0) - h.NeighUpdateSvc(rs, "B", "D", "X", 0, 1) + h.NeighUpdate(rs, "B", "B", nodeToPrefix("B"), 0, 0) + h.NeighUpdateSvc(rs, "B", "D", nodeToPrefix("X"), 0, 1) // C's advertised routes - h.NeighUpdate(rs, "C", "C", 0, 0) + h.NeighUpdate(rs, "C", "C", nodeToPrefix("C"), 0, 0) ComputeRoutes(rs, h) a := h.GetActions() - assert.Equal(t, `BROADCAST_UPDATE_ROUTE (router: A, svc: A, seqno: 0, metric: 0) -BROADCAST_UPDATE_ROUTE (router: B, svc: B, seqno: 0, metric: 1) -BROADCAST_UPDATE_ROUTE (router: C, svc: C, seqno: 0, metric: 1) -BROADCAST_UPDATE_ROUTE (router: S, svc: S, seqno: 0, metric: 1) -BROADCAST_UPDATE_ROUTE (router: S, svc: X, seqno: 0, metric: 1)`, a.String()) - assert.Equal(t, `A via (nh: A, router: A, svc: A, seqno: 0, metric: 0) -B via (nh: B, router: B, svc: B, seqno: 0, metric: 1) -C via (nh: C, router: C, svc: C, seqno: 0, metric: 1) -S via (nh: S, router: S, svc: S, seqno: 0, metric: 1) -X via (nh: S, router: S, svc: X, seqno: 0, metric: 1)`, rs.StringRoutes()) + assert.Equal(t, `BROADCAST_UPDATE_ROUTE (router: A, prefix: 10.0.0.1/32, seqno: 0, metric: 0) +BROADCAST_UPDATE_ROUTE (router: B, prefix: 10.0.0.2/32, seqno: 0, metric: 1) +BROADCAST_UPDATE_ROUTE (router: C, prefix: 10.0.0.3/32, seqno: 0, metric: 1) +BROADCAST_UPDATE_ROUTE (router: S, prefix: 10.0.0.19/32, seqno: 0, metric: 1) +BROADCAST_UPDATE_ROUTE (router: S, prefix: 10.0.0.24/32, seqno: 0, metric: 1)`, a.String()) + assert.Equal(t, `10.0.0.1/32 via (nh: A, router: A, prefix: 10.0.0.1/32, seqno: 0, metric: 0) +10.0.0.19/32 via (nh: S, router: S, prefix: 10.0.0.19/32, seqno: 0, metric: 1) +10.0.0.2/32 via (nh: B, router: B, prefix: 10.0.0.2/32, seqno: 0, metric: 1) +10.0.0.24/32 via (nh: S, router: S, prefix: 10.0.0.24/32, seqno: 0, metric: 1) +10.0.0.3/32 via (nh: C, router: C, prefix: 10.0.0.3/32, seqno: 0, metric: 1)`, rs.StringRoutes()) // Now, lets cut off both S from A and D from B, to see if we can produce a routing loop // C @@ -368,26 +384,26 @@ X via (nh: S, router: S, svc: X, seqno: 0, metric: 1)`, rs.StringRoutes()) RemoveLink(rs, SA) ComputeRoutes(rs, h) a = h.GetActions() - assert.Equal(t, `BROADCAST_UPDATE_ROUTE (router: S, svc: S, seqno: 0, metric: 4294967295) -BROADCAST_UPDATE_ROUTE (router: S, svc: X, seqno: 0, metric: 4294967295)`, a.String()) - HandleAckRetract(rs, h, "B", "S") - HandleAckRetract(rs, h, "B", "X") + assert.Equal(t, `BROADCAST_UPDATE_ROUTE (router: S, prefix: 10.0.0.19/32, seqno: 0, metric: 4294967295) +BROADCAST_UPDATE_ROUTE (router: S, prefix: 10.0.0.24/32, seqno: 0, metric: 4294967295)`, a.String()) + HandleAckRetract(rs, h, "B", nodeToPrefix("S")) + HandleAckRetract(rs, h, "B", nodeToPrefix("X")) ComputeRoutes(rs, h) a = h.GetActions() assert.Empty(t, a, "Expect S and X to be held until C also sends ACK") - HandleAckRetract(rs, h, "C", "S") - HandleAckRetract(rs, h, "C", "X") + HandleAckRetract(rs, h, "C", nodeToPrefix("S")) + HandleAckRetract(rs, h, "C", nodeToPrefix("X")) ComputeRoutes(rs, h) a = h.GetActions() - assert.Equal(t, `BROADCAST_UPDATE_ROUTE (router: D, svc: X, seqno: 0, metric: 2)`, a.String()) + assert.Equal(t, `BROADCAST_UPDATE_ROUTE (router: D, prefix: 10.0.0.24/32, seqno: 0, metric: 2)`, a.String()) // B retracts D's published routes - h.NeighUpdate(rs, "B", "D", 0, state.INF) - h.NeighUpdateSvc(rs, "B", "D", "X", 0, state.INF) + h.NeighUpdate(rs, "B", "D", nodeToPrefix("D"), 0, state.INF) + h.NeighUpdateSvc(rs, "B", "D", nodeToPrefix("X"), 0, state.INF) ComputeRoutes(rs, h) a = h.GetActions() - assert.Equal(t, `ACK_RETRACT B D -ACK_RETRACT B X -BROADCAST_UPDATE_ROUTE (router: D, svc: X, seqno: 0, metric: 4294967295)`, a.String()) + assert.Equal(t, `ACK_RETRACT B 10.0.0.24/32 +ACK_RETRACT B 10.0.0.4/32 +BROADCAST_UPDATE_ROUTE (router: D, prefix: 10.0.0.24/32, seqno: 0, metric: 4294967295)`, a.String()) } func TestRouterNet4A_OverlappingServiceMetricIncrease(t *testing.T) { @@ -403,11 +419,11 @@ func TestRouterNet4A_OverlappingServiceMetricIncrease(t *testing.T) { h := &RouterHarness{} rs := &state.RouterState{ Id: "A", - SelfSeqno: make(map[state.ServiceId]uint16), - Routes: make(map[state.ServiceId]state.SelRoute), + 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[state.ServiceId]state.Advertisement{"A": {state.NodeId("A"), maxTime, false}}, + Advertised: map[netip.Prefix]state.Advertisement{nodeToPrefix("A"): {state.NodeId("A"), maxTime, false}}, } SA := AddLink(rs, NewMockEndpoint("S", 1)) @@ -415,28 +431,28 @@ func TestRouterNet4A_OverlappingServiceMetricIncrease(t *testing.T) { _ = AddLink(rs, NewMockEndpoint("B", 1)) // S's advertised routes - h.NeighUpdate(rs, "S", "S", 0, 0) - h.NeighUpdateSvc(rs, "S", "S", "X", 0, 0) + h.NeighUpdate(rs, "S", "S", nodeToPrefix("S"), 0, 0) + h.NeighUpdateSvc(rs, "S", "S", nodeToPrefix("X"), 0, 0) // B's advertised routes - h.NeighUpdate(rs, "B", "B", 0, 0) - h.NeighUpdateSvc(rs, "B", "D", "X", 0, 4) + h.NeighUpdate(rs, "B", "B", nodeToPrefix("B"), 0, 0) + h.NeighUpdateSvc(rs, "B", "D", nodeToPrefix("X"), 0, 4) // C's advertised routes - h.NeighUpdate(rs, "C", "C", 0, 0) + h.NeighUpdate(rs, "C", "C", nodeToPrefix("C"), 0, 0) ComputeRoutes(rs, h) a := h.GetActions() - assert.Equal(t, `BROADCAST_UPDATE_ROUTE (router: A, svc: A, seqno: 0, metric: 0) -BROADCAST_UPDATE_ROUTE (router: B, svc: B, seqno: 0, metric: 1) -BROADCAST_UPDATE_ROUTE (router: C, svc: C, seqno: 0, metric: 1) -BROADCAST_UPDATE_ROUTE (router: S, svc: S, seqno: 0, metric: 1) -BROADCAST_UPDATE_ROUTE (router: S, svc: X, seqno: 0, metric: 1)`, a.String()) - assert.Equal(t, `A via (nh: A, router: A, svc: A, seqno: 0, metric: 0) -B via (nh: B, router: B, svc: B, seqno: 0, metric: 1) -C via (nh: C, router: C, svc: C, seqno: 0, metric: 1) -S via (nh: S, router: S, svc: S, seqno: 0, metric: 1) -X via (nh: S, router: S, svc: X, seqno: 0, metric: 1)`, rs.StringRoutes()) + assert.Equal(t, `BROADCAST_UPDATE_ROUTE (router: A, prefix: 10.0.0.1/32, seqno: 0, metric: 0) +BROADCAST_UPDATE_ROUTE (router: B, prefix: 10.0.0.2/32, seqno: 0, metric: 1) +BROADCAST_UPDATE_ROUTE (router: C, prefix: 10.0.0.3/32, seqno: 0, metric: 1) +BROADCAST_UPDATE_ROUTE (router: S, prefix: 10.0.0.19/32, seqno: 0, metric: 1) +BROADCAST_UPDATE_ROUTE (router: S, prefix: 10.0.0.24/32, seqno: 0, metric: 1)`, a.String()) + assert.Equal(t, `10.0.0.1/32 via (nh: A, router: A, prefix: 10.0.0.1/32, seqno: 0, metric: 0) +10.0.0.19/32 via (nh: S, router: S, prefix: 10.0.0.19/32, seqno: 0, metric: 1) +10.0.0.2/32 via (nh: B, router: B, prefix: 10.0.0.2/32, seqno: 0, metric: 1) +10.0.0.24/32 via (nh: S, router: S, prefix: 10.0.0.24/32, seqno: 0, metric: 1) +10.0.0.3/32 via (nh: C, router: C, prefix: 10.0.0.3/32, seqno: 0, metric: 1)`, rs.StringRoutes()) // Suppose now that SA's link cost increases to 2 // C // | 1 @@ -448,40 +464,40 @@ X via (nh: S, router: S, svc: X, seqno: 0, metric: 1)`, rs.StringRoutes()) assert.Empty(t, a, "We should not change routes as S is still feasible") // However, for C, Cost(A, S) = 3 > 2, meaning S is no longer feasible via A // C should send a seqno request to A - HandleSeqnoRequest(rs, h, "C", state.Source{NodeId: "S", ServiceId: "X"}, 1, 64) + HandleSeqnoRequest(rs, h, "C", state.Source{NodeId: "S", Prefix: nodeToPrefix("X")}, 1, 64) a = h.GetActions() // A should forward the request to S, decrementing the TTL by 1 - assert.Equal(t, `REQUEST_SEQNO S (router: S, svc: X) 1 63`, a.String()) + assert.Equal(t, `REQUEST_SEQNO S (router: S, prefix: 10.0.0.24/32) 1 63`, a.String()) // Now, S replies with an update with a higher seqno - h.NeighUpdateSvc(rs, "S", "S", "X", 1, 0) + h.NeighUpdateSvc(rs, "S", "S", nodeToPrefix("X"), 1, 0) ComputeRoutes(rs, h) a = h.GetActions() - assert.Equal(t, `BROADCAST_UPDATE_ROUTE (router: S, svc: X, seqno: 1, metric: 3)`, a.String()) + assert.Equal(t, `BROADCAST_UPDATE_ROUTE (router: S, prefix: 10.0.0.24/32, seqno: 1, metric: 3)`, a.String()) // Suppose, some other node also requests the seqno for S,X - HandleSeqnoRequest(rs, h, "B", state.Source{NodeId: "S", ServiceId: "X"}, 1, 64) + HandleSeqnoRequest(rs, h, "B", state.Source{NodeId: "S", Prefix: nodeToPrefix("X")}, 1, 64) // A should not forward the request as we already have a route to S with an equivalent or higher seqno a = h.GetActions() // Instead, A should just reply with its current route to S,X - assert.Equal(t, `UPDATE_ROUTE B (router: S, svc: X, seqno: 1, metric: 3)`, a.String()) + assert.Equal(t, `UPDATE_ROUTE B (router: S, prefix: 10.0.0.24/32, seqno: 1, metric: 3)`, a.String()) // Now, suppose some node requests the seqno for A // Req 1: A should not increase its seqno - HandleSeqnoRequest(rs, h, "B", state.Source{NodeId: "A", ServiceId: "A"}, 0, 64) + HandleSeqnoRequest(rs, h, "B", state.Source{NodeId: "A", Prefix: nodeToPrefix("A")}, 0, 64) a = h.GetActions() - assert.Equal(t, `UPDATE_ROUTE B (router: A, svc: A, seqno: 0, metric: 0)`, a.String()) + assert.Equal(t, `UPDATE_ROUTE B (router: A, prefix: 10.0.0.1/32, seqno: 0, metric: 0)`, a.String()) // Req 2: A should increase its seqno by 1 - HandleSeqnoRequest(rs, h, "B", state.Source{NodeId: "A", ServiceId: "A"}, 1, 64) + HandleSeqnoRequest(rs, h, "B", state.Source{NodeId: "A", Prefix: nodeToPrefix("A")}, 1, 64) a = h.GetActions() - assert.Equal(t, `BROADCAST_UPDATE_ROUTE (router: A, svc: A, seqno: 1, metric: 0)`, a.String()) + assert.Equal(t, `BROADCAST_UPDATE_ROUTE (router: A, prefix: 10.0.0.1/32, seqno: 1, metric: 0)`, a.String()) // Req 3: A should increase its seqno to 5 - HandleSeqnoRequest(rs, h, "B", state.Source{NodeId: "A", ServiceId: "A"}, 5, 64) + HandleSeqnoRequest(rs, h, "B", state.Source{NodeId: "A", Prefix: nodeToPrefix("A")}, 5, 64) a = h.GetActions() - assert.Equal(t, `BROADCAST_UPDATE_ROUTE (router: A, svc: A, seqno: 5, metric: 0)`, a.String()) + assert.Equal(t, `BROADCAST_UPDATE_ROUTE (router: A, prefix: 10.0.0.1/32, seqno: 5, metric: 0)`, a.String()) } func TestRouterNet5A_SelectedUnfeasibleUpdate(t *testing.T) { @@ -499,41 +515,41 @@ func TestRouterNet5A_SelectedUnfeasibleUpdate(t *testing.T) { h := &RouterHarness{} rs := &state.RouterState{ Id: "A", - SelfSeqno: make(map[state.ServiceId]uint16), - Routes: make(map[state.ServiceId]state.SelRoute), + 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[state.ServiceId]state.Advertisement{"A": {state.NodeId("A"), maxTime, false}}, + Advertised: map[netip.Prefix]state.Advertisement{nodeToPrefix("A"): {state.NodeId("A"), maxTime, false}}, } _ = AddLink(rs, NewMockEndpoint("B", 1)) _ = AddLink(rs, NewMockEndpoint("C", 5)) // B's advertised routes - h.NeighUpdate(rs, "B", "A", 0, 1) - h.NeighUpdate(rs, "B", "B", 0, 0) - h.NeighUpdate(rs, "B", "C", 0, 1) - h.NeighUpdate(rs, "B", "D", 0, 2) + h.NeighUpdate(rs, "B", "A", nodeToPrefix("A"), 0, 1) + h.NeighUpdate(rs, "B", "B", nodeToPrefix("B"), 0, 0) + h.NeighUpdate(rs, "B", "C", nodeToPrefix("C"), 0, 1) + h.NeighUpdate(rs, "B", "D", nodeToPrefix("D"), 0, 2) // C's advertised routes - h.NeighUpdate(rs, "C", "A", 0, 5) - h.NeighUpdate(rs, "C", "B", 0, 1) - h.NeighUpdate(rs, "C", "C", 0, 0) - h.NeighUpdate(rs, "C", "D", 0, 1) + h.NeighUpdate(rs, "C", "A", nodeToPrefix("A"), 0, 5) + h.NeighUpdate(rs, "C", "B", nodeToPrefix("B"), 0, 1) + h.NeighUpdate(rs, "C", "C", nodeToPrefix("C"), 0, 0) + h.NeighUpdate(rs, "C", "D", nodeToPrefix("D"), 0, 1) ComputeRoutes(rs, h) a := h.GetActions() // check that we converge to the correct table assert.Equal(t, - `BROADCAST_UPDATE_ROUTE (router: A, svc: A, seqno: 0, metric: 0) -BROADCAST_UPDATE_ROUTE (router: B, svc: B, seqno: 0, metric: 1) -BROADCAST_UPDATE_ROUTE (router: C, svc: C, seqno: 0, metric: 2) -BROADCAST_UPDATE_ROUTE (router: D, svc: D, seqno: 0, metric: 3)`, + `BROADCAST_UPDATE_ROUTE (router: A, prefix: 10.0.0.1/32, seqno: 0, metric: 0) +BROADCAST_UPDATE_ROUTE (router: B, prefix: 10.0.0.2/32, seqno: 0, metric: 1) +BROADCAST_UPDATE_ROUTE (router: C, prefix: 10.0.0.3/32, seqno: 0, metric: 2) +BROADCAST_UPDATE_ROUTE (router: D, prefix: 10.0.0.4/32, seqno: 0, metric: 3)`, a.String()) - assert.Equal(t, `A via (nh: A, router: A, svc: A, seqno: 0, metric: 0) -B via (nh: B, router: B, svc: B, seqno: 0, metric: 1) -C via (nh: B, router: C, svc: C, seqno: 0, metric: 2) -D via (nh: B, router: D, svc: D, seqno: 0, metric: 3)`, rs.StringRoutes()) + assert.Equal(t, `10.0.0.1/32 via (nh: A, router: A, prefix: 10.0.0.1/32, seqno: 0, metric: 0) +10.0.0.2/32 via (nh: B, router: B, prefix: 10.0.0.2/32, seqno: 0, metric: 1) +10.0.0.3/32 via (nh: B, router: C, prefix: 10.0.0.3/32, seqno: 0, metric: 2) +10.0.0.4/32 via (nh: B, router: D, prefix: 10.0.0.4/32, seqno: 0, metric: 3)`, rs.StringRoutes()) // Suppose now that the link between B and C increases in metric // 2 @@ -545,21 +561,21 @@ D via (nh: B, router: D, svc: D, seqno: 0, metric: 3)`, rs.StringRoutes()) // 5 \| / // C - h.NeighUpdate(rs, "B", "C", 0, 3) - h.NeighUpdate(rs, "B", "D", 0, 3) - h.NeighUpdate(rs, "C", "B", 0, 3) + h.NeighUpdate(rs, "B", "C", nodeToPrefix("C"), 0, 3) + h.NeighUpdate(rs, "B", "D", nodeToPrefix("D"), 0, 3) + h.NeighUpdate(rs, "C", "B", nodeToPrefix("B"), 0, 3) ComputeRoutes(rs, h) a = h.GetActions() - assert.Equal(t, `REQUEST_SEQNO B (router: C, svc: C) 1 64 -REQUEST_SEQNO B (router: D, svc: D) 1 64`, a.String()) + assert.Equal(t, `REQUEST_SEQNO B (router: C, prefix: 10.0.0.3/32) 1 64 +REQUEST_SEQNO B (router: D, prefix: 10.0.0.4/32) 1 64`, a.String()) // Now, we get the seqno updates from B - h.NeighUpdate(rs, "B", "C", 1, 3) - h.NeighUpdate(rs, "B", "D", 1, 3) + h.NeighUpdate(rs, "B", "C", nodeToPrefix("C"), 1, 3) + h.NeighUpdate(rs, "B", "D", nodeToPrefix("D"), 1, 3) ComputeRoutes(rs, h) a = h.GetActions() - assert.Equal(t, `BROADCAST_UPDATE_ROUTE (router: C, svc: C, seqno: 1, metric: 4) -BROADCAST_UPDATE_ROUTE (router: D, svc: D, seqno: 1, metric: 4)`, a.String()) + assert.Equal(t, `BROADCAST_UPDATE_ROUTE (router: C, prefix: 10.0.0.3/32, seqno: 1, metric: 4) +BROADCAST_UPDATE_ROUTE (router: D, prefix: 10.0.0.4/32, seqno: 1, metric: 4)`, a.String()) } func TestRouter5A_GCRoutes(t *testing.T) { @@ -578,47 +594,47 @@ func TestRouter5A_GCRoutes(t *testing.T) { h := &RouterHarness{} rs := &state.RouterState{ Id: "A", - SelfSeqno: make(map[state.ServiceId]uint16), - Routes: make(map[state.ServiceId]state.SelRoute), + 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[state.ServiceId]state.Advertisement{"A": {state.NodeId("A"), maxTime, false}}, + Advertised: map[netip.Prefix]state.Advertisement{nodeToPrefix("A"): {state.NodeId("A"), maxTime, false}}, } _ = AddLink(rs, NewMockEndpoint("B", 1)) _ = AddLink(rs, NewMockEndpoint("C", 5)) // B's advertised routes - h.NeighUpdate(rs, "B", "A", 0, 1) - h.NeighUpdate(rs, "B", "B", 0, 0) - h.NeighUpdate(rs, "B", "C", 0, 1) - h.NeighUpdate(rs, "B", "D", 0, 2) + h.NeighUpdate(rs, "B", "A", nodeToPrefix("A"), 0, 1) + h.NeighUpdate(rs, "B", "B", nodeToPrefix("B"), 0, 0) + h.NeighUpdate(rs, "B", "C", nodeToPrefix("C"), 0, 1) + h.NeighUpdate(rs, "B", "D", nodeToPrefix("D"), 0, 2) // C's advertised routes - h.NeighUpdate(rs, "C", "A", 0, 5) - h.NeighUpdate(rs, "C", "B", 0, 1) - h.NeighUpdate(rs, "C", "C", 0, 0) - h.NeighUpdate(rs, "C", "D", 0, 1) + h.NeighUpdate(rs, "C", "A", nodeToPrefix("A"), 0, 5) + h.NeighUpdate(rs, "C", "B", nodeToPrefix("B"), 0, 1) + h.NeighUpdate(rs, "C", "C", nodeToPrefix("C"), 0, 0) + h.NeighUpdate(rs, "C", "D", nodeToPrefix("D"), 0, 1) ComputeRoutes(rs, h) a := h.GetActions() // check that we converge to the correct table assert.Equal(t, - `BROADCAST_UPDATE_ROUTE (router: A, svc: A, seqno: 0, metric: 0) -BROADCAST_UPDATE_ROUTE (router: B, svc: B, seqno: 0, metric: 1) -BROADCAST_UPDATE_ROUTE (router: C, svc: C, seqno: 0, metric: 2) -BROADCAST_UPDATE_ROUTE (router: D, svc: D, seqno: 0, metric: 3)`, + `BROADCAST_UPDATE_ROUTE (router: A, prefix: 10.0.0.1/32, seqno: 0, metric: 0) +BROADCAST_UPDATE_ROUTE (router: B, prefix: 10.0.0.2/32, seqno: 0, metric: 1) +BROADCAST_UPDATE_ROUTE (router: C, prefix: 10.0.0.3/32, seqno: 0, metric: 2) +BROADCAST_UPDATE_ROUTE (router: D, prefix: 10.0.0.4/32, seqno: 0, metric: 3)`, a.String()) - assert.Equal(t, `A via (nh: A, router: A, svc: A, seqno: 0, metric: 0) -B via (nh: B, router: B, svc: B, seqno: 0, metric: 1) -C via (nh: B, router: C, svc: C, seqno: 0, metric: 2) -D via (nh: B, router: D, svc: D, seqno: 0, metric: 3)`, rs.StringRoutes()) + assert.Equal(t, `10.0.0.1/32 via (nh: A, router: A, prefix: 10.0.0.1/32, seqno: 0, metric: 0) +10.0.0.2/32 via (nh: B, router: B, prefix: 10.0.0.2/32, seqno: 0, metric: 1) +10.0.0.3/32 via (nh: B, router: C, prefix: 10.0.0.3/32, seqno: 0, metric: 2) +10.0.0.4/32 via (nh: B, router: D, prefix: 10.0.0.4/32, seqno: 0, metric: 3)`, rs.StringRoutes()) RunGC(rs, h) // expired routes are not held, so we do not need to wait for retraction ACK a = h.GetActions() - assert.Equal(t, `BROADCAST_UPDATE_ROUTE (router: B, svc: B, seqno: 0, metric: 4294967295) -BROADCAST_UPDATE_ROUTE (router: C, svc: C, seqno: 0, metric: 4294967295) -BROADCAST_UPDATE_ROUTE (router: D, svc: D, seqno: 0, metric: 4294967295)`, a.String()) + assert.Equal(t, `BROADCAST_UPDATE_ROUTE (router: B, prefix: 10.0.0.2/32, seqno: 0, metric: 4294967295) +BROADCAST_UPDATE_ROUTE (router: C, prefix: 10.0.0.3/32, seqno: 0, metric: 4294967295) +BROADCAST_UPDATE_ROUTE (router: D, prefix: 10.0.0.4/32, seqno: 0, metric: 4294967295)`, a.String()) RunGC(rs, h) for _, neigh := range rs.Neighbours { @@ -641,34 +657,34 @@ func TestRouterNet6A_ConvergeOptimal(t *testing.T) { h := &RouterHarness{} rs := &state.RouterState{ Id: "A", - SelfSeqno: make(map[state.ServiceId]uint16), - Routes: make(map[state.ServiceId]state.SelRoute), + 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[state.ServiceId]state.Advertisement{"A": {state.NodeId("A"), maxTime, false}}, + Advertised: map[netip.Prefix]state.Advertisement{nodeToPrefix("A"): {state.NodeId("A"), maxTime, false}}, } AB := AddLink(rs, NewMockEndpoint("B", 1)) // B's advertised routes - h.NeighUpdate(rs, "B", "A", 0, 1) - h.NeighUpdate(rs, "B", "B", 0, 0) - h.NeighUpdate(rs, "B", "C", 0, 4) - h.NeighUpdate(rs, "B", "D", 0, 3) + h.NeighUpdate(rs, "B", "A", nodeToPrefix("A"), 0, 1) + h.NeighUpdate(rs, "B", "B", nodeToPrefix("B"), 0, 0) + h.NeighUpdate(rs, "B", "C", nodeToPrefix("C"), 0, 4) + h.NeighUpdate(rs, "B", "D", nodeToPrefix("D"), 0, 3) ComputeRoutes(rs, h) a := h.GetActions() // check that we converge to the correct table assert.Equal(t, - `BROADCAST_UPDATE_ROUTE (router: A, svc: A, seqno: 0, metric: 0) -BROADCAST_UPDATE_ROUTE (router: B, svc: B, seqno: 0, metric: 1) -BROADCAST_UPDATE_ROUTE (router: C, svc: C, seqno: 0, metric: 5) -BROADCAST_UPDATE_ROUTE (router: D, svc: D, seqno: 0, metric: 4)`, + `BROADCAST_UPDATE_ROUTE (router: A, prefix: 10.0.0.1/32, seqno: 0, metric: 0) +BROADCAST_UPDATE_ROUTE (router: B, prefix: 10.0.0.2/32, seqno: 0, metric: 1) +BROADCAST_UPDATE_ROUTE (router: C, prefix: 10.0.0.3/32, seqno: 0, metric: 5) +BROADCAST_UPDATE_ROUTE (router: D, prefix: 10.0.0.4/32, seqno: 0, metric: 4)`, a.String()) - assert.Equal(t, `A via (nh: A, router: A, svc: A, seqno: 0, metric: 0) -B via (nh: B, router: B, svc: B, seqno: 0, metric: 1) -C via (nh: B, router: C, svc: C, seqno: 0, metric: 5) -D via (nh: B, router: D, svc: D, seqno: 0, metric: 4)`, rs.StringRoutes()) + assert.Equal(t, `10.0.0.1/32 via (nh: A, router: A, prefix: 10.0.0.1/32, seqno: 0, metric: 0) +10.0.0.2/32 via (nh: B, router: B, prefix: 10.0.0.2/32, seqno: 0, metric: 1) +10.0.0.3/32 via (nh: B, router: C, prefix: 10.0.0.3/32, seqno: 0, metric: 5) +10.0.0.4/32 via (nh: B, router: D, prefix: 10.0.0.4/32, seqno: 0, metric: 4)`, rs.StringRoutes()) // Suppose now, we introduce a new link // 3 @@ -682,19 +698,19 @@ D via (nh: B, router: D, svc: D, seqno: 0, metric: 4)`, rs.StringRoutes()) AC := AddLink(rs, NewMockEndpoint("C", 6)) // C's advertised routes - h.NeighUpdate(rs, "C", "B", 0, 4) - h.NeighUpdate(rs, "C", "C", 0, 0) - h.NeighUpdate(rs, "C", "D", 0, 1) + h.NeighUpdate(rs, "C", "B", nodeToPrefix("B"), 0, 4) + h.NeighUpdate(rs, "C", "C", nodeToPrefix("C"), 0, 0) + h.NeighUpdate(rs, "C", "D", nodeToPrefix("D"), 0, 1) // this should not change anything, as this route is not optimal ComputeRoutes(rs, h) a = h.GetActions() // check that we converge to the correct table assert.Empty(t, a, "No changes expected") - assert.Equal(t, `A via (nh: A, router: A, svc: A, seqno: 0, metric: 0) -B via (nh: B, router: B, svc: B, seqno: 0, metric: 1) -C via (nh: B, router: C, svc: C, seqno: 0, metric: 5) -D via (nh: B, router: D, svc: D, seqno: 0, metric: 4)`, rs.StringRoutes()) + assert.Equal(t, `10.0.0.1/32 via (nh: A, router: A, prefix: 10.0.0.1/32, seqno: 0, metric: 0) +10.0.0.2/32 via (nh: B, router: B, prefix: 10.0.0.2/32, seqno: 0, metric: 1) +10.0.0.3/32 via (nh: B, router: C, prefix: 10.0.0.3/32, seqno: 0, metric: 5) +10.0.0.4/32 via (nh: B, router: D, prefix: 10.0.0.4/32, seqno: 0, metric: 4)`, rs.StringRoutes()) // Now, we improve the cost of AC to 2 // 3 @@ -711,10 +727,10 @@ D via (nh: B, router: D, svc: D, seqno: 0, metric: 4)`, rs.StringRoutes()) a = h.GetActions() // check that we converge to the correct table assert.Equal(t, ``, a.String()) // not a significant change, so we should not broadcast - assert.Equal(t, `A via (nh: A, router: A, svc: A, seqno: 0, metric: 0) -B via (nh: B, router: B, svc: B, seqno: 0, metric: 1) -C via (nh: C, router: C, svc: C, seqno: 0, metric: 2) -D via (nh: C, router: D, svc: D, seqno: 0, metric: 3)`, rs.StringRoutes()) + assert.Equal(t, `10.0.0.1/32 via (nh: A, router: A, prefix: 10.0.0.1/32, seqno: 0, metric: 0) +10.0.0.2/32 via (nh: B, router: B, prefix: 10.0.0.2/32, seqno: 0, metric: 1) +10.0.0.3/32 via (nh: C, router: C, prefix: 10.0.0.3/32, seqno: 0, metric: 2) +10.0.0.4/32 via (nh: C, router: D, prefix: 10.0.0.4/32, seqno: 0, metric: 3)`, rs.StringRoutes()) // Now, AC degrades to 10000000, and AB degrades to 12000000 AC.metric = 10_000_000 @@ -722,11 +738,11 @@ D via (nh: C, router: D, svc: D, seqno: 0, metric: 3)`, rs.StringRoutes()) ComputeRoutes(rs, h) a = h.GetActions() // this is a significant change, so we should broadcast - assert.Equal(t, `BROADCAST_UPDATE_ROUTE (router: B, svc: B, seqno: 0, metric: 12000000) -BROADCAST_UPDATE_ROUTE (router: C, svc: C, seqno: 0, metric: 10000000) -BROADCAST_UPDATE_ROUTE (router: D, svc: D, seqno: 0, metric: 10000001)`, a.String()) - assert.Equal(t, `A via (nh: A, router: A, svc: A, seqno: 0, metric: 0) -B via (nh: B, router: B, svc: B, seqno: 0, metric: 12000000) -C via (nh: C, router: C, svc: C, seqno: 0, metric: 10000000) -D via (nh: C, router: D, svc: D, seqno: 0, metric: 10000001)`, rs.StringRoutes()) + assert.Equal(t, `BROADCAST_UPDATE_ROUTE (router: B, prefix: 10.0.0.2/32, seqno: 0, metric: 12000000) +BROADCAST_UPDATE_ROUTE (router: C, prefix: 10.0.0.3/32, seqno: 0, metric: 10000000) +BROADCAST_UPDATE_ROUTE (router: D, prefix: 10.0.0.4/32, seqno: 0, metric: 10000001)`, a.String()) + assert.Equal(t, `10.0.0.1/32 via (nh: A, router: A, prefix: 10.0.0.1/32, seqno: 0, metric: 0) +10.0.0.2/32 via (nh: B, router: B, prefix: 10.0.0.2/32, seqno: 0, metric: 12000000) +10.0.0.3/32 via (nh: C, router: C, prefix: 10.0.0.3/32, seqno: 0, metric: 10000000) +10.0.0.4/32 via (nh: C, router: D, prefix: 10.0.0.4/32, seqno: 0, metric: 10000001)`, rs.StringRoutes()) } diff --git a/core/sys_darwin.go b/core/sys_darwin.go index 8953196f..d9ccff55 100644 --- a/core/sys_darwin.go +++ b/core/sys_darwin.go @@ -1,28 +1,15 @@ package core import ( - "fmt" + "log/slog" "net" "net/netip" - "os/exec" - "strings" "github.com/encodeous/nylon/polyamide/ipc" "github.com/encodeous/nylon/polyamide/tun" "github.com/encodeous/nylon/state" ) -func VerifyForwarding() error { - res, err := exec.Command("sysctl", "net.inet.ip.forwarding").CombinedOutput() - if err != nil { - return err - } - if !strings.Contains(string(res), "1") { - return fmt.Errorf("expected net.inet.ip.forwarding = 1 got %s", string(res)) - } - return nil -} - func InitUAPI(e *state.Env, itfName string) (net.Listener, error) { fileUAPI, err := ipc.UAPIOpen(itfName) @@ -33,20 +20,18 @@ func InitUAPI(e *state.Env, itfName string) (net.Listener, error) { return uapi, nil } -func InitInterface(ifName string) error { +func InitInterface(logger *slog.Logger, ifName string) error { return nil } -func ConfigureAlias(ifName string, prefix netip.Prefix) error { - addr := prefix.Addr() +func ConfigureAlias(logger *slog.Logger, ifName string, addr netip.Addr) error { if addr.Is4() { - _, mask, _ := net.ParseCIDR(prefix.String()) - return Exec("/sbin/ifconfig", ifName, "alias", addr.String(), addr.String(), "netmask", net.IP(mask.Mask).String()) + return Exec(logger, "/sbin/ifconfig", ifName, "alias", addr.String(), "255.255.255.255") } else { - return Exec("/sbin/ifconfig", ifName, "inet6", prefix.String(), "add") + return Exec(logger, "/sbin/ifconfig", ifName, "inet6", addr.String(), "alias") } } -func ConfigureRoute(dev tun.Device, itfName string, route netip.Prefix) error { - return Exec("/sbin/route", "-n", "add", "-net", route.String(), "-interface", itfName) +func ConfigureRoute(logger *slog.Logger, dev tun.Device, itfName string, route netip.Prefix) error { + return Exec(logger, "/sbin/route", "-n", "add", "-net", route.String(), "-interface", itfName) } diff --git a/core/sys_linux.go b/core/sys_linux.go index 95971ab4..f7f4e0ec 100644 --- a/core/sys_linux.go +++ b/core/sys_linux.go @@ -1,28 +1,15 @@ package core import ( - "fmt" + "log/slog" "net" "net/netip" - "os" "github.com/encodeous/nylon/polyamide/ipc" "github.com/encodeous/nylon/polyamide/tun" "github.com/encodeous/nylon/state" ) -func VerifyForwarding() error { - forward, err := os.ReadFile("/proc/sys/net/ipv4/ip_forward") - if err != nil { - return err - } - if string(forward) != "1\n" { - return fmt.Errorf("expected /proc/sys/net/ipv4/ip_forward = 1 got %s", string(forward)) - } - // TODO: IPv6 forwarding - return nil -} - func InitUAPI(e *state.Env, itfName string) (net.Listener, error) { fileUAPI, err := ipc.UAPIOpen(itfName) @@ -33,22 +20,18 @@ func InitUAPI(e *state.Env, itfName string) (net.Listener, error) { return uapi, nil } -func InitInterface(ifName string) error { - err := Exec("ip", "link", "set", ifName, "up") +func InitInterface(logger *slog.Logger, ifName string) error { + err := Exec(logger, "ip", "link", "set", ifName, "up") if err != nil { return err } return nil } -func ConfigureAlias(ifName string, prefix netip.Prefix) error { - return Exec("ip", "addr", "add", prefix.String(), "dev", ifName) +func ConfigureAlias(logger *slog.Logger, ifName string, addr netip.Addr) error { + return Exec(logger, "ip", "addr", "add", addr.String(), "dev", ifName) } -func ConfigureRoute(dev tun.Device, itfName string, route netip.Prefix) error { - err := Exec("ip", "route", "flush", route.String()) - if err != nil { - return err - } - return Exec("ip", "route", "add", route.String(), "dev", itfName) +func ConfigureRoute(logger *slog.Logger, dev tun.Device, itfName string, route netip.Prefix) error { + return Exec(logger, "ip", "route", "add", route.String(), "dev", itfName) } diff --git a/core/sys_physical.go b/core/sys_physical.go index 1f8dabf3..d356924e 100644 --- a/core/sys_physical.go +++ b/core/sys_physical.go @@ -4,23 +4,16 @@ package core import ( "fmt" + "runtime" + "strings" + "github.com/encodeous/nylon/polyamide/conn" "github.com/encodeous/nylon/polyamide/device" "github.com/encodeous/nylon/polyamide/tun" "github.com/encodeous/nylon/state" - "runtime" - "strings" ) func NewWireGuardDevice(s *state.State, n *Nylon) (dev *device.Device, tunDevice tun.Device, realItf string, err error) { - if s.UseSystemRouting { - err = VerifyForwarding() - if err != nil { - s.Log.Warn("IP Forwarding is not enabled, routing disabled", "err", err.Error()) - s.DisableRouting = true - } - } - itfName := s.InterfaceName // attempt to name the interface if runtime.GOOS == "darwin" { diff --git a/core/sys_utils.go b/core/sys_utils.go index 432ec684..d216c6c0 100644 --- a/core/sys_utils.go +++ b/core/sys_utils.go @@ -2,11 +2,19 @@ package core import ( "fmt" + "log/slog" "os/exec" + "strings" ) -func Exec(name string, arg ...string) error { +func ExecSplit(logger *slog.Logger, command string) error { + parts := strings.Split(command, " ") + return Exec(logger, parts[0], parts[1:]...) +} + +func Exec(logger *slog.Logger, name string, arg ...string) error { out, err := exec.Command(name, arg...).CombinedOutput() + logger.Debug("exec command", "cmd", name, "arg", arg, "out", string(out)) if err != nil { return fmt.Errorf("error executing command: %s %s. %w. Output: %s", name, arg, err, out) } diff --git a/core/sys_windows.go b/core/sys_windows.go index 09e1cabf..48287c05 100644 --- a/core/sys_windows.go +++ b/core/sys_windows.go @@ -1,7 +1,7 @@ package core import ( - "fmt" + "log/slog" "net" "net/netip" "strconv" @@ -13,10 +13,6 @@ import ( "github.com/kmahyyg/go-network-compo/wintypes" ) -func VerifyForwarding() error { - return fmt.Errorf("Not implemented for Windows") -} - func InitUAPI(e *state.Env, itfName string) (net.Listener, error) { uapi, err := ipc.UAPIListen(itfName) if err != nil && strings.Contains(err.Error(), "This security ID may not be assigned as the owner of this object") { @@ -29,18 +25,15 @@ func InitUAPI(e *state.Env, itfName string) (net.Listener, error) { return uapi, nil } -func InitInterface(ifName string) error { +func InitInterface(logger *slog.Logger, ifName string) error { return nil } -func ConfigureAlias(ifName string, prefix netip.Prefix) error { - addr := prefix.Addr() - _, mask, _ := net.ParseCIDR(prefix.String()) - maskStr := net.IP(mask.Mask).String() - return Exec("netsh", "interface", "ip", "add", "address", ifName, addr.String(), maskStr) +func ConfigureAlias(logger *slog.Logger, ifName string, addr netip.Addr) error { + return Exec(logger, "netsh", "interface", "ip", "add", "address", ifName, addr.String()) } -func ConfigureRoute(dev tun.Device, itfName string, route netip.Prefix) error { +func ConfigureRoute(logger *slog.Logger, dev tun.Device, itfName string, route netip.Prefix) error { addr := route.Addr() _, mask, _ := net.ParseCIDR(route.String()) maskStr := net.IP(mask.Mask).String() @@ -49,5 +42,5 @@ func ConfigureRoute(dev tun.Device, itfName string, route netip.Prefix) error { if err != nil { return err } - return Exec("route", "add", addr.String(), "mask", maskStr, "0.0.0.0", "IF", strconv.FormatUint(uint64(itf.InterfaceIndex), 10)) + return Exec(logger, "route", "add", addr.String(), "mask", maskStr, "0.0.0.0", "IF", strconv.FormatUint(uint64(itf.InterfaceIndex), 10)) } diff --git a/example/README.md b/example/README.md index 3846df5b..9c120ffc 100644 --- a/example/README.md +++ b/example/README.md @@ -1,7 +1,7 @@ # Nylon Setup #### Node Setup -On every node, copy the `sample-node.yaml`, and fill in the relevant details. run `./nylon key` to generate the keypair. The first key printed (stdout) is the private key, and the second key printed (stderr) is the public key. Fill the private key in the node config, and put the public key in the central config later. +On every node, copy the `sample-node.yaml`, and fill in the relevant details. run `nylon key` to generate the keypair. The first key printed (stdout) is the private key, and the second key printed (stderr) is the public key. Fill the private key in the node config, and put the public key in the central config later. #### Distribution (Optional) To setup key distribution, you can also generate a keypair. Save the private key into `central.key` (this will be used by `nylon seal`). You can put the public key into the central config under `dist/key`. @@ -17,8 +17,8 @@ You can modify the `sample-central.yaml` to define your network topology. Here a - `id`: A unique identifier for the node, this will also be used as a service id for the node. - `pubkey`: The public key of the node, used to identify the node in the network, and to encrypt traffic to the node. - `endpoints`: A list of endpoints that the node can be reached at (whether over LAN or internet). Each endpoint should be in the format of `host:port`. You can specify multiple endpoints for a node, and Nylon will automatically pick the best one to use. -- `address`: The primary IP address of the node in the Nylon network. -- `services`: An additional list of services that the node provides. This is used to define other addresses associated with the node, and to allow anycast routing. Multiple nodes can provide the same service, and Nylon will automatically route to the nearest one. Each service should have a unique `id`, and an `address`, which is declared at the end of the file. +- `addresses`: The addresses of the current nylon node, nylon will configure the interface to use these aliases. +- `prefixes`: An additional list of advertised prefixes on the current node. This can be used to define other networks which can connect to nylon. Multiple prefixes (or addresses) may be declared across different nylon nodes, which will be routed in an anycast manner > [!NOTE] > **Difference between routers and clients:** @@ -35,9 +35,9 @@ Notice nodes can have 0 or more accessible endpoints. Nylon will regularly try t ### Running the network -Before running Nylon, make sure to open UDP port `57175` (or whatever port you configured) so that Nylon can communicate. Without further to do, simply run `./nylon run` (you may need CAP_NET_ADMIN or sudo). +Before running Nylon, make sure to open UDP port `57175` (or whatever port you configured) so that Nylon can communicate. Without further to do, simply run `nylon run` (you may need CAP_NET_ADMIN or sudo). After a while (5-10 seconds), you will notice that the network has converged! > [!TIP] -> You can check the status of the node by running `./nylon i `. This will show you the current routing table, peers, and other useful information. \ No newline at end of file +> You can check the status of the node by running `nylon i `. This will show you the current routing table, peers, and other useful information. \ No newline at end of file diff --git a/example/sample-central.yaml b/example/sample-central.yaml index a0d259e1..72cc3598 100644 --- a/example/sample-central.yaml +++ b/example/sample-central.yaml @@ -8,40 +8,36 @@ dist: routers: - id: alice pubkey: xmfAovAKN4AY5ocK5s+/VsG9I27KrQ13Vzb0HOsLKAs== - address: 10.0.0.1 # this is the primary address of the node within the Nylon network - services: - - some-dns-server + addresses: [10.0.0.1] # this is the primary address of the nylon interface, it will be used when sending out packets from the interface + prefixes: + - 192.168.0.0/24 # you can advertise a prefix as well. make sure to exclude this on nodes where it could overlap! + - 10.1.0.0/24 - id: bob pubkey: 4GfHHSyVpXc+wkbjyIIONERa6Xf5EafB0nVGZLf2r2o= - address: 10.0.0.2 + addresses: [10.0.0.2, 10.1.0.2] # you can advertise multiple addresses endpoints: - '192.168.1.1:57175' - id: eve pubkey: 2mXTTD+FYdtJm/v1vSHz8qimvCucjW9vY+nLYacXJFE= - address: 10.0.0.3 - services: - - linux-machine # This node will provide the "linux-machine" service defined below + addresses: [10.0.0.3] - id: public pubkey: dJcUE1qnXCQ5x8pMhFb/MZab7YrBaaHcrgfbmQI0MW4= - address: 10.0.0.4 + addresses: [10.0.0.4] endpoints: - '123.123.123.123:57175' # nylon supports multiple endpoints, picking the best endpoint dynamically - '123.123.123.124:57175' - services: - - some-dns-server - id: charlie pubkey: WcCkKijU0brYnRzxk867HTDyYFf/cqiKTTOLSxtWoFc= - address: 10.0.0.5 + addresses: [10.0.0.5] clients: # external "vanilla" WireGuard clients must be defined separately - id: client1 pubkey: SBI+yvF30Ba4xo0GKTtKHSSfbXAnRNFTBwydJyJp6Rk= - address: 10.0.0.7 + addresses: [10.0.0.7] + prefixes: + - 192.168.1.0/24 # clients may also advertise prefixes, just make sure it's also added in the allowed ips in the WireGuard config graph: # The graph determines which nodes will attempt/can peer with each other. Nodes will only connect to each other once they have been connected directly in the graph. - Group1 = alice, bob # Groups are a convenient way to connect many nodes. Nodes within groups are not automatically connected - Group1, Group1 # You can emulate that behaviour by connecting a group to its self - Group2 = Group1, client1 # You can use groups within groups - client1, eve, alice # Here, client1, eve and alice will all connect to each other -services: # service can be mapped to multiple nodes, using anycast (based on lowest latency) - linux-machine: 10.0.1.1/32 # here "linux-machine" is unicast, as only eve provides it - some-dns-server: 6.6.6.6/32 -timestamp: 1740832962209309000 # The timestamp is updated by "./nylon seal" and is used as a version number when checking for config updates. \ No newline at end of file +timestamp: 1740832962209309000 # The timestamp is updated by "nylon seal" and is used as a version number when checking for config updates. \ No newline at end of file diff --git a/example/sample-node.yaml b/example/sample-node.yaml index 4ef57a8b..0da5028f 100644 --- a/example/sample-node.yaml +++ b/example/sample-node.yaml @@ -1,12 +1,23 @@ key: 0NcrMcaS1tORCTA88L5ZPE+H6yUMV4e5vRp6iypnPW8= id: alice port: 57175 # Default: 57175 - UDP port that Nylon listens on -nonetconfigure: false # Default: false - If enabled, Nylon will not configure the system network settings. -usesystemrouting: false # Default: false - if enabled, Nylon will use the system route table to forward packets -logpath: "" # Default: "" - If set, Nylon will log to this file -interfacename: "" # Default: "" - If set, Nylon will use this interface name instead of the default "nylon" or utunx on macOS -disablerouting: false # Default: false - If true, Nylon will not route traffic through this node -dnsresolvers: [] # Default: [] - If set (e.g ["1.1.1.1:53"]), nylon will use these DNS resolvers for its own queries + +# the following are optional +use_system_routing: false # Default: false - all packets from peers will come out of the TUN interface +no_net_configure: false # Default: false - do not configure system networking at all +log_path: "" # Default: "" - If set, Nylon will log to this file +interface_name: "" # Default: "" - If set, Nylon will use this interface name instead of the default "nylon" or utunx on macOS +dns_resolvers: [] # Default: [] - If set (e.g ["1.1.1.1:53"]), nylon will use these DNS resolvers for its own queries dist: # Optional: If set, Nylon will bootstrap central.yaml from this URL if it does not exist already url: https://static.example.com/network1.nybundle - key: 7PaN6DmAayz4KnDnsXSXJH+Oy0TFGeoM4FEbQfLriVY= \ No newline at end of file + key: 7PaN6DmAayz4KnDnsXSXJH+Oy0TFGeoM4FEbQfLriVY= +include_ips: [] # split tunnel, included ip ranges. If empty, will include all prefixes on the network +exclude_ips: # split tunnel, excluded ip ranges. If empty, nothing is excluded + - 192.168.0.1/24 # e.g here, we exclude the local ip range + +# for even more flexibility, you may execute commands when the interface starts or stops +pre_up: [] +pre_down: [] +post_up: + - iptables -t nat -A POSTROUTING -s 10.0.0.0/24 -d 192.168.0.0/24 -j MASQUERADE # can be used in conjunction with an advertised prefix +post_down: [] \ No newline at end of file diff --git a/go.mod b/go.mod index f79afac3..da3c0a80 100644 --- a/go.mod +++ b/go.mod @@ -25,15 +25,19 @@ require ( require ( filippo.io/edwards25519 v1.1.0 // indirect - github.com/davecgh/go-spew v1.1.1 // indirect - github.com/google/btree v1.1.2 // indirect + github.com/cilium/cilium v1.18.4 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/google/btree v1.1.3 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/kr/pretty v0.3.1 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/rogpeppe/go-internal v1.14.1 // indirect github.com/samber/lo v1.51.0 // indirect github.com/samber/slog-common v0.19.0 // indirect github.com/spf13/pflag v1.0.10 // indirect + github.com/vishvananda/netlink v1.3.1 // indirect + github.com/vishvananda/netns v0.0.5 // indirect + go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect golang.org/x/sync v0.18.0 // indirect golang.org/x/text v0.31.0 // indirect golang.org/x/time v0.12.0 // indirect diff --git a/go.sum b/go.sum index 7b955da8..be94f222 100644 --- a/go.sum +++ b/go.sum @@ -1,9 +1,13 @@ filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= +github.com/cilium/cilium v1.18.4 h1:/HrbeMmk46UDkJs4uJemIqxB7wuZ+QRMNVscNYua5C8= +github.com/cilium/cilium v1.18.4/go.mod h1:PPAuhDhMHOLaAiraQKDxEHUTmE68RrDlZj3988R+Lco= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +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/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= @@ -12,6 +16,8 @@ github.com/gaissmai/bart v0.25.0 h1:eqiokVPqM3F94vJ0bTHXHtH91S8zkKL+bKh+BsGOsJM= github.com/gaissmai/bart v0.25.0/go.mod h1:GREWQfTLRWz/c5FTOsIw+KkscuFkIV5t8Rp7Nd1Td5c= github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU= github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= +github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= +github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= 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/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= @@ -30,6 +36,8 @@ github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 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/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= @@ -47,16 +55,24 @@ github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 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/vishvananda/netlink v1.3.1 h1:3AEMt62VKqz90r0tmNhog0r/PpWKmrEShJU0wJW6bV0= +github.com/vishvananda/netlink v1.3.1/go.mod h1:ARtKouGSTGchR8aMwmkzC0qiNPrrWO5JS/XMVl45+b4= +github.com/vishvananda/netns v0.0.5 h1:DfiHV+j8bA32MFM7bfEunvT8IAqQ/NzSJHtcmW5zdEY= +github.com/vishvananda/netns v0.0.5/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM= 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.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q= golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4= golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= diff --git a/integration/harness.go b/integration/harness.go index 2dfa87c9..13ecec87 100644 --- a/integration/harness.go +++ b/integration/harness.go @@ -129,11 +129,9 @@ func (v *VirtualHarness) NewNode(id state.NodeId, virtPrefix string) { } ncfg := state.RouterCfg{ NodeCfg: state.NodeCfg{ - Id: id, - PubKey: privKey.Pubkey(), - Services: []state.ServiceId{ - v.Central.RegisterService(state.ServiceId(id), netip.MustParsePrefix(virtPrefix)), - }, + Id: id, + PubKey: privKey.Pubkey(), + Prefixes: []netip.Prefix{netip.MustParsePrefix(virtPrefix)}, }, } v.Central.Routers = append(v.Central.Routers, ncfg) @@ -257,8 +255,7 @@ func (i *InMemoryNetwork) virtualRouteTable(node state.NodeId, src, dst netip.Ad return true } } - for _, svc := range curCfg.Services { - prefix := i.cfg.Central.GetSvcPrefix(svc) + for _, prefix := range curCfg.Prefixes { if prefix.Contains(dst) { if i.SelfHandler.TryApply(node, src, dst, data) { return true @@ -273,8 +270,7 @@ func (i *InMemoryNetwork) virtualRouteTable(node state.NodeId, src, dst netip.Ad if node == n.Id { continue } - for _, p := range n.Services { - prefix := i.cfg.Central.GetSvcPrefix(p) + for _, prefix := range n.Prefixes { if prefix.Contains(dst) { // route to this node select { diff --git a/protocol/nylon.pb.go b/protocol/nylon.pb.go index 02253a58..31dd0f09 100644 --- a/protocol/nylon.pb.go +++ b/protocol/nylon.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.8 -// protoc v5.29.3 +// protoc-gen-go v1.36.6 +// protoc v3.21.12 // source: protocol/nylon.proto package protocol @@ -182,7 +182,7 @@ func (*Ny_AckRetractOp) isNy_Type() {} type Ny_Update struct { state protoimpl.MessageState `protogen:"open.v1"` RouterId string `protobuf:"bytes,1,opt,name=RouterId,proto3" json:"RouterId,omitempty"` - ServiceId string `protobuf:"bytes,2,opt,name=ServiceId,proto3" json:"ServiceId,omitempty"` + Prefix []byte `protobuf:"bytes,2,opt,name=Prefix,proto3" json:"Prefix,omitempty"` Seqno uint32 `protobuf:"varint,3,opt,name=Seqno,proto3" json:"Seqno,omitempty"` Metric uint32 `protobuf:"varint,4,opt,name=Metric,proto3" json:"Metric,omitempty"` unknownFields protoimpl.UnknownFields @@ -226,11 +226,11 @@ func (x *Ny_Update) GetRouterId() string { return "" } -func (x *Ny_Update) GetServiceId() string { +func (x *Ny_Update) GetPrefix() []byte { if x != nil { - return x.ServiceId + return x.Prefix } - return "" + return nil } func (x *Ny_Update) GetSeqno() uint32 { @@ -249,7 +249,7 @@ func (x *Ny_Update) GetMetric() uint32 { type Ny_AckRetract struct { state protoimpl.MessageState `protogen:"open.v1"` - ServiceId string `protobuf:"bytes,1,opt,name=ServiceId,proto3" json:"ServiceId,omitempty"` + Prefix []byte `protobuf:"bytes,1,opt,name=Prefix,proto3" json:"Prefix,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -284,17 +284,17 @@ func (*Ny_AckRetract) Descriptor() ([]byte, []int) { return file_protocol_nylon_proto_rawDescGZIP(), []int{1, 1} } -func (x *Ny_AckRetract) GetServiceId() string { +func (x *Ny_AckRetract) GetPrefix() []byte { if x != nil { - return x.ServiceId + return x.Prefix } - return "" + return nil } type Ny_SeqnoRequest struct { state protoimpl.MessageState `protogen:"open.v1"` RouterId string `protobuf:"bytes,1,opt,name=RouterId,proto3" json:"RouterId,omitempty"` - ServiceId string `protobuf:"bytes,2,opt,name=ServiceId,proto3" json:"ServiceId,omitempty"` + Prefix []byte `protobuf:"bytes,2,opt,name=Prefix,proto3" json:"Prefix,omitempty"` Seqno uint32 `protobuf:"varint,3,opt,name=Seqno,proto3" json:"Seqno,omitempty"` HopCount uint32 `protobuf:"varint,4,opt,name=HopCount,proto3" json:"HopCount,omitempty"` unknownFields protoimpl.UnknownFields @@ -338,11 +338,11 @@ func (x *Ny_SeqnoRequest) GetRouterId() string { return "" } -func (x *Ny_SeqnoRequest) GetServiceId() string { +func (x *Ny_SeqnoRequest) GetPrefix() []byte { if x != nil { - return x.ServiceId + return x.Prefix } - return "" + return nil } func (x *Ny_SeqnoRequest) GetSeqno() uint32 { @@ -417,23 +417,23 @@ const file_protocol_nylon_proto_rawDesc = "" + "\n" + "\x14protocol/nylon.proto\x12\x05proto\"6\n" + "\x0fTransportBundle\x12#\n" + - "\aPackets\x18\x01 \x03(\v2\t.proto.NyR\aPackets\"\xdb\x04\n" + + "\aPackets\x18\x01 \x03(\v2\t.proto.NyR\aPackets\"\xc9\x04\n" + "\x02Ny\x12,\n" + "\aRouteOp\x18\x01 \x01(\v2\x10.proto.Ny.UpdateH\x00R\aRouteOp\x12@\n" + "\x0eSeqnoRequestOp\x18\x02 \x01(\v2\x16.proto.Ny.SeqnoRequestH\x00R\x0eSeqnoRequestOp\x12+\n" + "\aProbeOp\x18\x03 \x01(\v2\x0f.proto.Ny.ProbeH\x00R\aProbeOp\x12:\n" + - "\fAckRetractOp\x18\x04 \x01(\v2\x14.proto.Ny.AckRetractH\x00R\fAckRetractOp\x1ap\n" + + "\fAckRetractOp\x18\x04 \x01(\v2\x14.proto.Ny.AckRetractH\x00R\fAckRetractOp\x1aj\n" + "\x06Update\x12\x1a\n" + - "\bRouterId\x18\x01 \x01(\tR\bRouterId\x12\x1c\n" + - "\tServiceId\x18\x02 \x01(\tR\tServiceId\x12\x14\n" + + "\bRouterId\x18\x01 \x01(\tR\bRouterId\x12\x16\n" + + "\x06Prefix\x18\x02 \x01(\fR\x06Prefix\x12\x14\n" + "\x05Seqno\x18\x03 \x01(\rR\x05Seqno\x12\x16\n" + - "\x06Metric\x18\x04 \x01(\rR\x06Metric\x1a*\n" + + "\x06Metric\x18\x04 \x01(\rR\x06Metric\x1a$\n" + "\n" + - "AckRetract\x12\x1c\n" + - "\tServiceId\x18\x01 \x01(\tR\tServiceId\x1az\n" + + "AckRetract\x12\x16\n" + + "\x06Prefix\x18\x01 \x01(\fR\x06Prefix\x1at\n" + "\fSeqnoRequest\x12\x1a\n" + - "\bRouterId\x18\x01 \x01(\tR\bRouterId\x12\x1c\n" + - "\tServiceId\x18\x02 \x01(\tR\tServiceId\x12\x14\n" + + "\bRouterId\x18\x01 \x01(\tR\bRouterId\x12\x16\n" + + "\x06Prefix\x18\x02 \x01(\fR\x06Prefix\x12\x14\n" + "\x05Seqno\x18\x03 \x01(\rR\x05Seqno\x12\x1a\n" + "\bHopCount\x18\x04 \x01(\rR\bHopCount\x1aZ\n" + "\x05Probe\x12\x14\n" + diff --git a/protocol/nylon.proto b/protocol/nylon.proto index 8d4c5795..f518c035 100644 --- a/protocol/nylon.proto +++ b/protocol/nylon.proto @@ -10,16 +10,16 @@ message TransportBundle { message Ny { message Update { string RouterId = 1; - string ServiceId = 2; + bytes Prefix = 2; uint32 Seqno = 3; uint32 Metric = 4; } message AckRetract { - string ServiceId = 1; + bytes Prefix = 1; } message SeqnoRequest { string RouterId = 1; - string ServiceId = 2; + bytes Prefix = 2; uint32 Seqno = 3; uint32 HopCount = 4; } diff --git a/smoke/fixtures/testcentral1.yaml b/smoke/fixtures/testcentral1.yaml index 8361ea39..7fe4d13a 100755 --- a/smoke/fixtures/testcentral1.yaml +++ b/smoke/fixtures/testcentral1.yaml @@ -6,13 +6,13 @@ dist: routers: - id: sample_node1 pubkey: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= - address: 10.0.0.1 + addresses: [10.0.0.1] endpoints: - 8.8.8.8:57175 clients: - id: external-client pubkey: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= - address: 10.2.0.1 + addresses: [10.2.0.1] graph: - Group1 = sample_node1, sample_node1 timestamp: 1740879895918834978 diff --git a/smoke/fixtures/testnode1.yaml b/smoke/fixtures/testnode1.yaml index 12e48bfb..ab6597fb 100644 --- a/smoke/fixtures/testnode1.yaml +++ b/smoke/fixtures/testnode1.yaml @@ -1,5 +1,5 @@ key: IG5bLY5ar8+IXeqgI4pVUYqqQFbyCVhF2qUA4f/54Uo= id: sample_node1 port: 57175 -nonetconfigure: false +no_net_configure: false diff --git a/state/config.go b/state/config.go index aa2ef662..2404e5ea 100644 --- a/state/config.go +++ b/state/config.go @@ -3,16 +3,19 @@ package state import ( "cmp" "fmt" + "net" "net/netip" "slices" "strings" + + "github.com/cilium/cilium/pkg/ip" ) type NodeCfg struct { - Id NodeId - PubKey NyPublicKey - Address netip.Addr - Services []ServiceId `yaml:",omitempty"` + Id NodeId + PubKey NyPublicKey + Addresses []netip.Addr `yaml:",omitempty"` + Prefixes []netip.Prefix `yaml:",omitempty"` } // RouterCfg represents a central representation of a node that can route @@ -40,15 +43,53 @@ type CentralCfg struct { Clients []ClientCfg Graph []string Timestamp int64 - Services map[ServiceId]netip.Prefix } -func (c *CentralCfg) RegisterService(svcId ServiceId, prefix netip.Prefix) ServiceId { - if c.Services == nil { - c.Services = make(map[ServiceId]netip.Prefix) +// LocalCfg represents local node-level configuration +type LocalCfg struct { + // Node Private Key + Key NyPrivateKey + Id NodeId // unique id for this node + Port uint16 // Address that the data plane can be accessed by + Dist *LocalDistributionCfg `yaml:",omitempty"` // distribution configuration + UseSystemRouting bool `yaml:"use_system_routing,omitempty"` // all packets from peers will come out of the TUN interface + NoNetConfigure bool `yaml:"no_net_configure,omitempty"` // do not configure system networking at all + DnsResolvers []string `yaml:"dns_resolvers,omitempty"` // dns resolvers used by nylon, currently only for config repo + InterfaceName string `yaml:"interface_name,omitempty"` // the name of the nylon interface + LogPath string `yaml:"log_path,omitempty"` // if not empty, nylon will write to this file + IncludeIPs []netip.Prefix `yaml:"include_ips,omitempty"` // split tunnel, included ip ranges. If empty, will include all prefixes on the network + ExcludeIPs []netip.Prefix `yaml:"exclude_ips,omitempty"` // split tunnel, excluded ip ranges. If empty, nothing is excluded + PreUp []string `yaml:"pre_up,omitempty"` // a list of commands executed in order before the nylon interface is brought up + PreDown []string `yaml:"pre_down,omitempty"` // a list of commands executed in order before the nylon interface is brought down + PostUp []string `yaml:"post_up,omitempty"` // a list of commands executed in order after the nylon interface is brought up + PostDown []string `yaml:"post_down,omitempty"` // a list of commands executed in order after the nylon interface is brought down +} + +// GetPrefixes returns all unique prefixes from all nodes +func (c *CentralCfg) GetPrefixes() []netip.Prefix { + prefixMap := make(map[netip.Prefix]bool) + + // Collect from routers + for _, router := range c.Routers { + for _, prefix := range router.Prefixes { + prefixMap[prefix] = true + } + } + + // Collect from clients + for _, client := range c.Clients { + for _, prefix := range client.Prefixes { + prefixMap[prefix] = true + } + } + + // Convert to slice + prefixes := make([]netip.Prefix, 0, len(prefixMap)) + for prefix := range prefixMap { + prefixes = append(prefixes, prefix) } - c.Services[svcId] = prefix - return svcId + + return prefixes } func (c *CentralCfg) GetNodes() []NodeCfg { @@ -62,23 +103,6 @@ func (c *CentralCfg) GetNodes() []NodeCfg { return nodes } -// TODO: Allow node to be configured to NOT be a router -// LocalCfg represents local node-level configuration -type LocalCfg struct { - // Node Private Key - Key NyPrivateKey - Id NodeId - // Address that the data plane can be accessed by - Port uint16 - Dist *LocalDistributionCfg `yaml:",omitempty"` - DisableRouting bool - UseSystemRouting bool - NoNetConfigure bool `yaml:",omitempty"` - DnsResolvers []string `yaml:",omitempty"` - InterfaceName string - LogPath string -} - func parseSymbolList(s string, validSymbols []string) ([]string, error) { spl := strings.Split(strings.TrimSpace(s), ",") line := make([]string, 0) @@ -99,6 +123,36 @@ func parseSymbolList(s string, validSymbols []string) ([]string, error) { return line, nil } +func ComputeSplitTunnel(includesPrefix, excludesPrefix []netip.Prefix) []netip.Prefix { + // Convert netip.Prefix to *net.IPNet + toIPNets := func(prefixes []netip.Prefix) []*net.IPNet { + nets := make([]*net.IPNet, 0, len(prefixes)) + for _, p := range prefixes { + if p.IsValid() { + nets = append(nets, &net.IPNet{ + IP: p.Addr().AsSlice(), + Mask: net.CIDRMask(p.Bits(), p.Addr().BitLen()), + }) + } + } + return nets + } + + // Call the legacy functions + result := ip.RemoveCIDRs(toIPNets(includesPrefix), toIPNets(excludesPrefix)) + ipv4, ipv6 := ip.CoalesceCIDRs(result) + + // Convert back to netip.Prefix + output := make([]netip.Prefix, 0, len(ipv4)+len(ipv6)) + for _, n := range append(ipv4, ipv6...) { + if addr, ok := netip.AddrFromSlice(n.IP); ok { + ones, _ := n.Mask.Size() + output = append(output, netip.PrefixFrom(addr.Unmap(), ones)) + } + } + return output +} + /* ParseGraph Graph syntax is something like this: @@ -325,13 +379,17 @@ func (e *CentralCfg) FindNodeBy(pkey NyPublicKey) *NodeId { } func ExpandCentralConfig(cfg *CentralCfg) { - // compatibility & convenience: add a default service for a node + // compatibility & convenience: advertise address as a host address (/32 or /128) for idx, node := range cfg.Routers { - node.Services = append([]ServiceId{cfg.RegisterService(ServiceId(node.Id), AddrToPrefix(node.Address))}, node.Services...) + for _, addr := range node.Addresses { + node.Prefixes = append([]netip.Prefix{AddrToPrefix(addr)}, node.Prefixes...) + } cfg.Routers[idx] = node } for idx, node := range cfg.Clients { - node.Services = append([]ServiceId{cfg.RegisterService(ServiceId(node.Id), AddrToPrefix(node.Address))}, node.Services...) + for _, addr := range node.Addresses { + node.Prefixes = append([]netip.Prefix{AddrToPrefix(addr)}, node.Prefixes...) + } cfg.Clients[idx] = node } } @@ -389,10 +447,6 @@ func (e *CentralCfg) GetRouter(node NodeId) RouterCfg { return e.Routers[idx] } -func (e *CentralCfg) GetSvcPrefix(svc ServiceId) netip.Prefix { - return e.Services[svc] -} - func (e *CentralCfg) GetClient(node NodeId) ClientCfg { idx := slices.IndexFunc(e.Clients, func(cfg ClientCfg) bool { return cfg.Id == node diff --git a/state/distribution_test.go b/state/distribution_test.go index 6651e2bb..89435fe6 100644 --- a/state/distribution_test.go +++ b/state/distribution_test.go @@ -21,8 +21,9 @@ func TestBundleUnbundle(t *testing.T) { Routers: make([]RouterCfg, 0), Clients: []ClientCfg{ {NodeCfg{ - Id: "blah", - PubKey: NyPublicKey{}, + Id: "blah", + PubKey: NyPublicKey{}, + Prefixes: []netip.Prefix{netip.MustParsePrefix("10.0.0.1/32")}, }}, }, Graph: []string{ @@ -32,9 +33,6 @@ func TestBundleUnbundle(t *testing.T) { "a, b", }, Timestamp: 0, - Services: map[ServiceId]netip.Prefix{ - "test": netip.MustParsePrefix("10.0.0.1/32"), - }, } txt, err := yaml.Marshal(cfg) assert.NoError(t, err) @@ -56,6 +54,11 @@ func TestBundleTamper(t *testing.T) { {NodeCfg{ Id: "blah", PubKey: NyPublicKey{}, + Prefixes: []netip.Prefix{ + netip.MustParsePrefix("10.0.0.1/32"), + netip.MustParsePrefix("10.0.0.2/32"), + netip.MustParsePrefix("10.0.0.3/8"), + }, }}, }, Graph: []string{ @@ -65,11 +68,6 @@ func TestBundleTamper(t *testing.T) { "a, b", }, Timestamp: 0, - Services: map[ServiceId]netip.Prefix{ - "test": netip.MustParsePrefix("10.0.0.1/32"), - "test1": netip.MustParsePrefix("10.0.0.2/32"), - "test2": netip.MustParsePrefix("10.0.0.3/8"), - }, } txt, err := yaml.Marshal(cfg) assert.NoError(t, err) @@ -107,9 +105,6 @@ func TestBundleInvalidSign(t *testing.T) { "a, b", }, Timestamp: 0, - Services: map[ServiceId]netip.Prefix{ - "test2": netip.MustParsePrefix("10.0.0.3/8"), - }, } cfg.Timestamp = time.Now().UnixNano() diff --git a/state/routing.go b/state/routing.go index 75d760d7..58e74d77 100644 --- a/state/routing.go +++ b/state/routing.go @@ -2,6 +2,7 @@ package state import ( "fmt" + "net/netip" "slices" "strings" "time" @@ -9,17 +10,14 @@ import ( type NodeId string -// ServiceId maps to a real network prefix -type ServiceId string - -// Source is a pair of a router-id and a prefix (Babel Section 2.7). In this case, we use a service identifier +// Source is a pair of a router-id and a prefix (Babel Section 2.7). type Source struct { NodeId - ServiceId + netip.Prefix } func (s Source) String() string { - return fmt.Sprintf("(router: %s, svc: %s)", s.NodeId, s.ServiceId) + return fmt.Sprintf("(router: %s, prefix: %s)", s.NodeId, s.Prefix) } type Advertisement struct { @@ -29,32 +27,30 @@ type Advertisement struct { } type RouterState struct { Id NodeId - SelfSeqno map[ServiceId]uint16 - Routes map[ServiceId]SelRoute + SelfSeqno map[netip.Prefix]uint16 + Routes map[netip.Prefix]SelRoute Sources map[Source]FD Neighbours []*Neighbour - // Advertised is a map tracking the service id and the time it will be advertised until - Advertised map[ServiceId]Advertisement - // DisableRouting indicates that this node should not route traffic for other nodes - DisableRouting bool + // Advertised is a map tracking the prefix and the time it will be advertised until + Advertised map[netip.Prefix]Advertisement } -func (s *RouterState) GetSeqno(id ServiceId) uint16 { - seq, ok := s.SelfSeqno[id] +func (s *RouterState) GetSeqno(prefix netip.Prefix) uint16 { + seq, ok := s.SelfSeqno[prefix] if !ok { return 0 } return seq } -func (s *RouterState) SetSeqno(id ServiceId, seqno uint16) { - s.SelfSeqno[id] = seqno +func (s *RouterState) SetSeqno(prefix netip.Prefix, seqno uint16) { + s.SelfSeqno[prefix] = seqno } func (s *RouterState) StringRoutes() string { buf := make([]string, 0) - for svc, route := range s.Routes { - buf = append(buf, fmt.Sprintf("%s via %s", svc, route)) + for prefix, route := range s.Routes { + buf = append(buf, fmt.Sprintf("%s via %s", prefix, route)) } slices.Sort(buf) return strings.Join(buf, "\n") @@ -81,7 +77,7 @@ type PubRoute struct { } func (r PubRoute) String() string { - return fmt.Sprintf("(router: %s, svc: %s, seqno: %d, metric: %d)", r.NodeId, r.ServiceId, r.Seqno, r.Metric) + return fmt.Sprintf("(router: %s, prefix: %s, seqno: %d, metric: %d)", r.NodeId, r.Prefix, r.Seqno, r.Metric) } type NeighRoute struct { @@ -97,7 +93,7 @@ type SelRoute struct { } func (r SelRoute) String() string { - return fmt.Sprintf("(nh: %s, router: %s, svc: %s, seqno: %d, metric: %d)", r.Nh, r.NodeId, r.ServiceId, r.Seqno, r.Metric) + return fmt.Sprintf("(nh: %s, router: %s, prefix: %s, seqno: %d, metric: %d)", r.Nh, r.NodeId, r.Prefix, r.Seqno, r.Metric) } func (s *RouterState) GetNeighbour(node NodeId) *Neighbour { diff --git a/state/utils_test.go b/state/utils_test.go index c65e4107..a3b46a0b 100644 --- a/state/utils_test.go +++ b/state/utils_test.go @@ -21,7 +21,6 @@ func SampleNetwork(t *testing.T, numClients, numRouters int, fullyConnected bool "file:example.conf", }, }, - Services: make(map[ServiceId]netip.Prefix), } clients := make([]string, numClients) @@ -33,7 +32,7 @@ func SampleNetwork(t *testing.T, numClients, numRouters int, fullyConnected bool cfg.Clients = append(cfg.Clients, ClientCfg{NodeCfg{ Id: NodeId(client), PubKey: keyStore[client].Pubkey(), - Services: []ServiceId{cfg.RegisterService(ServiceId(client), netip.MustParsePrefix(fmt.Sprintf("10.1.0.%d/32", idx)))}, + Prefixes: []netip.Prefix{netip.MustParsePrefix(fmt.Sprintf("10.1.0.%d/32", idx))}, }}) } @@ -47,7 +46,7 @@ func SampleNetwork(t *testing.T, numClients, numRouters int, fullyConnected bool NodeCfg: NodeCfg{ Id: NodeId(router), PubKey: keyStore[router].Pubkey(), - Services: []ServiceId{cfg.RegisterService(ServiceId(router), netip.MustParsePrefix(fmt.Sprintf("10.1.0.%d/32", idx)))}, + Prefixes: []netip.Prefix{netip.MustParsePrefix(fmt.Sprintf("10.1.0.%d/32", idx))}, }, Endpoints: []netip.AddrPort{ netip.MustParseAddrPort(fmt.Sprintf("192.168.0.%d:25565", idx)), @@ -56,9 +55,6 @@ func SampleNetwork(t *testing.T, numClients, numRouters int, fullyConnected bool } cfg.Timestamp = time.Now().UnixNano() - cfg.Services = map[ServiceId]netip.Prefix{ - "service-a": netip.MustParsePrefix("10.0.0.5/24"), - } cfg.Graph = append(cfg.Graph, fmt.Sprintf("clients = %s", strings.Join(clients, ","))) cfg.Graph = append(cfg.Graph, fmt.Sprintf("routers = %s", strings.Join(routers, ","))) if fullyConnected { diff --git a/state/validation.go b/state/validation.go index f113446b..c6a6c5a5 100644 --- a/state/validation.go +++ b/state/validation.go @@ -106,36 +106,23 @@ func CentralConfigValidator(cfg *CentralCfg) error { return err } - prefixes := make([]netip.Prefix, 0) - - //ensure prefixes of services do not overlap - for svc, prefix := range cfg.Services { - if slices.Contains(nodes, string(svc)) { - return fmt.Errorf("service id %s conflicts with a node id", svc) - } - if slices.Contains(prefixes, prefix) { - return fmt.Errorf("service %s's prefix %s is identical to an existing prefix", svc, prefix.String()) - } - prefixes = append(prefixes, prefix) - } - - // ensure the current node contains unique services + // ensure each node contains unique prefixes (anycast routing allows duplicate prefixes across nodes) for _, router := range cfg.Routers { - svc := make(map[ServiceId]struct{}) - for _, s := range router.Services { - if _, ok := svc[s]; ok { - return fmt.Errorf("router %s has duplicate service %s", router.Id, s) + routerPrefixes := make(map[netip.Prefix]struct{}) + for _, p := range router.Prefixes { + if _, ok := routerPrefixes[p]; ok { + return fmt.Errorf("router %s has duplicate prefix %s", router.Id, p) } - svc[s] = struct{}{} + routerPrefixes[p] = struct{}{} } - for _, p := range cfg.GetPeers(router.Id) { - if cfg.IsClient(p) { - client := cfg.GetClient(p) - for _, cs := range client.Services { - if _, ok := svc[cs]; ok { - return fmt.Errorf("router %s has duplicate service %s (provided by client %s)", router.Id, cs, client.Id) + for _, peer := range cfg.GetPeers(router.Id) { + if cfg.IsClient(peer) { + client := cfg.GetClient(peer) + for _, cp := range client.Prefixes { + if _, ok := routerPrefixes[cp]; ok { + return fmt.Errorf("router %s has duplicate prefix %s (provided by client %s)", router.Id, cp, client.Id) } - svc[cs] = struct{}{} + routerPrefixes[cp] = struct{}{} } } } diff --git a/state/validation_test.go b/state/validation_test.go index 70462dbc..8704df88 100644 --- a/state/validation_test.go +++ b/state/validation_test.go @@ -55,24 +55,67 @@ func TestNodeConfigValidator_DnsResolver(t *testing.T) { })) } -func TestCentralConfigValidator_OverlappingService(t *testing.T) { +func TestCentralConfigValidator_OverlappingPrefix(t *testing.T) { cfg := &CentralCfg{ - Services: map[ServiceId]netip.Prefix{ - "svc1": netip.MustParsePrefix("10.5.0.1/32"), - "svc2": netip.MustParsePrefix("10.5.0.0/24"), - "svc3": netip.MustParsePrefix("10.5.0.1/8"), + Routers: []RouterCfg{ + { + NodeCfg: NodeCfg{ + Id: "node1", + PubKey: NyPublicKey{}, + Prefixes: []netip.Prefix{ + netip.MustParsePrefix("10.5.0.1/32"), + netip.MustParsePrefix("10.5.0.0/24"), + netip.MustParsePrefix("10.5.0.1/8"), + }, + }, + }, }, } assert.NoError(t, CentralConfigValidator(cfg)) } -func TestCentralConfigValidator_DuplicateService(t *testing.T) { +func TestCentralConfigValidator_DuplicatePrefix(t *testing.T) { cfg := &CentralCfg{ - Services: map[ServiceId]netip.Prefix{ - "svc1": netip.MustParsePrefix("10.5.0.1/32"), - "svc2": netip.MustParsePrefix("10.5.0.1/24"), - "svc3": netip.MustParsePrefix("10.5.0.1/32"), + Routers: []RouterCfg{ + { + NodeCfg: NodeCfg{ + Id: "node1", + PubKey: NyPublicKey{}, + Prefixes: []netip.Prefix{ + netip.MustParsePrefix("10.5.0.1/32"), + netip.MustParsePrefix("10.5.0.1/24"), + netip.MustParsePrefix("10.5.0.1/32"), // duplicate within same node + }, + }, + }, }, } assert.Error(t, CentralConfigValidator(cfg)) } + +func TestCentralConfigValidator_AnycastPrefix(t *testing.T) { + // Anycast routing allows the same prefix to be advertised by multiple nodes + cfg := &CentralCfg{ + Routers: []RouterCfg{ + { + NodeCfg: NodeCfg{ + Id: "node1", + PubKey: NyPublicKey{}, + Prefixes: []netip.Prefix{ + netip.MustParsePrefix("10.5.0.1/32"), + }, + }, + }, + { + NodeCfg: NodeCfg{ + Id: "node2", + PubKey: NyPublicKey{}, + Prefixes: []netip.Prefix{ + netip.MustParsePrefix("10.5.0.1/32"), // same prefix as node1 - this is valid for anycast + }, + }, + }, + }, + } + assert.NoError(t, CentralConfigValidator(cfg)) +}