CI / changes (push) Successful in 5s
CI / openapi (push) Has been skipped
CI / go (push) Successful in 38s
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-prime (push) Successful in 24s
CI / docker-go (deploy/docker/evobgp-agent/Dockerfile, , evobgp-agent) (push) Successful in 59s
CI / docker-go (evobgp-all, 1, deploy/docker/gobinary/Dockerfile, , evobgp-all) (push) Successful in 2m6s
CI / docker-go (evobgp-api, 1, deploy/docker/gobinary/Dockerfile, , evobgp-api) (push) Successful in 1m22s
CI / docker-go (evobgp-deploy, 0, deploy/docker/gobinary/Dockerfile, , evobgp-deploy) (push) Successful in 1m21s
CI / docker-go (evobgp-ingest, 0, deploy/docker/gobinary/Dockerfile, , evobgp-ingest) (push) Successful in 1m24s
CI / docker-go (evobgp-node, 0, deploy/docker/gobinary/Dockerfile, , evobgp-node) (push) Successful in 1m8s
CI / docker-go (evobgp-render, 0, deploy/docker/gobinary/Dockerfile, , evobgp-render) (push) Successful in 1m23s
CI / docker-go (evobgp-scheduler, 0, deploy/docker/gobinary/Dockerfile, , evobgp-scheduler) (push) Successful in 1m22s
150 lines
3.6 KiB
Go
150 lines
3.6 KiB
Go
package pipeline
|
|
|
|
import (
|
|
"fmt"
|
|
"net/netip"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"evobgp/internal/birdfmt"
|
|
"evobgp/internal/store"
|
|
)
|
|
|
|
type staticCommunityRoutes struct {
|
|
CommunityID string
|
|
RoutesV4 []birdfmt.StaticRoute
|
|
RoutesV6 []birdfmt.StaticRoute
|
|
}
|
|
|
|
// materializeRowsForBird turns prefix rows into export-filter CIDR lists, optional AS_PATH rules,
|
|
// and static routes grouped by community. The same prefix can appear in multiple community groups.
|
|
func materializeRowsForBird(st store.Backend, tenantID string, rows []store.PrefixRow) (
|
|
filterV4, filterV6 []netip.Prefix,
|
|
pathASNs []int64,
|
|
staticByCommunity []staticCommunityRoutes,
|
|
err error,
|
|
) {
|
|
var pathSeen map[int64]struct{}
|
|
addPath := func(n int64) {
|
|
if pathSeen == nil {
|
|
pathSeen = make(map[int64]struct{})
|
|
}
|
|
if _, ok := pathSeen[n]; ok {
|
|
return
|
|
}
|
|
pathSeen[n] = struct{}{}
|
|
pathASNs = append(pathASNs, n)
|
|
}
|
|
|
|
filterSeen := make(map[string]struct{})
|
|
type staticGroup struct {
|
|
v4 map[string]birdfmt.StaticRoute
|
|
v6 map[string]birdfmt.StaticRoute
|
|
}
|
|
staticGroups := make(map[string]*staticGroup)
|
|
routeBodyByCommunity := make(map[string]string)
|
|
|
|
for _, pr := range rows {
|
|
p := strings.TrimSpace(pr.Prefix)
|
|
if strings.HasPrefix(p, "as:") {
|
|
n, err := strconv.ParseInt(strings.TrimPrefix(p, "as:"), 10, 64)
|
|
if err != nil || !store.ValidASN(n) {
|
|
continue
|
|
}
|
|
addPath(n)
|
|
continue
|
|
}
|
|
pfx, err := netip.ParsePrefix(p)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
pfx = pfx.Masked()
|
|
key := pfx.String()
|
|
if _, ok := filterSeen[key]; !ok {
|
|
filterSeen[key] = struct{}{}
|
|
if pfx.Addr().Is4() {
|
|
filterV4 = append(filterV4, pfx)
|
|
} else {
|
|
filterV6 = append(filterV6, pfx)
|
|
}
|
|
}
|
|
|
|
communityID := ""
|
|
if pr.CommunityID != nil {
|
|
communityID = strings.TrimSpace(*pr.CommunityID)
|
|
}
|
|
body, ok := routeBodyByCommunity[communityID]
|
|
if !ok {
|
|
body, err = communityRouteBody(st, tenantID, pr.CommunityID)
|
|
if err != nil {
|
|
return nil, nil, nil, nil, err
|
|
}
|
|
routeBodyByCommunity[communityID] = body
|
|
}
|
|
grp, ok := staticGroups[communityID]
|
|
if !ok {
|
|
grp = &staticGroup{
|
|
v4: make(map[string]birdfmt.StaticRoute),
|
|
v6: make(map[string]birdfmt.StaticRoute),
|
|
}
|
|
staticGroups[communityID] = grp
|
|
}
|
|
if pfx.Addr().Is4() {
|
|
if _, exists := grp.v4[key]; !exists {
|
|
grp.v4[key] = birdfmt.StaticRoute{Prefix: pfx, RouteBody: body}
|
|
}
|
|
} else if pfx.Addr().Is6() {
|
|
if _, exists := grp.v6[key]; !exists {
|
|
grp.v6[key] = birdfmt.StaticRoute{Prefix: pfx, RouteBody: body}
|
|
}
|
|
}
|
|
}
|
|
|
|
groupKeys := make([]string, 0, len(staticGroups))
|
|
for k := range staticGroups {
|
|
groupKeys = append(groupKeys, k)
|
|
}
|
|
sort.Strings(groupKeys)
|
|
|
|
for _, gid := range groupKeys {
|
|
grp := staticGroups[gid]
|
|
out := staticCommunityRoutes{CommunityID: gid}
|
|
|
|
v4Keys := make([]string, 0, len(grp.v4))
|
|
for k := range grp.v4 {
|
|
v4Keys = append(v4Keys, k)
|
|
}
|
|
sort.Strings(v4Keys)
|
|
for _, k := range v4Keys {
|
|
out.RoutesV4 = append(out.RoutesV4, grp.v4[k])
|
|
}
|
|
|
|
v6Keys := make([]string, 0, len(grp.v6))
|
|
for k := range grp.v6 {
|
|
v6Keys = append(v6Keys, k)
|
|
}
|
|
sort.Strings(v6Keys)
|
|
for _, k := range v6Keys {
|
|
out.RoutesV6 = append(out.RoutesV6, grp.v6[k])
|
|
}
|
|
staticByCommunity = append(staticByCommunity, out)
|
|
}
|
|
return filterV4, filterV6, pathASNs, staticByCommunity, nil
|
|
}
|
|
|
|
func communityRouteBody(st store.Backend, tenantID string, cid *string) (string, error) {
|
|
if cid == nil {
|
|
return "", nil
|
|
}
|
|
id := strings.TrimSpace(*cid)
|
|
if id == "" {
|
|
return "", nil
|
|
}
|
|
c, err := st.GetCommunity(tenantID, id)
|
|
if err != nil {
|
|
return "", fmt.Errorf("community %s: %w", id, err)
|
|
}
|
|
return birdfmt.RouteCommunityAttrs("", c.Community, c.ValueJSON)
|
|
}
|