Skip to content

Commit 6a40d68

Browse files
authored
fix: enforce neighbor address validation in configuration (#6134)
fix: require cluster-as and neighbor-as parameters in configuration add more log Signed-off-by: zbb88888 <jmdxjsjgcxy@gmail.com>
1 parent a52abe2 commit 6a40d68

File tree

3 files changed

+175
-10
lines changed

3 files changed

+175
-10
lines changed

pkg/speaker/config.go

Lines changed: 69 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,13 @@ import (
2828

2929
const (
3030
DefaultBGPGrpcPort = 50051
31-
DefaultBGPClusterAs = 65000
32-
DefaultBGPNeighborAs = 65001
3331
DefaultBGPHoldtime = 90 * time.Second
3432
DefaultPprofPort = 10667
3533
DefaultGracefulRestartDeferralTime = 360 * time.Second
3634
DefaultGracefulRestartTime = 90 * time.Second
3735
DefaultEbgpMultiHop = 1
36+
addPeerMaxRetries = 12
37+
addPeerRetryInterval = 5 * time.Second
3838
)
3939

4040
type Configuration struct {
@@ -77,12 +77,12 @@ func ParseFlags() (*Configuration, error) {
7777
argAnnounceClusterIP = pflag.BoolP("announce-cluster-ip", "", false, "The Cluster IP of the service to announce to the BGP peers.")
7878
argGrpcHost = pflag.IP("grpc-host", net.IP{127, 0, 0, 1}, "The host address for grpc to listen, default: 127.0.0.1")
7979
argGrpcPort = pflag.Int32("grpc-port", DefaultBGPGrpcPort, "The port for grpc to listen, default:50051")
80-
argClusterAs = pflag.Uint32("cluster-as", DefaultBGPClusterAs, "The AS number of container network, default 65000")
80+
argClusterAs = pflag.Uint32("cluster-as", 0, "The AS number of the local BGP speaker (required)")
8181
argRouterID = pflag.IP("router-id", nil, "The address for the speaker to use as router id, default the node ip")
8282
argNodeIPs = pflag.IPSlice("node-ips", nil, "The comma-separated list of node IP addresses to use instead of the pod IP address for the next hop router IP address.")
8383
argNeighborAddress = pflag.IPSlice("neighbor-address", nil, "Comma separated IPv4 router addresses the speaker connects to.")
8484
argNeighborIPv6Address = pflag.IPSlice("neighbor-ipv6-address", nil, "Comma separated IPv6 router addresses the speaker connects to.")
85-
argNeighborAs = pflag.Uint32("neighbor-as", DefaultBGPNeighborAs, "The router as number, default 65001")
85+
argNeighborAs = pflag.Uint32("neighbor-as", 0, "The AS number of the BGP neighbor/peer (required)")
8686
argAuthPassword = pflag.String("auth-password", "", "bgp peer auth password")
8787
argHoldTime = pflag.Duration("holdtime", DefaultBGPHoldtime, "ovn-speaker goes down abnormally, the local saving time of BGP route will be affected.Holdtime must be in the range 3s to 65536s. (default 90s)")
8888
argPprofPort = pflag.Int32("pprof-port", DefaultPprofPort, "The port to get profiling data, default: 10667")
@@ -169,6 +169,10 @@ func ParseFlags() (*Configuration, error) {
169169
LogPerm: *argLogPerm,
170170
}
171171

172+
if err := config.validateRequiredFlags(); err != nil {
173+
return nil, err
174+
}
175+
172176
for _, addr := range config.NeighborAddresses {
173177
if addr.To4() == nil {
174178
return nil, fmt.Errorf("invalid neighbor-address format: %s", *argNeighborAddress)
@@ -202,6 +206,27 @@ func ParseFlags() (*Configuration, error) {
202206
return config, nil
203207
}
204208

209+
// validateRequiredFlags checks that all required BGP configuration flags are provided.
210+
// It collects all missing flags and returns them in a single error message.
211+
func (config *Configuration) validateRequiredFlags() error {
212+
var missingFlags []string
213+
214+
if len(config.NeighborAddresses) == 0 && len(config.NeighborIPv6Addresses) == 0 {
215+
missingFlags = append(missingFlags, "at least one of --neighbor-address or --neighbor-ipv6-address must be specified")
216+
}
217+
if config.ClusterAs == 0 {
218+
missingFlags = append(missingFlags, "--cluster-as must be specified")
219+
}
220+
if config.NeighborAs == 0 {
221+
missingFlags = append(missingFlags, "--neighbor-as must be specified")
222+
}
223+
224+
if len(missingFlags) > 0 {
225+
return fmt.Errorf("missing required flags: %s", strings.Join(missingFlags, "; "))
226+
}
227+
return nil
228+
}
229+
205230
func (config *Configuration) initKubeClient() error {
206231
var cfg *rest.Config
207232
var err error
@@ -293,6 +318,8 @@ func (config *Configuration) initBgpServer() error {
293318
UseMultiplePaths: true,
294319
},
295320
}); err != nil {
321+
err = fmt.Errorf("failed to start bgp server: %w", err)
322+
klog.Error(err)
296323
return err
297324
}
298325
for ipFamily, addresses := range peersMap {
@@ -318,6 +345,8 @@ func (config *Configuration) initBgpServer() error {
318345
}
319346
if config.GracefulRestart {
320347
if err := config.checkGracefulRestartOptions(); err != nil {
348+
err = fmt.Errorf("failed to check graceful restart options: %w", err)
349+
klog.Error(err)
321350
return err
322351
}
323352
peer.GracefulRestart = &api.GracefulRestart{
@@ -354,9 +383,10 @@ func (config *Configuration) initBgpServer() error {
354383
})
355384
}
356385

357-
if err := s.AddPeer(context.Background(), &api.AddPeerRequest{
358-
Peer: peer,
359-
}); err != nil {
386+
logBgpPeer(peer)
387+
if err := addPeerWithRetry(s, peer); err != nil {
388+
err = fmt.Errorf("failed to add peer %s: %w", addr.String(), err)
389+
klog.Error(err)
360390
return err
361391
}
362392
}
@@ -365,3 +395,35 @@ func (config *Configuration) initBgpServer() error {
365395
config.BgpServer = s
366396
return nil
367397
}
398+
399+
// addPeerWithRetry attempts to add a BGP peer with retry logic.
400+
// It retries up to addPeerMaxRetries times with addPeerRetryInterval between attempts.
401+
func addPeerWithRetry(s *gobgp.BgpServer, peer *api.Peer) error {
402+
var err error
403+
for i := 0; i < addPeerMaxRetries; i++ {
404+
if err = s.AddPeer(context.Background(), &api.AddPeerRequest{
405+
Peer: peer,
406+
}); err == nil {
407+
return nil
408+
}
409+
klog.Errorf("failed to add peer %s (attempt %d/%d): %v", peer.Conf.NeighborAddress, i+1, addPeerMaxRetries, err)
410+
if i < addPeerMaxRetries-1 {
411+
time.Sleep(addPeerRetryInterval)
412+
}
413+
}
414+
err = fmt.Errorf("failed to add peer %s after %d attempts: %w", peer.Conf.NeighborAddress, addPeerMaxRetries, err)
415+
klog.Error(err)
416+
return err
417+
}
418+
419+
// logBgpPeer logs the BGP peer configuration for debugging purposes.
420+
func logBgpPeer(peer *api.Peer) {
421+
klog.Infof("BGP Peer Configuration: NeighborAddress=%s, PeerAsn=%d, HoldTime=%d, PassiveMode=%v, EbgpMultihop=%v, GracefulRestart=%v, AfiSafis=%v",
422+
peer.Conf.NeighborAddress,
423+
peer.Conf.PeerAsn,
424+
peer.Timers.Config.HoldTime,
425+
peer.Transport.PassiveMode,
426+
peer.EbgpMultihop,
427+
peer.GracefulRestart,
428+
peer.AfiSafis)
429+
}

pkg/speaker/config_test.go

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
package speaker
2+
3+
import (
4+
"net"
5+
"testing"
6+
7+
"github.com/stretchr/testify/require"
8+
)
9+
10+
func TestValidateRequiredFlags(t *testing.T) {
11+
tests := []struct {
12+
name string
13+
config *Configuration
14+
expectError bool
15+
errContains []string
16+
}{
17+
{
18+
name: "all required flags provided with IPv4 neighbor",
19+
config: &Configuration{
20+
NeighborAddresses: []net.IP{net.ParseIP("192.168.1.1")},
21+
ClusterAs: 65000,
22+
NeighborAs: 65001,
23+
},
24+
expectError: false,
25+
},
26+
{
27+
name: "all required flags provided with IPv6 neighbor",
28+
config: &Configuration{
29+
NeighborIPv6Addresses: []net.IP{net.ParseIP("2001:db8::1")},
30+
ClusterAs: 65000,
31+
NeighborAs: 65001,
32+
},
33+
expectError: false,
34+
},
35+
{
36+
name: "all required flags provided with both IPv4 and IPv6 neighbors",
37+
config: &Configuration{
38+
NeighborAddresses: []net.IP{net.ParseIP("192.168.1.1")},
39+
NeighborIPv6Addresses: []net.IP{net.ParseIP("2001:db8::1")},
40+
ClusterAs: 65000,
41+
NeighborAs: 65001,
42+
},
43+
expectError: false,
44+
},
45+
{
46+
name: "missing neighbor addresses",
47+
config: &Configuration{
48+
ClusterAs: 65000,
49+
NeighborAs: 65001,
50+
},
51+
expectError: true,
52+
errContains: []string{"neighbor-address", "neighbor-ipv6-address"},
53+
},
54+
{
55+
name: "missing cluster-as",
56+
config: &Configuration{
57+
NeighborAddresses: []net.IP{net.ParseIP("192.168.1.1")},
58+
NeighborAs: 65001,
59+
},
60+
expectError: true,
61+
errContains: []string{"cluster-as"},
62+
},
63+
{
64+
name: "missing neighbor-as",
65+
config: &Configuration{
66+
NeighborAddresses: []net.IP{net.ParseIP("192.168.1.1")},
67+
ClusterAs: 65000,
68+
},
69+
expectError: true,
70+
errContains: []string{"neighbor-as"},
71+
},
72+
{
73+
name: "missing all required flags",
74+
config: &Configuration{},
75+
expectError: true,
76+
errContains: []string{"neighbor-address", "cluster-as", "neighbor-as"},
77+
},
78+
}
79+
80+
for _, tt := range tests {
81+
t.Run(tt.name, func(t *testing.T) {
82+
err := tt.config.validateRequiredFlags()
83+
if tt.expectError {
84+
require.Error(t, err)
85+
for _, s := range tt.errContains {
86+
require.Contains(t, err.Error(), s)
87+
}
88+
} else {
89+
require.NoError(t, err)
90+
}
91+
})
92+
}
93+
}

pkg/speaker/eip.go

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66

77
"k8s.io/apimachinery/pkg/labels"
88
"k8s.io/apimachinery/pkg/selection"
9+
"k8s.io/klog/v2"
910

1011
v1 "github.com/kubeovn/kube-ovn/pkg/apis/kubeovn/v1"
1112
"github.com/kubeovn/kube-ovn/pkg/util"
@@ -22,16 +23,25 @@ func (c *Controller) syncEIPRoutes() error {
2223
// Create label requirements to only get EIPs attached to our NAT GW
2324
requirements, err := labels.NewRequirement(util.VpcNatGatewayNameLabel, selection.Equals, []string{gatewayName})
2425
if err != nil {
25-
return fmt.Errorf("failed to create label selector requirement: %w", err)
26+
err = fmt.Errorf("failed to create label selector requirement: %w", err)
27+
klog.Error(err)
28+
return err
2629
}
2730

2831
// Filter all EIPs attached to our NAT GW
2932
eips, err := c.eipLister.List(labels.NewSelector().Add(*requirements))
3033
if err != nil {
31-
return fmt.Errorf("failed to list EIPs attached to our GW: %w", err)
34+
err = fmt.Errorf("failed to list EIPs attached to our GW: %w", err)
35+
klog.Error(err)
36+
return err
3237
}
3338

34-
return c.announceEIPs(eips)
39+
if err = c.announceEIPs(eips); err != nil {
40+
err = fmt.Errorf("failed to announce EIPs: %w", err)
41+
klog.Error(err)
42+
return err
43+
}
44+
return nil
3545
}
3646

3747
// announceEIPs announce all the prefixes related to EIPs attached to a GW

0 commit comments

Comments
 (0)