refactor: update AS entry handling in API and database to support ASN-only entries. Remove prefix field from AS entry structure and adjust related functions and tests. Enhance OpenAPI specifications and documentation to reflect changes in AS entry representation.

This commit is contained in:
Denozordec
2026-04-05 22:38:17 +07:00
parent 73d0003281
commit 1db6b5b7b2
19 changed files with 218 additions and 115 deletions
+43 -10
View File
@@ -7,9 +7,27 @@ import (
"strings"
)
const maxBGPASN = 4294967295
func filterUniqueASNs(pathASNs []int64) []int64 {
seen := make(map[int64]struct{})
for _, a := range pathASNs {
if a >= 1 && a <= maxBGPASN {
seen[a] = struct{}{}
}
}
out := make([]int64, 0, len(seen))
for a := range seen {
out = append(out, a)
}
sort.Slice(out, func(i, j int) bool { return out[i] < out[j] })
return out
}
// RenderExportFilterIPv4 renders a BIRD 2 filter that accepts IPv4 routes whose prefix
// is in prefixes (exact CIDR match via set membership), and rejects others.
func RenderExportFilterIPv4(filterName string, prefixes []netip.Prefix) (string, error) {
// is in prefixes (exact CIDR match via set membership), and/or routes whose AS_PATH
// contains any of pathASNs (BIRD pattern [= * ASN =]), then rejects others.
func RenderExportFilterIPv4(filterName string, prefixes []netip.Prefix, pathASNs []int64) (string, error) {
if strings.TrimSpace(filterName) == "" {
return "", fmt.Errorf("birdfmt: filter name is required")
}
@@ -27,24 +45,32 @@ func RenderExportFilterIPv4(filterName string, prefixes []netip.Prefix) (string,
}
sort.Strings(keys)
asns := filterUniqueASNs(pathASNs)
var b strings.Builder
b.WriteString("filter ")
b.WriteString(strings.TrimSpace(filterName))
b.WriteString(" {\n")
if len(keys) == 0 {
b.WriteString(" reject;\n")
} else {
if len(keys) > 0 {
b.WriteString(" if net ~ [ ")
b.WriteString(strings.Join(keys, ", "))
b.WriteString(" ] then accept;\n")
}
for _, asn := range asns {
fmt.Fprintf(&b, " if bgp_path ~ [= * %d =] then accept;\n", asn)
}
if len(keys) == 0 && len(asns) == 0 {
b.WriteString(" reject;\n")
} else {
b.WriteString(" reject;\n")
}
b.WriteString("}\n")
return b.String(), nil
}
// RenderExportFilterIPv6 renders a BIRD 2 filter for IPv6 prefixes (CIDR set, then reject).
func RenderExportFilterIPv6(filterName string, prefixes []netip.Prefix) (string, error) {
// RenderExportFilterIPv6 renders a BIRD 2 filter for IPv6 prefixes (CIDR set) and/or
// AS_PATH matches, then reject.
func RenderExportFilterIPv6(filterName string, prefixes []netip.Prefix, pathASNs []int64) (string, error) {
if strings.TrimSpace(filterName) == "" {
return "", fmt.Errorf("birdfmt: filter name is required")
}
@@ -62,16 +88,23 @@ func RenderExportFilterIPv6(filterName string, prefixes []netip.Prefix) (string,
}
sort.Strings(keys)
asns := filterUniqueASNs(pathASNs)
var b strings.Builder
b.WriteString("filter ")
b.WriteString(strings.TrimSpace(filterName))
b.WriteString(" {\n")
if len(keys) == 0 {
b.WriteString(" reject;\n")
} else {
if len(keys) > 0 {
b.WriteString(" if net ~ [ ")
b.WriteString(strings.Join(keys, ", "))
b.WriteString(" ] then accept;\n")
}
for _, asn := range asns {
fmt.Fprintf(&b, " if bgp_path ~ [= * %d =] then accept;\n", asn)
}
if len(keys) == 0 && len(asns) == 0 {
b.WriteString(" reject;\n")
} else {
b.WriteString(" reject;\n")
}
b.WriteString("}\n")
+13 -3
View File
@@ -7,7 +7,7 @@ import (
)
func TestRenderExportFilterIPv4_Empty(t *testing.T) {
got, err := RenderExportFilterIPv4("evobgp_x", nil)
got, err := RenderExportFilterIPv4("evobgp_x", nil, nil)
if err != nil {
t.Fatal(err)
}
@@ -21,7 +21,7 @@ func TestRenderExportFilterIPv4_Empty(t *testing.T) {
func TestRenderExportFilterIPv4_SkipsNonV4(t *testing.T) {
v6 := netip.MustParsePrefix("2001:db8::/32")
got, err := RenderExportFilterIPv4("f", []netip.Prefix{v6})
got, err := RenderExportFilterIPv4("f", []netip.Prefix{v6}, nil)
if err != nil {
t.Fatal(err)
}
@@ -32,7 +32,7 @@ func TestRenderExportFilterIPv4_SkipsNonV4(t *testing.T) {
func TestRenderExportFilterIPv6(t *testing.T) {
p := netip.MustParsePrefix("2001:db8::/32")
got, err := RenderExportFilterIPv6("evobgp_export_v6", []netip.Prefix{p, p})
got, err := RenderExportFilterIPv6("evobgp_export_v6", []netip.Prefix{p, p}, nil)
if err != nil {
t.Fatal(err)
}
@@ -45,3 +45,13 @@ func TestRenderExportFilterIPv6(t *testing.T) {
t.Fatalf("mismatch\n--- got ---\n%s\n--- want ---\n%s", got, want)
}
}
func TestRenderExportFilterIPv4_ASPathOnly(t *testing.T) {
got, err := RenderExportFilterIPv4("f", nil, []int64{65001, 65002})
if err != nil {
t.Fatal(err)
}
if !strings.Contains(got, "bgp_path ~ [= * 65001 =]") || !strings.Contains(got, "bgp_path ~ [= * 65002 =]") {
t.Fatal(got)
}
}
+2 -2
View File
@@ -11,13 +11,13 @@ import (
func TestStandardLayout_GeneratorMatchesFixtures(t *testing.T) {
p4 := netip.MustParsePrefix("203.0.113.0/24")
f4, err := RenderExportFilterIPv4("evobgp_export_v4", []netip.Prefix{p4})
f4, err := RenderExportFilterIPv4("evobgp_export_v4", []netip.Prefix{p4}, nil)
if err != nil {
t.Fatal(err)
}
assertFileEquals(t, "testdata/scenarios/standard_layout/bird.d/evobgp_filters_v4.conf", f4)
f6, err := RenderExportFilterIPv6("evobgp_export_v6", nil)
f6, err := RenderExportFilterIPv6("evobgp_export_v6", nil, nil)
if err != nil {
t.Fatal(err)
}