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 ;" only (no local IP); peers add neighbor / multihop / passive and optional "local … as …" 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") 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("}\n") return b.String(), nil } // BGPPeerFromTemplateOptions describes protocol bgp NAME from TEMPLATE { … }. type BGPPeerFromTemplateOptions struct { ProtocolName string TemplateName string NeighborIP string NeighborASN uint32 // 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; passive; }. 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) == "" { return "", fmt.Errorf("birdfmt: neighbor is 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(" 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 }