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  }