diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index bad4cfea..0650eabb 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,7 +1,7 @@ name: test on: push: - branches: ["main"] + branches: ["main", "dv2"] pull_request: jobs: @@ -20,7 +20,8 @@ jobs: - name: Build run: | make - make examples + mkdir -p .bin + CGO_ENABLED=0 go build -o .bin/alo-latest std/examples/svs/alo-latest/main.go - name: Test run: make test diff --git a/dv/config/config.go b/dv/config/config.go index 0de66d95..04575edc 100644 --- a/dv/config/config.go +++ b/dv/config/config.go @@ -115,7 +115,7 @@ func DefaultConfig() *Config { PrefixInsertionKeychainUri: "", TrustSchemaPath: "", PrefixInsertionTrustSchemaPath: "", - PrefixStateReplicate: true, + PrefixStateReplicate: true, } } diff --git a/dv/dv/router.go b/dv/dv/router.go index 7599e0a0..67977db5 100644 --- a/dv/dv/router.go +++ b/dv/dv/router.go @@ -50,6 +50,8 @@ type Router struct { // prefix state daemon pfx *PrefixModule + // installed PSD sync/data PET egresses keyed by router TLV. + psdSyncInstalled map[string]enc.Name // neighbor table neighbors *table.NeighborTable // routing information base @@ -129,6 +131,8 @@ func NewRouter(config *config.Config, engine ndn.Engine) (*Router, error) { client: object.NewClient(engine, store, trust), nfdc: nfdc.NewNfdMgmtThread(engine), mutex: sync.Mutex{}, + + psdSyncInstalled: make(map[string]enc.Name), } // Initialize advertisement module @@ -290,19 +294,13 @@ func (dv *Router) register() (err error) { Name: prefix, }) } - // // Allow outgoing local-prefix-sync Interests to use two-phase forwarding. - // // Incoming Interests still terminate locally on the same prefix. - // dv.execMgmtRetry("pet", "add-egress", &mgmt.ControlArgs{ - // Name: dv.pfx.SyncPrefix(), - // Egress: &mgmt.EgressRecord{Name: neighborsPrefix.Clone()}, - // Multicast: true, - // }) // Set Advertisement Sync to localhop neighbors dv.execMgmtRetry("pet", "add-egress", &mgmt.ControlArgs{ Name: dv.config.AdvertisementSyncPrefix(), Egress: &mgmt.EgressRecord{Name: neighborsPrefix.Clone()}, }) - // Set broadcast strategy for Advertisement Sync prefix + + // Keep advertisement sync on the current working setup. dv.execMgmtRetry("strategy-choice", "set", &mgmt.ControlArgs{ Name: dv.config.AdvertisementSyncPrefix(), Strategy: &mgmt.Strategy{Name: defn.BROADCAST_STRATEGY}, @@ -323,33 +321,50 @@ func (dv *Router) execMgmtRetry(module, cmd string, args *mgmt.ControlArgs) { } } -// updatePsdSyncPrefix updates the PSD sync prefix PET entry with all routers as egress for BIER delivery. +// updatePsdPrefix incrementally updates PSD sync/data PET egresses. func (dv *Router) updatePsdPrefix() { synPfx := dv.pfx.SyncPrefix() grpPfx := dv.pfx.GroupPrefix() - // First, remove existing egress entries for this prefix + + dv.mutex.Lock() + desired := make(map[string]enc.Name) for _, router := range dv.rib.Entries() { + name := router.Name().Clone() + desired[name.TlvStr()] = name + } + installed := make(map[string]enc.Name, len(dv.psdSyncInstalled)) + for key, name := range dv.psdSyncInstalled { + installed[key] = name.Clone() + } + dv.psdSyncInstalled = desired + dv.mutex.Unlock() + + for key, router := range installed { + if _, ok := desired[key]; ok { + continue + } dv.execMgmtRetry("pet", "remove-egress", &mgmt.ControlArgs{ Name: synPfx, - Egress: &mgmt.EgressRecord{Name: router.Name().Clone()}, + Egress: &mgmt.EgressRecord{Name: router.Clone()}, }) - // Protocol naming convention dv.execMgmtRetry("pet", "remove-egress", &mgmt.ControlArgs{ - Name: grpPfx.Clone().Append(router.Name().Clone()...), - Egress: &mgmt.EgressRecord{Name: router.Name().Clone()}, + Name: grpPfx.Clone().Append(router.Clone()...), + Egress: &mgmt.EgressRecord{Name: router.Clone()}, }) } - // Then add all routers as egress - for _, router := range dv.rib.Entries() { + + for key, router := range desired { + if _, ok := installed[key]; ok { + continue + } dv.execMgmtRetry("pet", "add-egress", &mgmt.ControlArgs{ Name: synPfx, - Egress: &mgmt.EgressRecord{Name: router.Name().Clone()}, + Egress: &mgmt.EgressRecord{Name: router.Clone()}, Multicast: true, }) - // Protocol naming convention dv.execMgmtRetry("pet", "add-egress", &mgmt.ControlArgs{ - Name: grpPfx.Clone().Append(router.Name().Clone()...), - Egress: &mgmt.EgressRecord{Name: router.Name().Clone()}, + Name: grpPfx.Clone().Append(router.Clone()...), + Egress: &mgmt.EgressRecord{Name: router.Clone()}, }) } } diff --git a/dv/dv/table_algo.go b/dv/dv/table_algo.go index ced06a5f..8f7a3059 100644 --- a/dv/dv/table_algo.go +++ b/dv/dv/table_algo.go @@ -14,7 +14,6 @@ import ( func (dv *Router) postUpdateRib() { dv.updateFib() dv.advert.generate() - // Update PSD sync prefix with all routers as egress for BIER delivery. dv.updatePsdPrefix() } diff --git a/e2e/client_lvs_minindn.tlv b/e2e/client_lvs_minindn.tlv index 4f0e0e2f..a948b439 100644 Binary files a/e2e/client_lvs_minindn.tlv and b/e2e/client_lvs_minindn.tlv differ diff --git a/fw/bier/bier.go b/fw/bier/bier.go index 05d3316e..d2667d22 100644 --- a/fw/bier/bier.go +++ b/fw/bier/bier.go @@ -109,22 +109,44 @@ func BierClearBit(bs []byte, pos int) { bs[byteIdx] &^= (1 << bitIdx) } -// BierAnd returns bitwise AND of two bitstrings. Result length = min(len(a), len(b)). +// BierAnd returns bitwise AND of two bitstrings. Result length = max(len(a), len(b)). func BierAnd(a, b []byte) []byte { + maxLen := len(a) + if len(b) > maxLen { + maxLen = len(b) + } + result := make([]byte, maxLen) minLen := len(a) if len(b) < minLen { minLen = len(b) } - result := make([]byte, minLen) for i := 0; i < minLen; i++ { result[i] = a[i] & b[i] } return result } -// BierAndNot returns a &^ b (a AND NOT b). Clears bits in a that are set in b. +// BierOr returns bitwise OR of two bitstrings. Result length = max(len(a), len(b)). +func BierOr(a, b []byte) []byte { + maxLen := len(a) + if len(b) > maxLen { + maxLen = len(b) + } + result := make([]byte, maxLen) + copy(result, a) + for i := 0; i < len(b); i++ { + result[i] |= b[i] + } + return result +} + +// BierAndNot returns a &^ b (a AND NOT b). Result length = max(len(a), len(b)). func BierAndNot(a, b []byte) []byte { - result := make([]byte, len(a)) + maxLen := len(a) + if len(b) > maxLen { + maxLen = len(b) + } + result := make([]byte, maxLen) copy(result, a) minLen := len(a) if len(b) < minLen { @@ -368,7 +390,9 @@ func (b *BiftState) GetNeighborEntries() []BiftNeighborEntry { if entry.NextHop == 0 || entry.Fbm == nil { continue } - if _, ok := faceMap[entry.NextHop]; !ok { + if fbm, ok := faceMap[entry.NextHop]; ok { + faceMap[entry.NextHop] = BierOr(fbm, entry.Fbm) + } else { faceMap[entry.NextHop] = BierClone(entry.Fbm) } } @@ -377,5 +401,8 @@ func (b *BiftState) GetNeighborEntries() []BiftNeighborEntry { for faceID, fbm := range faceMap { neighbors = append(neighbors, BiftNeighborEntry{FaceID: faceID, Fbm: fbm}) } + sort.Slice(neighbors, func(i, j int) bool { + return neighbors[i].FaceID < neighbors[j].FaceID + }) return neighbors } diff --git a/fw/bier/bier_integration_test.go b/fw/bier/bier_integration_test.go index 3b9674ee..487374fd 100644 --- a/fw/bier/bier_integration_test.go +++ b/fw/bier/bier_integration_test.go @@ -86,10 +86,10 @@ func TestBierBitManipulationEdgeCases(t *testing.T) { a := []byte{0xFF, 0xFF, 0xFF} // 3 bytes b := []byte{0x0F, 0xF0} // 2 bytes — shorter res := bier.BierAnd(a, b) - if len(res) != 2 { - t.Errorf("result length should be min(3,2)=2, got %d", len(res)) + if len(res) != 3 { + t.Errorf("result length should be max(3,2)=3, got %d", len(res)) } - if res[0] != 0x0F || res[1] != 0xF0 { + if res[0] != 0x0F || res[1] != 0xF0 || res[2] != 0x00 { t.Errorf("unexpected result %v", res) } }) @@ -230,6 +230,32 @@ func TestBiftEdgeCases(t *testing.T) { } }) + t.Run("GetNeighborEntries aggregates all bits for same face", func(t *testing.T) { + b := &bier.BiftState{} + r0 := enc.Name{enc.NewGenericComponent("r0")} + r1 := enc.Name{enc.NewGenericComponent("r1")} + r9 := enc.Name{enc.NewGenericComponent("r9")} + + b.RegisterRouter(r0, 0) + b.RegisterRouter(r1, 1) + b.RegisterRouter(r9, 9) + + b.UpdateNextHop(0, 77) + b.UpdateNextHop(1, 77) + b.UpdateNextHop(9, 77) + b.RebuildFbm() + + neighbors := b.GetNeighborEntries() + if len(neighbors) != 1 { + t.Fatalf("expected 1 neighbor entry, got %d", len(neighbors)) + } + + fbm := neighbors[0].Fbm + if !bier.BierGetBit(fbm, 0) || !bier.BierGetBit(fbm, 1) || !bier.BierGetBit(fbm, 9) { + t.Fatalf("expected aggregated F-BM to contain bits 0, 1, and 9; got %08b %08b", fbm[0], fbm[1]) + } + }) + t.Run("RebuildFbm on empty BIFT does not panic", func(t *testing.T) { b := &bier.BiftState{} b.RebuildFbm() // must not panic diff --git a/fw/fw/thread.go b/fw/fw/thread.go index 719d81f7..ea77d2f9 100644 --- a/fw/fw/thread.go +++ b/fw/fw/thread.go @@ -20,6 +20,7 @@ import ( "github.com/named-data/ndnd/fw/dispatch" "github.com/named-data/ndnd/fw/table" enc "github.com/named-data/ndnd/std/encoding" + "github.com/named-data/ndnd/std/types/optional" "github.com/named-data/ndnd/std/utils" ) @@ -58,7 +59,7 @@ type pipelineContext struct { Pipeline forwardPipeline PetLocalHops []*table.PetNextHop PetEntry table.PetEntry - PetFound bool + PetFound optional.Optional[bool] LookupName enc.Name LocalFacesOnly bool } @@ -79,7 +80,7 @@ type petLocalHopsContext struct { LookupName enc.Name PitEntry table.PitEntry PetEntry table.PetEntry - PetFound bool + PetFound optional.Optional[bool] } // forwardInContext holds arguments for tryContentStoreHit @@ -417,10 +418,29 @@ func (t *Thread) validateNonce(interest *defn.FwInterest, packet *defn.Pkt) bool return true } -func (t *Thread) determinePipeline(packet *defn.Pkt, lookupName enc.Name) (forwardPipeline, table.PetEntry, bool) { +func petLookupState(found optional.Optional[bool]) string { + if !found.IsSet() { + return "none" + } + if found.Unwrap() { + return "true" + } + return "false" +} + +func (t *Thread) ensurePetLookup(petEntry *table.PetEntry, petFound *optional.Optional[bool], lookupName enc.Name) { + if petFound.IsSet() { + return + } + entry, found := table.Pet.FindLongestPrefixEnc(lookupName) + *petEntry = entry + *petFound = optional.Some(found) +} + +func (t *Thread) determinePipeline(packet *defn.Pkt, lookupName enc.Name) (forwardPipeline, table.PetEntry, optional.Optional[bool]) { var pipeline forwardPipeline var petEntry table.PetEntry - var petFound bool + petFound := optional.None[bool]() routerName, routerNameSet := CfgRouterName() @@ -438,8 +458,9 @@ func (t *Thread) determinePipeline(packet *defn.Pkt, lookupName enc.Name) (forwa pipeline = fwUnicastTransit } } else { - petEntry, petFound = table.Pet.FindLongestPrefixEnc(lookupName) - if petFound && petEntry.Multicast { + petEntry, found := table.Pet.FindLongestPrefixEnc(lookupName) + petFound = optional.Some(found) + if found && petEntry.Multicast { pipeline = fwMulticastIngress } else { pipeline = fwUnicastIngress @@ -451,7 +472,7 @@ func (t *Thread) determinePipeline(packet *defn.Pkt, lookupName enc.Name) (forwa "name", packet.Name, "lookup", lookupName, "pipeline", pipeline, - "petFound", petFound, + "petFound", petLookupState(petFound), "isLocalHop", isLocalHop, "egressRouter", len(packet.EgressRouter) > 0, "bier", len(packet.Bier) > 0, @@ -516,11 +537,8 @@ func (t *Thread) collectPetLocalHops(ctx petLocalHopsContext) []*table.PetNextHo return nil } - if !ctx.PetFound { - ctx.PetEntry, ctx.PetFound = table.Pet.FindLongestPrefixEnc(ctx.LookupName) - } - - if !ctx.PetFound { + t.ensurePetLookup(&ctx.PetEntry, &ctx.PetFound, ctx.LookupName) + if !ctx.PetFound.GetOr(false) { return nil } @@ -543,7 +561,7 @@ func (t *Thread) handleUnicastPipeline(ctx pipelineContext) { core.Log.Trace(t, "Unicast pipeline", "name", ctx.Pkt.Name, "lookup", ctx.LookupName, - "petFound", ctx.PetFound, + "petFound", petLookupState(ctx.PetFound), "localHop", isLocalHop, "localFacesOnly", ctx.LocalFacesOnly, ) @@ -586,7 +604,7 @@ func (t *Thread) handleUnicastPipeline(ctx pipelineContext) { } } -func (t *Thread) collectNetworkNextHops(packet *defn.Pkt, petEntry table.PetEntry, petFound bool) []StrategyCandidateHop { +func (t *Thread) collectNetworkNextHops(packet *defn.Pkt, petEntry table.PetEntry, petFound optional.Optional[bool]) []StrategyCandidateHop { var nextNet []StrategyCandidateHop if len(packet.EgressRouter) > 0 { @@ -596,7 +614,7 @@ func (t *Thread) collectNetworkNextHops(packet *defn.Pkt, petEntry table.PetEntr EgressRouter: packet.EgressRouter, }) } - } else if petFound { + } else if petFound.GetOr(false) { for _, er := range petEntry.EgressRouters { for _, nextHop := range table.FibStrategyTable.FindNextHopsEnc(er) { nextNet = append(nextNet, StrategyCandidateHop{ @@ -639,7 +657,7 @@ func (t *Thread) handleMulticastPipeline(ctx pipelineContext) { core.Log.Trace(t, "Multicast pipeline", "name", ctx.Pkt.Name, "lookup", ctx.LookupName, - "petFound", ctx.PetFound, + "petFound", petLookupState(ctx.PetFound), "localHop", isLocalHop, "localFacesOnly", ctx.LocalFacesOnly, "bier", len(ctx.Pkt.Bier), @@ -664,7 +682,8 @@ func (t *Thread) handleMulticastPipeline(ctx pipelineContext) { } // BIER forwarding for multicast - if !ctx.PetFound || len(ctx.PetEntry.EgressRouters) == 0 { + t.ensurePetLookup(&ctx.PetEntry, &ctx.PetFound, ctx.LookupName) + if !ctx.PetFound.GetOr(false) || len(ctx.PetEntry.EgressRouters) == 0 { return } diff --git a/fw/mgmt/pet.go b/fw/mgmt/pet.go index ed5ea6ed..8da29dc0 100644 --- a/fw/mgmt/pet.go +++ b/fw/mgmt/pet.go @@ -206,7 +206,7 @@ func (p *PETModule) list(interest *Interest) { Name: entry.Name, EgressRecords: make([]*mgmt.EgressRecord, 0, len(entry.EgressRouters)), NextHopRecords: make([]*mgmt.NextHopRecord, 0, len(entry.NextHops)), - Multicast: entry.Multicast, + Multicast: entry.Multicast, } for _, egress := range entry.EgressRouters {