refactor: remove strict bind configuration from BGP templates and update related logic. Eliminate 'strict bind on' from both IPv4 and IPv6 templates to simplify BGP peer rendering. Adjust OpenAPI documentation and internal handling of BGP source addresses to reflect these changes, enhancing clarity and compatibility.
CI / changes (push) Successful in 7s
CI / openapi (push) Successful in 23s
CI / go (push) Successful in 24s
CI / docker-web (deploy/docker/evobgp-web/Dockerfile, , evobgp-web) (push) Successful in 1m7s
CI / docker-web (deploy/docker/evobgp-web/Dockerfile, evobgp-all, evobgp-web-all) (push) Successful in 1m2s
CI / docker-bird (push) Has been skipped
CI / bird2 (push) Successful in 55s
CI / docker-go (deploy/docker/evobgp-agent/Dockerfile, , evobgp-agent) (push) Successful in 54s
CI / docker-go (evobgp-all, 1, deploy/docker/gobinary/Dockerfile, , evobgp-all) (push) Successful in 2m19s
CI / docker-go (evobgp-api, 1, deploy/docker/gobinary/Dockerfile, , evobgp-api) (push) Successful in 2m27s
CI / docker-go (evobgp-deploy, 0, deploy/docker/gobinary/Dockerfile, , evobgp-deploy) (push) Successful in 1m22s
CI / docker-go (evobgp-ingest, 0, deploy/docker/gobinary/Dockerfile, , evobgp-ingest) (push) Successful in 1m26s
CI / docker-go (evobgp-node, 0, deploy/docker/gobinary/Dockerfile, , evobgp-node) (push) Successful in 1m9s
CI / docker-go (evobgp-render, 0, deploy/docker/gobinary/Dockerfile, , evobgp-render) (push) Successful in 1m19s
CI / docker-go (evobgp-scheduler, 0, deploy/docker/gobinary/Dockerfile, , evobgp-scheduler) (push) Successful in 1m19s

This commit is contained in:
Denozordec
2026-04-06 14:03:25 +07:00
parent dc5e20e9b2
commit 482eeb122d
8 changed files with 29 additions and 72 deletions
+4 -5
View File
@@ -704,8 +704,8 @@ components:
KV настройки tenant/глобальные лимиты и feature flags. KV настройки tenant/глобальные лимиты и feature flags.
Параметры BIRD (строки в `global_settings`, JSON-значения — обычно строка или число): Параметры BIRD (строки в `global_settings`, JSON-значения — обычно строка или число):
`bird_router_id`, `bird_local_ipv4`, `bird_local_ipv6`, `bird_local_asn`; `bird_router_id`, `bird_local_ipv4`, `bird_local_ipv6`, `bird_local_asn`;
`bird_bgp_source_ipv4` / `bird_bgp_source_ipv6` — для `source address` у BGP-пиров (если не заданы — эффективный `bird_local_ipv4` / политика пира). если задан `bird_bgp_source_ipv4`, он подставляется как BIRD `router id` (перекрывает `bird_router_id`).
Если задан `bird_bgp_source_ipv4`, он же подставляется как BIRD `router id` (перекрывает `bird_router_id`). Ключи `bird_bgp_source_ipv4` / `bird_bgp_source_ipv6` в сгенерированном BGP для пиров не используются (оставлены для совместимости API).
Шаблон BGP в конфиге: `local as <bird_local_asn>;` без локального IP. Шаблон BGP в конфиге: `local as <bird_local_asn>;` без локального IP.
properties: properties:
bird_router_id: bird_router_id:
@@ -720,11 +720,10 @@ components:
bird_bgp_source_ipv4: bird_bgp_source_ipv4:
type: string type: string
description: | description: |
Исходящий IPv4 для `source address` у пиров; также задаёт `router id`, если указан. Если задан — используется как BIRD `router id` (IPv4). В блоках `protocol bgp … from bgp_template` строка `source address` не генерируется.
На стороне MikroTik в BGP Connection поле **Remote address** должно совпадать с этим адресом (или с тем, с которого реально устанавливается TCP-сессия к 179/tcp), иначе сессия не поднимется.
bird_bgp_source_ipv6: bird_bgp_source_ipv6:
type: string type: string
description: Исходящий IPv6 для `source address` у пиров IPv6. description: Зарезервировано; в текущей генерации BGP не используется.
additionalProperties: true additionalProperties: true
RevisionDiff: RevisionDiff:
+4 -13
View File
@@ -12,7 +12,7 @@ const (
) )
// BGPTemplatesOptions holds ASN and export filters for template bgp bgp_template (+ v6 mirror). // BGPTemplatesOptions holds ASN and export filters for template bgp bgp_template (+ v6 mirror).
// Templates use "local as <asn>;" only (no local IP); peers supply source address / optional local override. // Templates use "local as <asn>;" only (no local IP); peers add neighbor / multihop / passive and optional "local … as …" override.
type BGPTemplatesOptions struct { type BGPTemplatesOptions struct {
LocalASN uint32 LocalASN uint32
ExportFilterV4 string ExportFilterV4 string
@@ -43,8 +43,6 @@ func RenderBGPTemplates(opts BGPTemplatesOptions) (string, error) {
b.WriteString(" };\n") b.WriteString(" };\n")
b.WriteString(" hold time 90;\n") b.WriteString(" hold time 90;\n")
b.WriteString(" keepalive time 30;\n") b.WriteString(" keepalive time 30;\n")
// Жёсткая привязка к source address (см. пир): полезно в Docker / bridge, иначе listen может уйти на 0.0.0.0.
b.WriteString(" strict bind on;\n")
b.WriteString("}\n\n") b.WriteString("}\n\n")
fmt.Fprintf(&b, "template bgp %s {\n", BGPTemplateNameV6) fmt.Fprintf(&b, "template bgp %s {\n", BGPTemplateNameV6)
fmt.Fprintf(&b, " local as %d;\n", opts.LocalASN) fmt.Fprintf(&b, " local as %d;\n", opts.LocalASN)
@@ -56,7 +54,6 @@ func RenderBGPTemplates(opts BGPTemplatesOptions) (string, error) {
b.WriteString(" };\n") b.WriteString(" };\n")
b.WriteString(" hold time 90;\n") b.WriteString(" hold time 90;\n")
b.WriteString(" keepalive time 30;\n") b.WriteString(" keepalive time 30;\n")
b.WriteString(" strict bind on;\n")
b.WriteString("}\n") b.WriteString("}\n")
return b.String(), nil return b.String(), nil
} }
@@ -67,14 +64,12 @@ type BGPPeerFromTemplateOptions struct {
TemplateName string TemplateName string
NeighborIP string NeighborIP string
NeighborASN uint32 NeighborASN uint32
SourceAddress string
// If set, emits "local … as …" before neighbor (overrides template local/ASN for this peer). // If set, emits "local … as …" before neighbor (overrides template local/ASN for this peer).
OverrideLocalIP string OverrideLocalIP string
OverrideLocalASN uint32 OverrideLocalASN uint32
} }
// RenderProtocolBGPFromTemplate renders protocol bgp … from TEMPLATE { neighbor; multihop; source address; strict bind; passive; }. // RenderProtocolBGPFromTemplate renders protocol bgp … from TEMPLATE { neighbor; multihop; passive; }.
// strict bind дублируется и в шаблоне, и в каждом пире — явная фиксация на сессию.
func RenderProtocolBGPFromTemplate(opts BGPPeerFromTemplateOptions) (string, error) { func RenderProtocolBGPFromTemplate(opts BGPPeerFromTemplateOptions) (string, error) {
if strings.TrimSpace(opts.ProtocolName) == "" { if strings.TrimSpace(opts.ProtocolName) == "" {
return "", fmt.Errorf("birdfmt: protocol name is required") return "", fmt.Errorf("birdfmt: protocol name is required")
@@ -82,8 +77,8 @@ func RenderProtocolBGPFromTemplate(opts BGPPeerFromTemplateOptions) (string, err
if strings.TrimSpace(opts.TemplateName) == "" { if strings.TrimSpace(opts.TemplateName) == "" {
return "", fmt.Errorf("birdfmt: template name is required") return "", fmt.Errorf("birdfmt: template name is required")
} }
if strings.TrimSpace(opts.NeighborIP) == "" || strings.TrimSpace(opts.SourceAddress) == "" { if strings.TrimSpace(opts.NeighborIP) == "" {
return "", fmt.Errorf("birdfmt: neighbor and source address are required") return "", fmt.Errorf("birdfmt: neighbor is required")
} }
if opts.NeighborASN == 0 { if opts.NeighborASN == 0 {
return "", fmt.Errorf("birdfmt: neighbor ASN must be non-zero") return "", fmt.Errorf("birdfmt: neighbor ASN must be non-zero")
@@ -107,10 +102,6 @@ func RenderProtocolBGPFromTemplate(opts BGPPeerFromTemplateOptions) (string, err
b.WriteString(strings.TrimSpace(opts.NeighborIP)) b.WriteString(strings.TrimSpace(opts.NeighborIP))
fmt.Fprintf(&b, " as %d;\n", opts.NeighborASN) fmt.Fprintf(&b, " as %d;\n", opts.NeighborASN)
b.WriteString(" multihop;\n") b.WriteString(" multihop;\n")
b.WriteString(" source address ")
b.WriteString(strings.TrimSpace(opts.SourceAddress))
b.WriteString(";\n")
b.WriteString(" strict bind on;\n")
b.WriteString(" passive;\n") b.WriteString(" passive;\n")
b.WriteString("}\n") b.WriteString("}\n")
return b.String(), nil return b.String(), nil
+5 -10
View File
@@ -37,11 +37,10 @@ func TestRenderBGPTemplates_Validation(t *testing.T) {
func TestRenderProtocolBGPFromTemplate_MultihopPassive(t *testing.T) { func TestRenderProtocolBGPFromTemplate_MultihopPassive(t *testing.T) {
got, err := RenderProtocolBGPFromTemplate(BGPPeerFromTemplateOptions{ got, err := RenderProtocolBGPFromTemplate(BGPPeerFromTemplateOptions{
ProtocolName: "evobgp_p_x", ProtocolName: "evobgp_p_x",
TemplateName: BGPTemplateNameV4, TemplateName: BGPTemplateNameV4,
NeighborIP: "94.142.140.141", NeighborIP: "94.142.140.141",
NeighborASN: 65002, NeighborASN: 65002,
SourceAddress: "77.232.38.173",
}) })
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@@ -52,10 +51,7 @@ func TestRenderProtocolBGPFromTemplate_MultihopPassive(t *testing.T) {
if !strings.Contains(got, "multihop;") || !strings.Contains(got, "passive;") { if !strings.Contains(got, "multihop;") || !strings.Contains(got, "passive;") {
t.Fatal(got) t.Fatal(got)
} }
if !strings.Contains(got, "source address 77.232.38.173;") { if strings.Contains(got, "source address") || strings.Contains(got, "strict bind") {
t.Fatal(got)
}
if !strings.Contains(got, "strict bind on;") {
t.Fatal(got) t.Fatal(got)
} }
} }
@@ -66,7 +62,6 @@ func TestRenderProtocolBGPFromTemplate_OverrideLocal(t *testing.T) {
TemplateName: BGPTemplateNameV4, TemplateName: BGPTemplateNameV4,
NeighborIP: "192.0.2.2", NeighborIP: "192.0.2.2",
NeighborASN: 2, NeighborASN: 2,
SourceAddress: "10.0.0.1",
OverrideLocalIP: "10.0.0.1", OverrideLocalIP: "10.0.0.1",
OverrideLocalASN: 65099, OverrideLocalASN: 65099,
}) })
+4 -5
View File
@@ -40,11 +40,10 @@ func TestStandardLayout_GeneratorMatchesFixtures(t *testing.T) {
assertFileEquals(t, "testdata/scenarios/standard_layout/bird.d/evobgp_bgp_template.conf", tpl) assertFileEquals(t, "testdata/scenarios/standard_layout/bird.d/evobgp_bgp_template.conf", tpl)
peer, err := RenderProtocolBGPFromTemplate(BGPPeerFromTemplateOptions{ peer, err := RenderProtocolBGPFromTemplate(BGPPeerFromTemplateOptions{
ProtocolName: "evobgp_peer_ci", ProtocolName: "evobgp_peer_ci",
TemplateName: BGPTemplateNameV4, TemplateName: BGPTemplateNameV4,
NeighborIP: "192.0.2.2", NeighborIP: "192.0.2.2",
NeighborASN: 65002, NeighborASN: 65002,
SourceAddress: "192.0.2.1",
}) })
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@@ -6,7 +6,6 @@ template bgp bgp_template {
}; };
hold time 90; hold time 90;
keepalive time 30; keepalive time 30;
strict bind on;
} }
template bgp bgp_template_v6 { template bgp bgp_template_v6 {
@@ -17,5 +16,4 @@ template bgp bgp_template_v6 {
}; };
hold time 90; hold time 90;
keepalive time 30; keepalive time 30;
strict bind on;
} }
@@ -1,7 +1,5 @@
protocol bgp evobgp_peer_ci from bgp_template { protocol bgp evobgp_peer_ci from bgp_template {
neighbor 192.0.2.2 as 65002; neighbor 192.0.2.2 as 65002;
multihop; multihop;
source address 192.0.2.1;
strict bind on;
passive; passive;
} }
+9 -32
View File
@@ -331,9 +331,6 @@ type birdLocals struct {
localV4 string localV4 string
localV6 string localV6 string
localASN uint32 localASN uint32
// Optional BGP TCP source (BIRD "source address"); empty => use effective local per peer.
sourceV4 string
sourceV6 string
} }
func birdLocalsFromStore(st store.Backend, tenantID string) birdLocals { func birdLocalsFromStore(st store.Backend, tenantID string) birdLocals {
@@ -361,30 +358,12 @@ func birdLocalsFromStore(st store.Backend, tenantID string) birdLocals {
loc.localASN = n loc.localASN = n
} }
if s := stringFromSettingsMap(settings, "bird_bgp_source_ipv4"); s != "" { if s := stringFromSettingsMap(settings, "bird_bgp_source_ipv4"); s != "" {
loc.sourceV4 = s // BIRD router id must be an IPv4 address; historically aligned with optional BGP source setting.
// BIRD router id must be an IPv4 address; align with BGP source when operator sets it.
loc.routerID = strings.TrimSpace(s) loc.routerID = strings.TrimSpace(s)
} }
if s := stringFromSettingsMap(settings, "bird_bgp_source_ipv6"); s != "" {
loc.sourceV6 = s
}
return loc return loc
} }
func bgpPeerSourceIPv4(loc birdLocals, effectiveLocal string) string {
if s := strings.TrimSpace(loc.sourceV4); s != "" {
return s
}
return effectiveLocal
}
func bgpPeerSourceIPv6(loc birdLocals, effectiveLocal string) string {
if s := strings.TrimSpace(loc.sourceV6); s != "" {
return s
}
return effectiveLocal
}
func stringFromSettingsMap(m map[string]any, key string) string { func stringFromSettingsMap(m map[string]any, key string) string {
v, ok := m[key] v, ok := m[key]
if !ok || v == nil { if !ok || v == nil {
@@ -474,11 +453,10 @@ func renderPeersBirdFragment(st store.Backend, tenantID string, loc birdLocals)
ra := uint32(p.RemoteASN) ra := uint32(p.RemoteASN)
if addr.Is4() { if addr.Is4() {
opts := birdfmt.BGPPeerFromTemplateOptions{ opts := birdfmt.BGPPeerFromTemplateOptions{
ProtocolName: proto, ProtocolName: proto,
TemplateName: birdfmt.BGPTemplateNameV4, TemplateName: birdfmt.BGPTemplateNameV4,
NeighborIP: addr.String(), NeighborIP: addr.String(),
NeighborASN: ra, NeighborASN: ra,
SourceAddress: bgpPeerSourceIPv4(loc, lv4),
} }
if peerNeedsLocalOverride(loc, lv4, asn, true) { if peerNeedsLocalOverride(loc, lv4, asn, true) {
opts.OverrideLocalIP = lv4 opts.OverrideLocalIP = lv4
@@ -493,11 +471,10 @@ func renderPeersBirdFragment(st store.Backend, tenantID string, loc birdLocals)
} }
if addr.Is6() { if addr.Is6() {
opts := birdfmt.BGPPeerFromTemplateOptions{ opts := birdfmt.BGPPeerFromTemplateOptions{
ProtocolName: proto, ProtocolName: proto,
TemplateName: birdfmt.BGPTemplateNameV6, TemplateName: birdfmt.BGPTemplateNameV6,
NeighborIP: addr.String(), NeighborIP: addr.String(),
NeighborASN: ra, NeighborASN: ra,
SourceAddress: bgpPeerSourceIPv6(loc, lv6),
} }
if peerNeedsLocalOverride(loc, lv6, asn, false) { if peerNeedsLocalOverride(loc, lv6, asn, false) {
opts.OverrideLocalIP = lv6 opts.OverrideLocalIP = lv6
+3 -3
View File
@@ -98,8 +98,8 @@
<code class="text-xs">GET/PATCH /v1/settings</code> — глобальные параметры control plane (хранятся в БД). <code class="text-xs">GET/PATCH /v1/settings</code> — глобальные параметры control plane (хранятся в БД).
Требуется роль operator. Для BIRD, например: Требуется роль operator. Для BIRD, например:
<code class="bg-muted rounded px-1 py-0.5 text-xs">bird_local_ipv4</code>, <code class="bg-muted rounded px-1 py-0.5 text-xs">bird_local_ipv4</code>,
<code class="bg-muted rounded px-1 py-0.5 text-xs">bird_bgp_source_ipv4</code> (опционально — только для строки <code class="bg-muted rounded px-1 py-0.5 text-xs">bird_bgp_source_ipv4</code> (опционально — задаёт BIRD
<code class="text-xs">source address</code> у BGP-пиров). <code class="text-xs">router id</code>).
</CardDescription> </CardDescription>
</CardHeader> </CardHeader>
<CardContent class="space-y-4"> <CardContent class="space-y-4">
@@ -111,7 +111,7 @@
<p class="text-muted-foreground text-xs leading-relaxed"> <p class="text-muted-foreground text-xs leading-relaxed">
Должен быть строго валидный JSON: ключи и строки в двойных кавычках, без точки с запятой. Пример: Должен быть строго валидный JSON: ключи и строки в двойных кавычках, без точки с запятой. Пример:
<code class="bg-muted mt-1 block rounded px-2 py-1 font-mono" <code class="bg-muted mt-1 block rounded px-2 py-1 font-mono"
>{`{"bird_bgp_source_ipv4": "178.250.186.111"}`}</code >{`{"bird_router_id": "203.0.113.1", "bird_local_asn": 65001}`}</code
> >
</p> </p>
<Textarea <Textarea