github.com/cilium/cilium@v1.16.2/pkg/bgpv1/test/gobgp.go (about) 1 // SPDX-License-Identifier: Apache-2.0 2 // Copyright Authors of Cilium 3 4 package test 5 6 import ( 7 "context" 8 "fmt" 9 10 gobgpapi "github.com/osrg/gobgp/v3/api" 11 "github.com/osrg/gobgp/v3/pkg/apiutil" 12 gobgpb "github.com/osrg/gobgp/v3/pkg/packet/bgp" 13 "github.com/osrg/gobgp/v3/pkg/server" 14 15 "github.com/cilium/cilium/pkg/bgpv1/gobgp" 16 ) 17 18 // goBGP configuration used in tests 19 var ( 20 gobgpASN = uint32(65011) 21 gobgpASN2 = uint32(65012) 22 gobgpListenPort = int32(1791) 23 gobgpListenPort2 = int32(1792) 24 25 gobgpGlobal = &gobgpapi.Global{ 26 Asn: gobgpASN, 27 RouterId: dummies[instance1Link].ipv4.Addr().String(), 28 ListenPort: gobgpListenPort, 29 } 30 gobgpGlobal2 = &gobgpapi.Global{ 31 Asn: gobgpASN2, 32 RouterId: dummies[instance1Link].ipv4.Addr().String(), 33 ListenPort: gobgpListenPort2, 34 } 35 gobgpGlobalIBGP = &gobgpapi.Global{ 36 Asn: ciliumASN, // iBGP 37 RouterId: dummies[instance1Link].ipv4.Addr().String(), 38 ListenPort: gobgpListenPort, 39 } 40 41 gbgpNeighConf = &gobgpapi.Peer{ 42 Conf: &gobgpapi.PeerConf{ 43 NeighborAddress: dummies[ciliumLink].ipv4.Addr().String(), 44 PeerAsn: ciliumASN, 45 }, 46 Transport: &gobgpapi.Transport{ 47 RemoteAddress: dummies[ciliumLink].ipv4.Addr().String(), 48 RemotePort: ciliumListenPort, 49 LocalAddress: dummies[instance1Link].ipv4.Addr().String(), 50 PassiveMode: false, 51 }, 52 AfiSafis: []*gobgpapi.AfiSafi{ 53 { 54 Config: &gobgpapi.AfiSafiConfig{ 55 Family: gobgp.GoBGPIPv4Family, 56 }, 57 }, 58 { 59 Config: &gobgpapi.AfiSafiConfig{ 60 Family: gobgp.GoBGPIPv6Family, 61 }, 62 }, 63 }, 64 } 65 gbgpNeighConf2 = &gobgpapi.Peer{ 66 Conf: &gobgpapi.PeerConf{ 67 NeighborAddress: dummies[ciliumLink].ipv4.Addr().String(), 68 PeerAsn: ciliumASN, 69 }, 70 Transport: &gobgpapi.Transport{ 71 RemoteAddress: dummies[ciliumLink].ipv4.Addr().String(), 72 RemotePort: ciliumListenPort, 73 LocalAddress: dummies[instance2Link].ipv4.Addr().String(), 74 PassiveMode: false, 75 }, 76 AfiSafis: []*gobgpapi.AfiSafi{ 77 { 78 Config: &gobgpapi.AfiSafiConfig{ 79 Family: gobgp.GoBGPIPv4Family, 80 }, 81 }, 82 { 83 Config: &gobgpapi.AfiSafiConfig{ 84 Family: gobgp.GoBGPIPv6Family, 85 }, 86 }, 87 }, 88 } 89 gbgpNeighConfPassword = &gobgpapi.Peer{ 90 Conf: &gobgpapi.PeerConf{ 91 NeighborAddress: dummies[ciliumLink].ipv4.Addr().String(), 92 PeerAsn: ciliumASN, 93 AuthPassword: "testing-123", 94 }, 95 Transport: &gobgpapi.Transport{ 96 RemoteAddress: dummies[ciliumLink].ipv4.Addr().String(), 97 RemotePort: ciliumListenPort, 98 LocalAddress: dummies[instance1Link].ipv4.Addr().String(), 99 PassiveMode: false, 100 }, 101 AfiSafis: []*gobgpapi.AfiSafi{ 102 { 103 Config: &gobgpapi.AfiSafiConfig{ 104 Family: gobgp.GoBGPIPv4Family, 105 }, 106 }, 107 { 108 Config: &gobgpapi.AfiSafiConfig{ 109 Family: gobgp.GoBGPIPv6Family, 110 }, 111 }, 112 }, 113 } 114 115 gobgpConf = gobgpConfig{ 116 global: gobgpGlobal, 117 neighbors: []*gobgpapi.Peer{ 118 gbgpNeighConf, 119 }, 120 } 121 gobgpConf2 = gobgpConfig{ 122 global: gobgpGlobal2, 123 neighbors: []*gobgpapi.Peer{ 124 gbgpNeighConf2, 125 }, 126 } 127 gobgpConfPassword = gobgpConfig{ 128 global: gobgpGlobal, 129 neighbors: []*gobgpapi.Peer{ 130 gbgpNeighConfPassword, 131 }, 132 } 133 gobgpConfIBGP = gobgpConfig{ 134 global: gobgpGlobalIBGP, 135 neighbors: []*gobgpapi.Peer{ 136 gbgpNeighConf, 137 }, 138 } 139 ) 140 141 // gobgpConfig used for starting gobgp instance 142 type gobgpConfig struct { 143 global *gobgpapi.Global 144 neighbors []*gobgpapi.Peer 145 } 146 147 // routeEvent contains information about new event in routing table of gobgp 148 type routeEvent struct { 149 sourceASN uint32 150 prefix string 151 prefixLen uint8 152 isWithdrawn bool 153 extraPathAttributes []gobgpb.PathAttributeInterface // non-standard path attributes (other than Origin / ASPath / NextHop / MpReachNLRI) 154 } 155 156 // peerEvent contains information about peer state change of gobgp 157 type peerEvent struct { 158 peerASN uint32 159 state string 160 } 161 162 // goBGP wrapper on gobgp server and provides route and peer event handling 163 type goBGP struct { 164 context context.Context 165 166 server *server.BgpServer 167 peerEvents chan *gobgpapi.WatchEventResponse_PeerEvent 168 tableEvents chan *gobgpapi.WatchEventResponse_TableEvent 169 170 peerNotif chan peerEvent 171 routeNotif chan routeEvent 172 } 173 174 // startGoBGP initialized new gobgp server, configures neighbors and starts listening on route and peer events 175 func startGoBGP(ctx context.Context, conf gobgpConfig) (g *goBGP, err error) { 176 g = &goBGP{ 177 context: ctx, 178 server: server.NewBgpServer(server.LoggerOption(gobgp.NewServerLogger(log, gobgp.LogParams{ 179 AS: conf.global.Asn, 180 Component: "tests.BGP", 181 SubSys: "basic", 182 }))), 183 peerEvents: make(chan *gobgpapi.WatchEventResponse_PeerEvent), 184 tableEvents: make(chan *gobgpapi.WatchEventResponse_TableEvent), 185 peerNotif: make(chan peerEvent), 186 routeNotif: make(chan routeEvent), 187 } 188 189 go g.server.Serve() 190 go g.readEvents() 191 192 // in case of err, clean up 193 defer func() { 194 if err != nil { 195 g.server.Stop() 196 } 197 }() 198 199 log.Info("GoBGP test instance: starting") 200 err = g.server.StartBgp(ctx, &gobgpapi.StartBgpRequest{Global: conf.global}) 201 if err != nil { 202 return 203 } 204 205 // register watchers for peer and route events 206 watchRequest := &gobgpapi.WatchEventRequest{ 207 Peer: &gobgpapi.WatchEventRequest_Peer{}, 208 Table: &gobgpapi.WatchEventRequest_Table{ 209 Filters: []*gobgpapi.WatchEventRequest_Table_Filter{ 210 { 211 Type: gobgpapi.WatchEventRequest_Table_Filter_BEST, 212 Init: true, 213 }, 214 }, 215 }, 216 } 217 218 err = g.server.WatchEvent(ctx, watchRequest, func(r *gobgpapi.WatchEventResponse) { 219 switch r.Event.(type) { 220 case *gobgpapi.WatchEventResponse_Peer: 221 g.peerEvents <- r.GetPeer() 222 case *gobgpapi.WatchEventResponse_Table: 223 g.tableEvents <- r.GetTable() 224 } 225 }) 226 if err != nil { 227 return 228 } 229 230 // configure neighbors 231 for _, peer := range conf.neighbors { 232 err = g.server.AddPeer(ctx, &gobgpapi.AddPeerRequest{Peer: peer}) 233 if err != nil { 234 return 235 } 236 } 237 238 return 239 } 240 241 // stopGoBGP stops server 242 func (g *goBGP) stopGoBGP() { 243 log.Infof("GoBGP test instance: stopping") 244 g.server.Stop() 245 } 246 247 // readEvents receives peer and route events from gobgp callbacks, unmarshal response to well-defined structs and 248 // pass this to consumers of peer and route events. 249 // Note this will block if there is no consumer reading, in which case test context would timeout resulting in termination 250 // of this goroutine. 251 func (g *goBGP) readEvents() { 252 for { 253 select { 254 case e := <-g.tableEvents: 255 for _, p := range e.Paths { 256 var prefix string 257 var length uint8 258 259 nlri, err := apiutil.UnmarshalNLRI(gobgpb.AfiSafiToRouteFamily(uint16(p.Family.Afi), uint8(p.Family.Safi)), p.Nlri) 260 if err != nil { 261 log.Errorf("failed to unmarshal path nlri %v: %v", p, err) 262 continue 263 } 264 265 switch a := nlri.(type) { 266 case *gobgpb.IPAddrPrefix: 267 prefix = a.Prefix.String() 268 length = a.Length 269 case *gobgpb.IPv6AddrPrefix: 270 prefix = a.Prefix.String() 271 length = a.Length 272 default: 273 log.Errorf("failed to identify nlri %v", nlri) 274 continue 275 } 276 277 pattrs, err := apiutil.UnmarshalPathAttributes(p.Pattrs) 278 if err != nil { 279 log.Errorf("failed to unmarshal path attributes %v: %v", p, err) 280 continue 281 } 282 283 select { 284 case g.routeNotif <- routeEvent{ 285 sourceASN: p.SourceAsn, 286 prefix: prefix, 287 prefixLen: length, 288 isWithdrawn: p.IsWithdraw, 289 extraPathAttributes: g.filterStandardPathAttributes(pattrs), 290 }: 291 case <-g.context.Done(): 292 return 293 } 294 } 295 296 case e := <-g.peerEvents: 297 if e.Peer != nil { 298 select { 299 case g.peerNotif <- peerEvent{ 300 peerASN: e.Peer.Conf.PeerAsn, 301 state: e.Peer.State.SessionState.String(), 302 }: 303 case <-g.context.Done(): 304 return 305 } 306 } 307 308 case <-g.context.Done(): 309 return 310 } 311 } 312 } 313 314 // waitForSessionState consumes state changes from gobgp and compares it with expected states 315 func (g *goBGP) waitForSessionState(ctx context.Context, expectedStates []string) error { 316 for { 317 select { 318 case e := <-g.peerNotif: 319 log.Infof("GoBGP test instance: Peer Event: %v", e) 320 321 for _, state := range expectedStates { 322 if e.state == state { 323 return nil 324 } 325 } 326 case <-ctx.Done(): 327 return fmt.Errorf("did not receive expected peering state %q: %w", expectedStates, ctx.Err()) 328 } 329 } 330 } 331 332 // getRouteEvents drains number of events from routeNotif chan and return those events to caller. 333 func (g *goBGP) getRouteEvents(ctx context.Context, numExpectedEvents int) ([]routeEvent, error) { 334 var receivedEvents []routeEvent 335 336 for i := 0; i < numExpectedEvents; i++ { 337 select { 338 case r := <-g.routeNotif: 339 log.Infof("GoBGP test instance: Route Event: %v", r) 340 receivedEvents = append(receivedEvents, r) 341 case <-ctx.Done(): 342 return receivedEvents, fmt.Errorf("time elapsed waiting for all route events - received %d, expected %d : %w", 343 len(receivedEvents), numExpectedEvents, ctx.Err()) 344 } 345 } 346 347 return receivedEvents, nil 348 } 349 350 // filterStandardPathAttributes filters standard path attributes (usually present on all routes) from the 351 // provided list of the path attributes. 352 func (g *goBGP) filterStandardPathAttributes(attrs []gobgpb.PathAttributeInterface) []gobgpb.PathAttributeInterface { 353 var res []gobgpb.PathAttributeInterface 354 for _, a := range attrs { 355 switch a.(type) { 356 case *gobgpb.PathAttributeOrigin: 357 continue 358 case *gobgpb.PathAttributeAsPath: 359 continue 360 case *gobgpb.PathAttributeNextHop: 361 continue 362 case *gobgpb.PathAttributeMpReachNLRI: 363 continue 364 } 365 res = append(res, a) 366 } 367 return res 368 }