feat: enhance peer neighbor handling by introducing NormalizePeerNeighborString function. Update CreatePeer and UpdatePeer methods in Postgres and Memory stores to validate and normalize neighbor input, improving data integrity and error handling.
CI / changes (push) Successful in 5s
CI / openapi (push) Has been skipped
CI / go (push) Successful in 26s
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 1m4s
CI / docker-go (evobgp-all, 1, deploy/docker/gobinary/Dockerfile, , evobgp-all) (push) Successful in 1m26s
CI / docker-go (evobgp-api, 1, deploy/docker/gobinary/Dockerfile, , evobgp-api) (push) Successful in 1m29s
CI / docker-go (evobgp-deploy, 0, deploy/docker/gobinary/Dockerfile, , evobgp-deploy) (push) Successful in 1m27s
CI / docker-go (evobgp-ingest, 0, deploy/docker/gobinary/Dockerfile, , evobgp-ingest) (push) Successful in 1m23s
CI / docker-go (evobgp-node, 0, deploy/docker/gobinary/Dockerfile, , evobgp-node) (push) Successful in 1m19s
CI / docker-go (evobgp-render, 0, deploy/docker/gobinary/Dockerfile, , evobgp-render) (push) Successful in 1m29s
CI / docker-go (evobgp-scheduler, 0, deploy/docker/gobinary/Dockerfile, , evobgp-scheduler) (push) Successful in 1m22s

This commit is contained in:
Denozordec
2026-04-06 01:01:40 +07:00
parent d92a06c9c8
commit 80f3a8d105
5 changed files with 96 additions and 12 deletions
+2 -7
View File
@@ -7,7 +7,6 @@ import (
"fmt" "fmt"
"io" "io"
"net/http" "net/http"
"net/netip"
"os" "os"
"sort" "sort"
"strconv" "strconv"
@@ -345,12 +344,8 @@ func renderPeersBirdFragment(st store.Backend, tenantID string, loc birdLocals)
if p == nil || !p.Enabled { if p == nil || !p.Enabled {
continue continue
} }
neighbor := strings.TrimSpace(p.Neighbor) addr, ok := store.ParsePeerNeighbor(p.Neighbor)
if neighbor == "" { if !ok {
continue
}
addr, err := netip.ParseAddr(neighbor)
if err != nil {
continue continue
} }
if !store.ValidASN(p.RemoteASN) { if !store.ValidASN(p.RemoteASN) {
+10 -2
View File
@@ -315,6 +315,10 @@ func (p *Postgres) CreatePeer(tenantID string, in *store.BGPPeer) (*store.BGPPee
if in == nil || in.RemoteASN == 0 { if in == nil || in.RemoteASN == 0 {
return nil, store.ErrInvalidInput return nil, store.ErrInvalidInput
} }
neighbor, ok := store.NormalizePeerNeighborString(in.Neighbor)
if !ok {
return nil, store.ErrInvalidInput
}
ctx := context.Background() ctx := context.Background()
id := uuid.NewString() id := uuid.NewString()
meta := map[string]any{"name": in.Name, "session_state": in.SessionState} meta := map[string]any{"name": in.Name, "session_state": in.SessionState}
@@ -330,7 +334,7 @@ func (p *Postgres) CreatePeer(tenantID string, in *store.BGPPeer) (*store.BGPPee
_, err := p.pool.Exec(ctx, ` _, err := p.pool.Exec(ctx, `
INSERT INTO bgp_peer (id, tenant_id, bgp_speaker_id, neighbor, remote_asn, enabled, policies_json, meta_json) INSERT INTO bgp_peer (id, tenant_id, bgp_speaker_id, neighbor, remote_asn, enabled, policies_json, meta_json)
VALUES ($1,$2,$3,$4::inet, $5, $6, $7::jsonb, $8::jsonb)`, VALUES ($1,$2,$3,$4::inet, $5, $6, $7::jsonb, $8::jsonb)`,
id, tenantID, sp, in.Neighbor, in.RemoteASN, in.Enabled, pol, string(mb)) id, tenantID, sp, neighbor, in.RemoteASN, in.Enabled, pol, string(mb))
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -343,7 +347,11 @@ func (p *Postgres) UpdatePeer(tenantID, id string, patch *store.PeerPatch) (*sto
return nil, err return nil, err
} }
if patch.Neighbor != nil { if patch.Neighbor != nil {
cur.Neighbor = strings.TrimSpace(*patch.Neighbor) n, ok := store.NormalizePeerNeighborString(*patch.Neighbor)
if !ok {
return nil, store.ErrInvalidInput
}
cur.Neighbor = n
} }
if patch.RemoteASN != nil { if patch.RemoteASN != nil {
cur.RemoteASN = *patch.RemoteASN cur.RemoteASN = *patch.RemoteASN
+11 -3
View File
@@ -614,7 +614,11 @@ func (m *Memory) GetPeer(tenantID, id string) (*BGPPeer, error) {
} }
func (m *Memory) CreatePeer(tenantID string, in *BGPPeer) (*BGPPeer, error) { func (m *Memory) CreatePeer(tenantID string, in *BGPPeer) (*BGPPeer, error) {
if in == nil || strings.TrimSpace(in.Neighbor) == "" || in.RemoteASN == 0 { if in == nil || in.RemoteASN == 0 {
return nil, ErrInvalidInput
}
neighbor, ok := NormalizePeerNeighborString(in.Neighbor)
if !ok {
return nil, ErrInvalidInput return nil, ErrInvalidInput
} }
m.mu.Lock() m.mu.Lock()
@@ -625,7 +629,7 @@ func (m *Memory) CreatePeer(tenantID string, in *BGPPeer) (*BGPPeer, error) {
id := uuid.NewString() id := uuid.NewString()
p := &BGPPeer{ p := &BGPPeer{
ID: id, TenantID: tenantID, SpeakerID: in.SpeakerID, Name: in.Name, ID: id, TenantID: tenantID, SpeakerID: in.SpeakerID, Name: in.Name,
Neighbor: strings.TrimSpace(in.Neighbor), RemoteASN: in.RemoteASN, Enabled: in.Enabled, Neighbor: neighbor, RemoteASN: in.RemoteASN, Enabled: in.Enabled,
SessionState: in.SessionState, PoliciesJSON: in.PoliciesJSON, SessionState: in.SessionState, PoliciesJSON: in.PoliciesJSON,
} }
if !p.Enabled && p.SessionState == "" { if !p.Enabled && p.SessionState == "" {
@@ -646,7 +650,11 @@ func (m *Memory) UpdatePeer(tenantID, id string, patch *PeerPatch) (*BGPPeer, er
return nil, ErrNotFound return nil, ErrNotFound
} }
if patch.Neighbor != nil { if patch.Neighbor != nil {
p.Neighbor = strings.TrimSpace(*patch.Neighbor) n, ok := NormalizePeerNeighborString(*patch.Neighbor)
if !ok {
return nil, ErrInvalidInput
}
p.Neighbor = n
} }
if patch.RemoteASN != nil { if patch.RemoteASN != nil {
p.RemoteASN = *patch.RemoteASN p.RemoteASN = *patch.RemoteASN
+40
View File
@@ -0,0 +1,40 @@
package store
import (
"net/netip"
"strings"
)
// ParsePeerNeighbor parses a BGP neighbor value for BIRD output: a plain IPv4/IPv6
// address, or a host prefix (/32 or /128) which is a common input mistake.
func ParsePeerNeighbor(s string) (netip.Addr, bool) {
s = strings.TrimSpace(s)
if s == "" {
return netip.Addr{}, false
}
if addr, err := netip.ParseAddr(s); err == nil {
return addr, true
}
pfx, err := netip.ParsePrefix(s)
if err != nil {
return netip.Addr{}, false
}
addr := pfx.Addr()
if addr.Is4() && pfx.Bits() == 32 {
return addr, true
}
if addr.Is6() && pfx.Bits() == 128 {
return addr, true
}
return netip.Addr{}, false
}
// NormalizePeerNeighborString returns the canonical host address string for storage
// and BIRD, or false if s is not a usable neighbor.
func NormalizePeerNeighborString(s string) (string, bool) {
addr, ok := ParsePeerNeighbor(s)
if !ok {
return "", false
}
return addr.String(), true
}
+33
View File
@@ -0,0 +1,33 @@
package store
import "testing"
func TestParsePeerNeighbor(t *testing.T) {
tests := []struct {
in string
want string
wantOK bool
}{
{"192.168.0.2", "192.168.0.2", true},
{"192.168.0.2/32", "192.168.0.2", true},
{" 192.168.0.2/32 ", "192.168.0.2", true},
{"2001:db8::1", "2001:db8::1", true},
{"2001:db8::1/128", "2001:db8::1", true},
{"192.168.0.0/24", "", false},
{"not-an-ip", "", false},
{"", "", false},
}
for _, tt := range tests {
addr, ok := ParsePeerNeighbor(tt.in)
if ok != tt.wantOK {
t.Errorf("ParsePeerNeighbor(%q) ok=%v want %v", tt.in, ok, tt.wantOK)
continue
}
if !tt.wantOK {
continue
}
if got := addr.String(); got != tt.want {
t.Errorf("ParsePeerNeighbor(%q) = %q want %q", tt.in, got, tt.want)
}
}
}