github.com/cilium/cilium@v1.16.2/pkg/bgpv1/gobgp/state_test.go (about) 1 // SPDX-License-Identifier: Apache-2.0 2 // Copyright Authors of Cilium 3 4 package gobgp 5 6 import ( 7 "context" 8 "net/netip" 9 "testing" 10 11 "github.com/osrg/gobgp/v3/pkg/packet/bgp" 12 "github.com/stretchr/testify/require" 13 "k8s.io/utils/ptr" 14 15 "github.com/cilium/cilium/api/v1/models" 16 "github.com/cilium/cilium/pkg/bgpv1/types" 17 v2alpha1api "github.com/cilium/cilium/pkg/k8s/apis/cilium.io/v2alpha1" 18 "github.com/cilium/cilium/pkg/logging" 19 "github.com/cilium/cilium/pkg/logging/logfields" 20 ) 21 22 var ( 23 log = logging.DefaultLogger.WithField(logfields.LogSubsys, "bgp-test") 24 25 neighbor64125 = &v2alpha1api.CiliumBGPNeighbor{ 26 PeerASN: 64125, 27 PeerAddress: "192.168.0.1/32", 28 PeerPort: ptr.To[int32](v2alpha1api.DefaultBGPPeerPort), 29 EBGPMultihopTTL: ptr.To[int32](1), 30 ConnectRetryTimeSeconds: ptr.To[int32](99), 31 HoldTimeSeconds: ptr.To[int32](9), 32 KeepAliveTimeSeconds: ptr.To[int32](3), 33 } 34 35 // changed ConnectRetryTime 36 neighbor64125Update = &v2alpha1api.CiliumBGPNeighbor{ 37 PeerASN: 64125, 38 PeerAddress: "192.168.0.1/32", 39 PeerPort: ptr.To[int32](v2alpha1api.DefaultBGPPeerPort), 40 EBGPMultihopTTL: ptr.To[int32](1), 41 ConnectRetryTimeSeconds: ptr.To[int32](101), 42 HoldTimeSeconds: ptr.To[int32](9), 43 KeepAliveTimeSeconds: ptr.To[int32](3), 44 } 45 46 // enabled graceful restart 47 neighbor64125UpdateGR = &v2alpha1api.CiliumBGPNeighbor{ 48 PeerASN: 64125, 49 PeerAddress: "192.168.0.1/32", 50 PeerPort: ptr.To[int32](v2alpha1api.DefaultBGPPeerPort), 51 EBGPMultihopTTL: ptr.To[int32](1), 52 ConnectRetryTimeSeconds: ptr.To[int32](99), 53 HoldTimeSeconds: ptr.To[int32](9), 54 KeepAliveTimeSeconds: ptr.To[int32](3), 55 GracefulRestart: &v2alpha1api.CiliumBGPNeighborGracefulRestart{ 56 Enabled: true, 57 RestartTimeSeconds: ptr.To[int32](120), 58 }, 59 } 60 61 // enabled graceful restart - updated restart time 62 neighbor64125UpdateGRTimer = &v2alpha1api.CiliumBGPNeighbor{ 63 PeerASN: 64125, 64 PeerAddress: "192.168.0.1/32", 65 PeerPort: ptr.To[int32](v2alpha1api.DefaultBGPPeerPort), 66 EBGPMultihopTTL: ptr.To[int32](1), 67 ConnectRetryTimeSeconds: ptr.To[int32](99), 68 HoldTimeSeconds: ptr.To[int32](9), 69 KeepAliveTimeSeconds: ptr.To[int32](3), 70 GracefulRestart: &v2alpha1api.CiliumBGPNeighborGracefulRestart{ 71 Enabled: true, 72 RestartTimeSeconds: ptr.To[int32](20), 73 }, 74 } 75 76 neighbor64126 = &v2alpha1api.CiliumBGPNeighbor{ 77 PeerASN: 64126, 78 PeerAddress: "192.168.66.1/32", 79 PeerPort: ptr.To[int32](v2alpha1api.DefaultBGPPeerPort), 80 EBGPMultihopTTL: ptr.To[int32](1), 81 ConnectRetryTimeSeconds: ptr.To[int32](99), 82 HoldTimeSeconds: ptr.To[int32](9), 83 KeepAliveTimeSeconds: ptr.To[int32](3), 84 } 85 86 // changed HoldTime & KeepAliveTime 87 neighbor64126Update = &v2alpha1api.CiliumBGPNeighbor{ 88 PeerASN: 64126, 89 PeerAddress: "192.168.66.1/32", 90 PeerPort: ptr.To[int32](v2alpha1api.DefaultBGPPeerPort), 91 EBGPMultihopTTL: ptr.To[int32](1), 92 ConnectRetryTimeSeconds: ptr.To[int32](99), 93 HoldTimeSeconds: ptr.To[int32](12), 94 KeepAliveTimeSeconds: ptr.To[int32](4), 95 } 96 97 neighbor64127 = &v2alpha1api.CiliumBGPNeighbor{ 98 PeerASN: 64127, 99 PeerAddress: "192.168.88.1/32", 100 EBGPMultihopTTL: ptr.To[int32](1), 101 ConnectRetryTimeSeconds: ptr.To[int32](99), 102 HoldTimeSeconds: ptr.To[int32](9), 103 KeepAliveTimeSeconds: ptr.To[int32](3), 104 } 105 106 // changed EBGPMultihopTTL 107 neighbor64127Update = &v2alpha1api.CiliumBGPNeighbor{ 108 PeerASN: 64127, 109 PeerAddress: "192.168.88.1/32", 110 EBGPMultihopTTL: ptr.To[int32](10), 111 ConnectRetryTimeSeconds: ptr.To[int32](99), 112 HoldTimeSeconds: ptr.To[int32](9), 113 KeepAliveTimeSeconds: ptr.To[int32](3), 114 } 115 116 neighbor64128 = &v2alpha1api.CiliumBGPNeighbor{ 117 PeerASN: 64128, 118 PeerAddress: "192.168.77.1/32", 119 PeerPort: ptr.To[int32](v2alpha1api.DefaultBGPPeerPort), 120 EBGPMultihopTTL: ptr.To[int32](1), 121 ConnectRetryTimeSeconds: ptr.To[int32](99), 122 HoldTimeSeconds: ptr.To[int32](9), 123 KeepAliveTimeSeconds: ptr.To[int32](3), 124 } 125 ) 126 127 // TestGetPeerState confirms the parsing of go bgp ListPeers to cilium modes work as intended 128 func TestGetPeerState(t *testing.T) { 129 var table = []struct { 130 // name of the test 131 name string 132 // neighbors to configure 133 neighbors []*v2alpha1api.CiliumBGPNeighbor 134 // neighbors to update 135 neighborsAfterUpdate []*v2alpha1api.CiliumBGPNeighbor 136 // localASN is local autonomous number 137 localASN uint32 138 // expected error message on AddNeighbor() or empty string for no error 139 errStr string 140 // expected error message on UpdateNeighbor() or empty string for no error 141 updateErrStr string 142 }{ 143 { 144 name: "test add neighbor", 145 neighbors: []*v2alpha1api.CiliumBGPNeighbor{neighbor64125}, 146 localASN: 64124, 147 errStr: "", 148 }, 149 { 150 name: "test add neighbor with port", 151 neighbors: []*v2alpha1api.CiliumBGPNeighbor{ 152 { 153 PeerASN: 64125, 154 PeerAddress: "192.168.0.1/32", 155 PeerPort: ptr.To[int32](175), 156 ConnectRetryTimeSeconds: ptr.To[int32](99), 157 HoldTimeSeconds: ptr.To[int32](9), 158 KeepAliveTimeSeconds: ptr.To[int32](3), 159 }, 160 }, 161 localASN: 64124, 162 errStr: "", 163 }, 164 { 165 name: "test add + update neighbors", 166 neighbors: []*v2alpha1api.CiliumBGPNeighbor{ 167 neighbor64125, 168 neighbor64126, 169 neighbor64127, 170 neighbor64128, 171 }, 172 neighborsAfterUpdate: []*v2alpha1api.CiliumBGPNeighbor{ 173 // changed ConnectRetryTime 174 neighbor64125Update, 175 // changed HoldTime & KeepAliveTime 176 neighbor64126Update, 177 // changed EBGPMultihopTTL 178 neighbor64127Update, 179 // no change 180 neighbor64128, 181 }, 182 localASN: 64124, 183 errStr: "", 184 }, 185 { 186 name: "test graceful restart - update enable", 187 neighbors: []*v2alpha1api.CiliumBGPNeighbor{ 188 neighbor64125, 189 }, 190 neighborsAfterUpdate: []*v2alpha1api.CiliumBGPNeighbor{ 191 // enabled GR 192 neighbor64125UpdateGR, 193 }, 194 localASN: 64124, 195 errStr: "", 196 }, 197 { 198 name: "test graceful restart - update restart time", 199 neighbors: []*v2alpha1api.CiliumBGPNeighbor{ 200 neighbor64125UpdateGR, 201 }, 202 neighborsAfterUpdate: []*v2alpha1api.CiliumBGPNeighbor{ 203 // changed gr restart time 204 neighbor64125UpdateGRTimer, 205 }, 206 localASN: 64124, 207 errStr: "", 208 }, 209 { 210 name: "test add invalid neighbor", 211 neighbors: []*v2alpha1api.CiliumBGPNeighbor{ 212 // invalid PeerAddress 213 { 214 PeerASN: 64125, 215 PeerAddress: "192.168.0.XYZ", 216 ConnectRetryTimeSeconds: ptr.To[int32](101), 217 HoldTimeSeconds: ptr.To[int32](30), 218 KeepAliveTimeSeconds: ptr.To[int32](10), 219 }, 220 }, 221 localASN: 64124, 222 errStr: "failed to parse PeerAddress: netip.ParsePrefix(\"192.168.0.XYZ\"): no '/'", 223 }, 224 { 225 name: "test invalid neighbor update", 226 neighbors: []*v2alpha1api.CiliumBGPNeighbor{ 227 { 228 PeerASN: 64125, 229 PeerAddress: "192.168.0.1/32", 230 ConnectRetryTimeSeconds: ptr.To[int32](101), 231 HoldTimeSeconds: ptr.To[int32](30), 232 KeepAliveTimeSeconds: ptr.To[int32](10), 233 }, 234 }, 235 neighborsAfterUpdate: []*v2alpha1api.CiliumBGPNeighbor{ 236 // different ASN 237 { 238 PeerASN: 64999, 239 PeerAddress: "192.168.0.1/32", 240 ConnectRetryTimeSeconds: ptr.To[int32](101), 241 HoldTimeSeconds: ptr.To[int32](30), 242 KeepAliveTimeSeconds: ptr.To[int32](10), 243 }, 244 }, 245 localASN: 64124, 246 errStr: "", 247 updateErrStr: "failed retrieving peer: could not find existing peer with ASN: 64999 and IP: 192.168.0.1", 248 }, 249 } 250 for _, tt := range table { 251 srvParams := types.ServerParameters{ 252 Global: types.BGPGlobal{ 253 ASN: tt.localASN, 254 RouterID: "127.0.0.1", 255 ListenPort: -1, 256 }, 257 } 258 t.Run(tt.name, func(t *testing.T) { 259 testSC, err := NewGoBGPServer(context.Background(), log, srvParams) 260 require.NoError(t, err) 261 262 t.Cleanup(func() { 263 testSC.Stop() 264 }) 265 266 // add neighbours 267 for _, n := range tt.neighbors { 268 n.SetDefaults() 269 270 err = testSC.AddNeighbor(context.Background(), types.NeighborRequest{ 271 Neighbor: n, 272 }) 273 if tt.errStr != "" { 274 require.EqualError(t, err, tt.errStr) 275 return // no more checks 276 } else { 277 require.NoError(t, err) 278 } 279 } 280 281 res, err := testSC.GetPeerState(context.Background()) 282 require.NoError(t, err) 283 284 // validate neighbors count 285 require.Len(t, res.Peers, len(tt.neighbors)) 286 287 // validate peers 288 validatePeers(t, tt.localASN, tt.neighbors, res.Peers) 289 290 // update neighbours 291 for _, n := range tt.neighborsAfterUpdate { 292 n.SetDefaults() 293 err = testSC.UpdateNeighbor(context.Background(), types.NeighborRequest{ 294 Neighbor: n, 295 }) 296 if tt.updateErrStr != "" { 297 require.EqualError(t, err, tt.updateErrStr) 298 return // no more checks 299 } else { 300 require.NoError(t, err) 301 } 302 } 303 304 res, err = testSC.GetPeerState(context.Background()) 305 require.NoError(t, err) 306 307 // validate peers 308 validatePeers(t, tt.localASN, tt.neighborsAfterUpdate, res.Peers) 309 }) 310 } 311 } 312 313 // validatePeers validates that peers returned from GoBGP GetPeerState match expected list of CiliumBGPNeighbors 314 func validatePeers(t *testing.T, localASN uint32, neighbors []*v2alpha1api.CiliumBGPNeighbor, peers []*models.BgpPeer) { 315 for _, n := range neighbors { 316 p := findMatchingPeer(t, peers, n) 317 require.NotNilf(t, p, "no matching peer for PeerASN %d and PeerAddress %s", n.PeerASN, n.PeerAddress) 318 319 // validate basic data is returned correctly 320 require.Equal(t, int64(localASN), p.LocalAsn) 321 322 expConnectRetry := ptr.Deref[int32](n.ConnectRetryTimeSeconds, v2alpha1api.DefaultBGPConnectRetryTimeSeconds) 323 expHoldTime := ptr.Deref[int32](n.HoldTimeSeconds, v2alpha1api.DefaultBGPHoldTimeSeconds) 324 expKeepAlive := ptr.Deref[int32](n.KeepAliveTimeSeconds, ptr.Deref[int32](n.KeepAliveTimeSeconds, v2alpha1api.DefaultBGPKeepAliveTimeSeconds)) 325 require.EqualValues(t, expConnectRetry, p.ConnectRetryTimeSeconds) 326 require.EqualValues(t, expHoldTime, p.ConfiguredHoldTimeSeconds) 327 require.EqualValues(t, expKeepAlive, p.ConfiguredKeepAliveTimeSeconds) 328 329 if n.GracefulRestart != nil { 330 require.EqualValues(t, n.GracefulRestart.Enabled, p.GracefulRestart.Enabled) 331 expGRRestartTime := ptr.Deref[int32](n.GracefulRestart.RestartTimeSeconds, v2alpha1api.DefaultBGPGRRestartTimeSeconds) 332 require.EqualValues(t, expGRRestartTime, p.GracefulRestart.RestartTimeSeconds) 333 } else { 334 require.False(t, p.GracefulRestart.Enabled) 335 } 336 337 if n.EBGPMultihopTTL != nil && *n.EBGPMultihopTTL > 0 { 338 require.EqualValues(t, *n.EBGPMultihopTTL, p.EbgpMultihopTTL) 339 } 340 341 // since there is no real neighbor, bgp session state will be either idle or active. 342 require.Contains(t, []string{"idle", "active"}, p.SessionState) 343 } 344 } 345 346 // findMatchingPeer finds models.BgpPeer matching to the provided v2alpha1api.CiliumBGPNeighbor based on the peer ASN and IP 347 func findMatchingPeer(t *testing.T, peers []*models.BgpPeer, n *v2alpha1api.CiliumBGPNeighbor) *models.BgpPeer { 348 for _, p := range peers { 349 nPrefix, err := netip.ParsePrefix(n.PeerAddress) 350 require.NoError(t, err) 351 pIP, err := netip.ParseAddr(p.PeerAddress) 352 require.NoError(t, err) 353 354 if p.PeerAsn == int64(n.PeerASN) && pIP.Compare(nPrefix.Addr()) == 0 { 355 return p 356 } 357 } 358 return nil 359 } 360 361 func TestGetRoutes(t *testing.T) { 362 testSC, err := NewGoBGPServer(context.Background(), log, types.ServerParameters{ 363 Global: types.BGPGlobal{ 364 ASN: 65000, 365 RouterID: "127.0.0.1", 366 ListenPort: -1, 367 }, 368 }) 369 require.NoError(t, err) 370 371 t.Cleanup(func() { 372 testSC.Stop() 373 }) 374 375 err = testSC.AddNeighbor(context.TODO(), types.NeighborRequest{ 376 Neighbor: neighbor64125, 377 }) 378 require.NoError(t, err) 379 380 _, err = testSC.AdvertisePath(context.TODO(), types.PathRequest{ 381 Path: types.NewPathForPrefix(netip.MustParsePrefix("10.0.0.0/24")), 382 }) 383 require.NoError(t, err) 384 385 _, err = testSC.AdvertisePath(context.TODO(), types.PathRequest{ 386 Path: types.NewPathForPrefix(netip.MustParsePrefix("fd00::/64")), 387 }) 388 require.NoError(t, err) 389 390 // test IPv4 address family 391 res, err := testSC.GetRoutes(context.TODO(), &types.GetRoutesRequest{ 392 TableType: types.TableTypeLocRIB, 393 Family: types.Family{ 394 Afi: types.AfiIPv4, 395 Safi: types.SafiUnicast, 396 }, 397 }) 398 require.NoError(t, err) 399 require.Equal(t, 1, len(res.Routes)) 400 require.Equal(t, 1, len(res.Routes[0].Paths)) 401 require.Equal(t, uint16(bgp.AFI_IP), res.Routes[0].Paths[0].NLRI.AFI()) 402 require.Equal(t, uint8(bgp.SAFI_UNICAST), res.Routes[0].Paths[0].NLRI.SAFI()) 403 require.IsType(t, &bgp.IPAddrPrefix{}, res.Routes[0].Paths[0].NLRI) 404 405 // test IPv6 address family 406 res, err = testSC.GetRoutes(context.TODO(), &types.GetRoutesRequest{ 407 TableType: types.TableTypeLocRIB, 408 Family: types.Family{ 409 Afi: types.AfiIPv6, 410 Safi: types.SafiUnicast, 411 }, 412 }) 413 require.NoError(t, err) 414 require.Equal(t, 1, len(res.Routes)) 415 require.Equal(t, 1, len(res.Routes[0].Paths)) 416 require.Equal(t, uint16(bgp.AFI_IP6), res.Routes[0].Paths[0].NLRI.AFI()) 417 require.Equal(t, uint8(bgp.SAFI_UNICAST), res.Routes[0].Paths[0].NLRI.SAFI()) 418 require.IsType(t, &bgp.IPv6AddrPrefix{}, res.Routes[0].Paths[0].NLRI) 419 420 // test adj-rib-out 421 res, err = testSC.GetRoutes(context.TODO(), &types.GetRoutesRequest{ 422 TableType: types.TableTypeAdjRIBOut, 423 Family: types.Family{ 424 Afi: types.AfiIPv4, 425 Safi: types.SafiUnicast, 426 }, 427 Neighbor: netip.MustParsePrefix(neighbor64125.PeerAddress).Addr(), 428 }) 429 require.NoError(t, err) 430 require.Equal(t, 0, len(res.Routes)) // adj-rib is empty as there is no actual peering up 431 432 // test adj-rib-in 433 res, err = testSC.GetRoutes(context.TODO(), &types.GetRoutesRequest{ 434 TableType: types.TableTypeAdjRIBIn, 435 Family: types.Family{ 436 Afi: types.AfiIPv6, 437 Safi: types.SafiUnicast, 438 }, 439 Neighbor: netip.MustParsePrefix(neighbor64125.PeerAddress).Addr(), 440 }) 441 require.NoError(t, err) 442 require.Equal(t, 0, len(res.Routes)) // adj-rib is empty as there is no actual peering up 443 }