Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 17 additions & 1 deletion internal/runtime/gobgp/paths.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
gobgpserver "github.com/osrg/gobgp/v4/pkg/server"

"go.datum.net/galactic/internal/model"
"go.datum.net/galactic/internal/plumbing/ebpf/uformat"
)

// deriveRD builds an RFC 4364 Type 1 route distinguisher from the router ID
Expand Down Expand Up @@ -62,10 +63,25 @@ func gatewayForPrefix(prefix netip.Prefix) netip.Addr {
// the sole carrier for the destination SID in this design — see
// gatewayForPrefix for why the EVPN route's own Gateway IP field can't be
// used for this instead.
//
// The Information Sub-TLV also carries an RFC 9252 §3.2.1 SID Structure
// Sub-Sub-TLV describing the uSID layout (uFMT 48+16, see
// internal/plumbing/ebpf/uformat) already present in sid, with no
// transposition (TL/TO = 0). The field widths are derived from uformat's own
// constants rather than re-hardcoded, so the wire encoding and the eBPF
// datapath's bit layout can't silently drift apart.
func prefixSIDAttr(sid netip.Addr) bgp.PathAttributeInterface {
structure := bgp.NewSRv6SIDStructureSubSubTLV(
uformat.BlockBits, // LBL = 48, uSID Block
uformat.NodeIDBits, // LNL = 16, Node-ID
uformat.FunctionBits, // FL = 4, Function
uformat.ArgumentBits, // AL = 12, Instance ID (Argument)
0, // TL = 0, no transposition
0, // TO = 0, n/a when TL=0
)
return bgp.NewPathAttributePrefixSID(
bgp.NewSRv6ServiceTLV(bgp.TLVTypeSRv6L3Service,
bgp.NewSRv6InformationSubTLV(sid, bgp.END_DT46),
bgp.NewSRv6InformationSubTLV(sid, bgp.END_DT46, structure),
),
)
}
Expand Down
77 changes: 77 additions & 0 deletions internal/runtime/gobgp/paths_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
bgp "github.com/osrg/gobgp/v4/pkg/packet/bgp"

"go.datum.net/galactic/internal/model"
"go.datum.net/galactic/internal/plumbing/ebpf/uformat"
)

const (
Expand Down Expand Up @@ -351,6 +352,82 @@ func TestBuildEVPNPathsMixedFamily(t *testing.T) {
}
}

// TestPrefixSIDAttrCarriesSIDStructure verifies that prefixSIDAttr's
// PathAttributePrefixSID, once serialized to wire bytes and decoded back via
// GoBGP's own DecodeFromBytes, carries an RFC 9252 §3.2.1 SRv6 SID Structure
// Sub-Sub-TLV describing the uFMT 48+16 layout with no transposition. Before
// this fix, NewSRv6InformationSubTLV was called with no sub-sub-TLV values at
// all, so the attribute Galactic put on the wire never contained a Structure
// Sub-Sub-TLV — a spec-compliant third-party EVPN/SRv6 receiver would be
// missing the structure description entirely.
func TestPrefixSIDAttrCarriesSIDStructure(t *testing.T) {
sid := netip.MustParseAddr(testSID1)

attr := prefixSIDAttr(sid)

serialized, err := attr.Serialize()
if err != nil {
t.Fatalf("Serialize() error = %v", err)
}

// PathAttribute.Serialize() includes the attribute header (flags, type
// code, and length); DecodeFromBytes expects that same header back, so
// round-trip through a fresh attribute rather than re-using attr.
got := &bgp.PathAttributePrefixSID{}
if err := got.DecodeFromBytes(serialized); err != nil {
t.Fatalf("DecodeFromBytes() error = %v", err)
}

structure := extractSIDStructure(t, got)

if structure.LocatorBlockLength != uformat.BlockBits {
t.Errorf("LocatorBlockLength = %d, want %d", structure.LocatorBlockLength, uformat.BlockBits)
}
if structure.LocatorNodeLength != uformat.NodeIDBits {
t.Errorf("LocatorNodeLength = %d, want %d", structure.LocatorNodeLength, uformat.NodeIDBits)
}
if structure.FunctionLength != uformat.FunctionBits {
t.Errorf("FunctionLength = %d, want %d", structure.FunctionLength, uformat.FunctionBits)
}
if structure.ArgumentLength != uformat.ArgumentBits {
t.Errorf("ArgumentLength = %d, want %d", structure.ArgumentLength, uformat.ArgumentBits)
}
if structure.TranspositionLength != 0 {
t.Errorf("TranspositionLength = %d, want 0", structure.TranspositionLength)
}
if structure.TranspositionOffset != 0 {
t.Errorf("TranspositionOffset = %d, want 0", structure.TranspositionOffset)
}
}

// extractSIDStructure digs the SRv6 SID Structure Sub-Sub-TLV out of a
// decoded Prefix-SID attribute's L3 Service TLV / Information Sub-TLV, or
// fails the test if any layer is missing.
func extractSIDStructure(t *testing.T, attr *bgp.PathAttributePrefixSID) *bgp.SRv6SIDStructureSubSubTLV {
t.Helper()

for _, tlv := range attr.TLVs {
svc, ok := tlv.(*bgp.SRv6ServiceTLV)
if !ok || svc.Type != bgp.TLVTypeSRv6L3Service {
continue
}
for _, sub := range svc.SubTLVs {
info, ok := sub.(*bgp.SRv6InformationSubTLV)
if !ok {
continue
}
for _, subSub := range info.SubSubTLVs {
if structure, ok := subSub.(*bgp.SRv6SIDStructureSubSubTLV); ok {
return structure
}
}
}
}

t.Fatal("no SRv6SIDStructureSubSubTLV found in decoded Prefix-SID attribute")
return nil
}

// TestBuildEVPNPathsMatchesApplyVRFRD verifies that the RD derived by
// buildEVPNPaths matches the one used by applyVRF for the same VRFID.
// This ensures VRF registration and EVPN path advertisement are consistent.
Expand Down