Files
EvoBGP/internal/birdfmt/bgp.go
T
Denozordec dc5e20e9b2
CI / changes (push) Successful in 6s
CI / openapi (push) Has been skipped
CI / go (push) Successful in 24s
CI / docker-web (deploy/docker/evobgp-web/Dockerfile, , evobgp-web) (push) Has been skipped
CI / docker-web (deploy/docker/evobgp-web/Dockerfile, evobgp-all, evobgp-web-all) (push) Has been skipped
CI / docker-bird (push) Has been skipped
CI / bird2 (push) Successful in 15s
CI / docker-go (deploy/docker/evobgp-agent/Dockerfile, , evobgp-agent) (push) Successful in 55s
CI / docker-go (evobgp-api, 1, deploy/docker/gobinary/Dockerfile, , evobgp-api) (push) Has been cancelled
CI / docker-go (evobgp-deploy, 0, deploy/docker/gobinary/Dockerfile, , evobgp-deploy) (push) Has been cancelled
CI / docker-go (evobgp-ingest, 0, deploy/docker/gobinary/Dockerfile, , evobgp-ingest) (push) Has been cancelled
CI / docker-go (evobgp-node, 0, deploy/docker/gobinary/Dockerfile, , evobgp-node) (push) Has been cancelled
CI / docker-go (evobgp-render, 0, deploy/docker/gobinary/Dockerfile, , evobgp-render) (push) Has been cancelled
CI / docker-go (evobgp-all, 1, deploy/docker/gobinary/Dockerfile, , evobgp-all) (push) Has been cancelled
CI / docker-go (evobgp-scheduler, 0, deploy/docker/gobinary/Dockerfile, , evobgp-scheduler) (push) Has been cancelled
test: add validation for 'strict bind on' in BGP rendering tests. Enhance BGP test cases to ensure 'strict bind on' is included in the rendered output, improving test coverage for BGP template configurations.
2026-04-06 14:00:41 +07:00

226 lines
7.6 KiB
Go

package birdfmt
import (
"fmt"
"strings"
)
// BGP template names referenced by generated peers (BIRD 2).
const (
BGPTemplateNameV4 = "bgp_template"
BGPTemplateNameV6 = "bgp_template_v6"
)
// 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.
type BGPTemplatesOptions struct {
LocalASN uint32
ExportFilterV4 string
ExportFilterV6 string
}
// RenderBGPTemplates renders two template bgp blocks (IPv4 and IPv6 AFI).
func RenderBGPTemplates(opts BGPTemplatesOptions) (string, error) {
if opts.LocalASN == 0 {
return "", fmt.Errorf("birdfmt: template local ASN must be non-zero")
}
exp4 := "all"
if strings.TrimSpace(opts.ExportFilterV4) != "" {
exp4 = "filter " + strings.TrimSpace(opts.ExportFilterV4)
}
exp6 := "all"
if strings.TrimSpace(opts.ExportFilterV6) != "" {
exp6 = "filter " + strings.TrimSpace(opts.ExportFilterV6)
}
var b strings.Builder
fmt.Fprintf(&b, "template bgp %s {\n", BGPTemplateNameV4)
fmt.Fprintf(&b, " local as %d;\n", opts.LocalASN)
b.WriteString(" ipv4 {\n")
b.WriteString(" import none;\n")
b.WriteString(" export ")
b.WriteString(exp4)
b.WriteString(";\n")
b.WriteString(" };\n")
b.WriteString(" hold time 90;\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")
fmt.Fprintf(&b, "template bgp %s {\n", BGPTemplateNameV6)
fmt.Fprintf(&b, " local as %d;\n", opts.LocalASN)
b.WriteString(" ipv6 {\n")
b.WriteString(" import none;\n")
b.WriteString(" export ")
b.WriteString(exp6)
b.WriteString(";\n")
b.WriteString(" };\n")
b.WriteString(" hold time 90;\n")
b.WriteString(" keepalive time 30;\n")
b.WriteString(" strict bind on;\n")
b.WriteString("}\n")
return b.String(), nil
}
// BGPPeerFromTemplateOptions describes protocol bgp NAME from TEMPLATE { … }.
type BGPPeerFromTemplateOptions struct {
ProtocolName string
TemplateName string
NeighborIP string
NeighborASN uint32
SourceAddress string
// If set, emits "local … as …" before neighbor (overrides template local/ASN for this peer).
OverrideLocalIP string
OverrideLocalASN uint32
}
// RenderProtocolBGPFromTemplate renders protocol bgp … from TEMPLATE { neighbor; multihop; source address; strict bind; passive; }.
// strict bind дублируется и в шаблоне, и в каждом пире — явная фиксация на сессию.
func RenderProtocolBGPFromTemplate(opts BGPPeerFromTemplateOptions) (string, error) {
if strings.TrimSpace(opts.ProtocolName) == "" {
return "", fmt.Errorf("birdfmt: protocol name is required")
}
if strings.TrimSpace(opts.TemplateName) == "" {
return "", fmt.Errorf("birdfmt: template name is required")
}
if strings.TrimSpace(opts.NeighborIP) == "" || strings.TrimSpace(opts.SourceAddress) == "" {
return "", fmt.Errorf("birdfmt: neighbor and source address are required")
}
if opts.NeighborASN == 0 {
return "", fmt.Errorf("birdfmt: neighbor ASN must be non-zero")
}
if opts.OverrideLocalIP != "" && opts.OverrideLocalASN == 0 {
return "", fmt.Errorf("birdfmt: override local ASN is required when override local IP is set")
}
var b strings.Builder
b.WriteString("protocol bgp ")
b.WriteString(strings.TrimSpace(opts.ProtocolName))
b.WriteString(" from ")
b.WriteString(strings.TrimSpace(opts.TemplateName))
b.WriteString(" {\n")
if ip := strings.TrimSpace(opts.OverrideLocalIP); ip != "" {
b.WriteString(" local ")
b.WriteString(ip)
fmt.Fprintf(&b, " as %d;\n", opts.OverrideLocalASN)
}
b.WriteString(" neighbor ")
b.WriteString(strings.TrimSpace(opts.NeighborIP))
fmt.Fprintf(&b, " as %d;\n", opts.NeighborASN)
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("}\n")
return b.String(), nil
}
// BGPPeerIPv4Options describes a single BGP session (BIRD 2, IPv4 AF).
type BGPPeerIPv4Options struct {
ProtocolName string // e.g. evobgp_peer_uplink
LocalIP string // e.g. 192.0.2.1
LocalASN uint32
NeighborIP string
NeighborASN uint32
// ExportFilter is a filter name, or empty for "export all".
ExportFilter string
// ImportFilter is a filter name, or empty for "import all".
ImportFilter string
}
// RenderProtocolBGPIPv4 renders a protocol bgp { … ipv4 { … } } block.
func RenderProtocolBGPIPv4(opts BGPPeerIPv4Options) (string, error) {
if strings.TrimSpace(opts.ProtocolName) == "" {
return "", fmt.Errorf("birdfmt: protocol name is required")
}
if strings.TrimSpace(opts.LocalIP) == "" || strings.TrimSpace(opts.NeighborIP) == "" {
return "", fmt.Errorf("birdfmt: local and neighbor addresses are required")
}
if opts.LocalASN == 0 || opts.NeighborASN == 0 {
return "", fmt.Errorf("birdfmt: AS numbers must be non-zero")
}
imp := "all"
if strings.TrimSpace(opts.ImportFilter) != "" {
imp = "filter " + strings.TrimSpace(opts.ImportFilter)
}
exp := "all"
if strings.TrimSpace(opts.ExportFilter) != "" {
exp = "filter " + strings.TrimSpace(opts.ExportFilter)
}
var b strings.Builder
b.WriteString("protocol bgp ")
b.WriteString(strings.TrimSpace(opts.ProtocolName))
b.WriteString(" {\n")
b.WriteString(" local ")
b.WriteString(strings.TrimSpace(opts.LocalIP))
fmt.Fprintf(&b, " as %d;\n", opts.LocalASN)
b.WriteString(" neighbor ")
b.WriteString(strings.TrimSpace(opts.NeighborIP))
fmt.Fprintf(&b, " as %d;\n", opts.NeighborASN)
b.WriteString(" ipv4 {\n")
b.WriteString(" import ")
b.WriteString(imp)
b.WriteString(";\n")
b.WriteString(" export ")
b.WriteString(exp)
b.WriteString(";\n")
b.WriteString(" };\n")
b.WriteString("}\n")
return b.String(), nil
}
// BGPPeerIPv6Options describes a BGP session for the IPv6 AF.
type BGPPeerIPv6Options struct {
ProtocolName string
LocalIP string
LocalASN uint32
NeighborIP string
NeighborASN uint32
ExportFilter string
ImportFilter string
}
// RenderProtocolBGPIPv6 renders a protocol bgp block with ipv6 { import/export }.
func RenderProtocolBGPIPv6(opts BGPPeerIPv6Options) (string, error) {
if strings.TrimSpace(opts.ProtocolName) == "" {
return "", fmt.Errorf("birdfmt: protocol name is required")
}
if strings.TrimSpace(opts.LocalIP) == "" || strings.TrimSpace(opts.NeighborIP) == "" {
return "", fmt.Errorf("birdfmt: local and neighbor addresses are required")
}
if opts.LocalASN == 0 || opts.NeighborASN == 0 {
return "", fmt.Errorf("birdfmt: AS numbers must be non-zero")
}
imp := "all"
if strings.TrimSpace(opts.ImportFilter) != "" {
imp = "filter " + strings.TrimSpace(opts.ImportFilter)
}
exp := "all"
if strings.TrimSpace(opts.ExportFilter) != "" {
exp = "filter " + strings.TrimSpace(opts.ExportFilter)
}
var b strings.Builder
b.WriteString("protocol bgp ")
b.WriteString(strings.TrimSpace(opts.ProtocolName))
b.WriteString(" {\n")
b.WriteString(" local ")
b.WriteString(strings.TrimSpace(opts.LocalIP))
fmt.Fprintf(&b, " as %d;\n", opts.LocalASN)
b.WriteString(" neighbor ")
b.WriteString(strings.TrimSpace(opts.NeighborIP))
fmt.Fprintf(&b, " as %d;\n", opts.NeighborASN)
b.WriteString(" ipv6 {\n")
b.WriteString(" import ")
b.WriteString(imp)
b.WriteString(";\n")
b.WriteString(" export ")
b.WriteString(exp)
b.WriteString(";\n")
b.WriteString(" };\n")
b.WriteString("}\n")
return b.String(), nil
}