github.com/cilium/cilium@v1.16.2/pkg/bgpv1/gobgp/peer_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  	"fmt"
     9  	"net/netip"
    10  	"testing"
    11  
    12  	gobgp "github.com/osrg/gobgp/v3/api"
    13  	"github.com/stretchr/testify/require"
    14  	"k8s.io/utils/ptr"
    15  
    16  	"github.com/cilium/cilium/pkg/bgpv1/types"
    17  	"github.com/cilium/cilium/pkg/k8s/apis/cilium.io/v2alpha1"
    18  )
    19  
    20  var (
    21  	defaultConf = neighborConf{
    22  		address: "1.2.3.4",
    23  		port:    ptr.To[int32](179),
    24  		asn:     65001,
    25  	}
    26  
    27  	invalidIPConf = neighborConf{
    28  		address: "1.2.3.x",
    29  		port:    ptr.To[int32](179),
    30  		asn:     65001,
    31  	}
    32  
    33  	afiConf = func() neighborConf {
    34  		d := defaultConf.DeepCopy()
    35  		d.families = []v2alpha1.CiliumBGPFamily{
    36  			{
    37  				Afi:  "l2vpn",
    38  				Safi: "multicast",
    39  			},
    40  		}
    41  		return d
    42  	}
    43  
    44  	invalidAfiConf = func() neighborConf {
    45  		d := defaultConf.DeepCopy()
    46  		d.families = []v2alpha1.CiliumBGPFamily{
    47  			{
    48  				Afi:  "foo",
    49  				Safi: "bar",
    50  			},
    51  		}
    52  		return d
    53  	}
    54  
    55  	multiHopConf = func() neighborConf {
    56  		d := defaultConf.DeepCopy()
    57  		d.multihop = ptr.To[int32](3)
    58  		return d
    59  	}
    60  
    61  	timersConf = func() neighborConf {
    62  		d := defaultConf.DeepCopy()
    63  		d.timers = &timersConfig{
    64  			connect:   60,
    65  			hold:      30,
    66  			keepalive: 15,
    67  		}
    68  		return d
    69  	}
    70  
    71  	restartConf = func() neighborConf {
    72  		d := defaultConf.DeepCopy()
    73  		d.restart = &restartConfig{
    74  			enabled: true,
    75  			time:    ptr.To[int32](90),
    76  		}
    77  		return d
    78  	}
    79  )
    80  
    81  type restartConfig struct {
    82  	enabled bool
    83  	time    *int32
    84  }
    85  
    86  type timersConfig struct {
    87  	connect   int32
    88  	hold      int32
    89  	keepalive int32
    90  }
    91  
    92  type neighborConf struct {
    93  	address  string
    94  	port     *int32
    95  	asn      int64
    96  	families []v2alpha1.CiliumBGPFamily
    97  	multihop *int32
    98  	timers   *timersConfig
    99  	restart  *restartConfig
   100  }
   101  
   102  func (n neighborConf) DeepCopy() neighborConf {
   103  	neighCopy := neighborConf{
   104  		address:  n.address,
   105  		port:     ptr.To[int32](*n.port),
   106  		asn:      n.asn,
   107  		multihop: n.multihop,
   108  	}
   109  
   110  	neighCopy.families = make([]v2alpha1.CiliumBGPFamily, len(n.families))
   111  	for i, f := range n.families {
   112  		neighCopy.families[i] = *f.DeepCopy()
   113  	}
   114  
   115  	if n.timers != nil {
   116  		neighCopy.timers = &timersConfig{
   117  			connect:   n.timers.connect,
   118  			hold:      n.timers.hold,
   119  			keepalive: n.timers.keepalive,
   120  		}
   121  	}
   122  
   123  	if n.restart != nil {
   124  		neighCopy.restart = &restartConfig{
   125  			enabled: n.restart.enabled,
   126  			time:    ptr.To[int32](*n.restart.time),
   127  		}
   128  	}
   129  
   130  	return neighCopy
   131  }
   132  
   133  func neighborFromTestConf(c neighborConf) *v2alpha1.CiliumBGPNeighbor {
   134  	n := &v2alpha1.CiliumBGPNeighbor{
   135  		PeerAddress:              fmt.Sprintf("%s/32", c.address),
   136  		PeerPort:                 ptr.To[int32](179),
   137  		PeerASN:                  c.asn,
   138  		EBGPMultihopTTL:          c.multihop,
   139  		GracefulRestart:          &v2alpha1.CiliumBGPNeighborGracefulRestart{},
   140  		Families:                 c.families,
   141  		AdvertisedPathAttributes: []v2alpha1.CiliumBGPPathAttributes{},
   142  		ConnectRetryTimeSeconds:  ptr.To[int32](120),
   143  		HoldTimeSeconds:          ptr.To[int32](90),
   144  		KeepAliveTimeSeconds:     ptr.To[int32](30),
   145  	}
   146  
   147  	if c.port != nil {
   148  		n.PeerPort = c.port
   149  	}
   150  
   151  	if c.timers != nil {
   152  		n.ConnectRetryTimeSeconds = &c.timers.connect
   153  		n.HoldTimeSeconds = &c.timers.hold
   154  		n.KeepAliveTimeSeconds = &c.timers.keepalive
   155  	}
   156  
   157  	if c.restart != nil {
   158  		n.GracefulRestart.Enabled = c.restart.enabled
   159  		n.GracefulRestart.RestartTimeSeconds = c.restart.time
   160  	}
   161  
   162  	return n
   163  }
   164  
   165  func bgpNodePeerFromTestConf(c neighborConf) *v2alpha1.CiliumBGPNodePeer {
   166  	p := &v2alpha1.CiliumBGPNodePeer{
   167  		Name:        "peer-1",
   168  		PeerAddress: ptr.To[string](c.address),
   169  		PeerASN:     ptr.To[int64](c.asn),
   170  	}
   171  
   172  	return p
   173  }
   174  
   175  func bgpPeerConfigFromTestConf(c neighborConf) *v2alpha1.CiliumBGPPeerConfigSpec {
   176  	p := &v2alpha1.CiliumBGPPeerConfigSpec{}
   177  	p.SetDefaults()
   178  
   179  	p.Families = []v2alpha1.CiliumBGPFamilyWithAdverts{}
   180  	for _, fam := range c.families {
   181  		p.Families = append(p.Families, v2alpha1.CiliumBGPFamilyWithAdverts{
   182  			CiliumBGPFamily: fam,
   183  		})
   184  	}
   185  
   186  	if c.multihop != nil {
   187  		p.EBGPMultihop = c.multihop
   188  	}
   189  
   190  	if c.port != nil {
   191  		p.Transport.PeerPort = c.port
   192  	}
   193  
   194  	if c.timers != nil {
   195  		p.Timers.ConnectRetryTimeSeconds = &c.timers.connect
   196  		p.Timers.HoldTimeSeconds = &c.timers.hold
   197  		p.Timers.KeepAliveTimeSeconds = &c.timers.keepalive
   198  	}
   199  
   200  	if c.restart != nil {
   201  		p.GracefulRestart.Enabled = c.restart.enabled
   202  		p.GracefulRestart.RestartTimeSeconds = c.restart.time
   203  	}
   204  
   205  	return p
   206  }
   207  
   208  func gobgpPeerFromTestConf(c neighborConf) *gobgp.Peer {
   209  	p := &gobgp.Peer{
   210  		Conf: &gobgp.PeerConf{
   211  			NeighborAddress: c.address,
   212  			PeerAsn:         uint32(c.asn),
   213  		},
   214  		Transport: &gobgp.Transport{
   215  			RemotePort: uint32(*c.port),
   216  		},
   217  		Timers:          &gobgp.Timers{},
   218  		GracefulRestart: &gobgp.GracefulRestart{},
   219  	}
   220  
   221  	addr, err := netip.ParseAddr(c.address)
   222  	if err != nil {
   223  		return nil
   224  	}
   225  	if addr.Is4() {
   226  		p.Transport.LocalAddress = wildcardIPv4Addr
   227  	} else {
   228  		p.Transport.LocalAddress = wildcardIPv6Addr
   229  	}
   230  
   231  	p.AfiSafis, err = convertBGPNeighborSAFI(c.families)
   232  	if err != nil {
   233  		return nil
   234  	}
   235  
   236  	if testServerParameters.Global.ASN != uint32(c.asn) && c.multihop != nil && *c.multihop > 1 {
   237  		p.EbgpMultihop = &gobgp.EbgpMultihop{
   238  			Enabled:     true,
   239  			MultihopTtl: uint32(*c.multihop),
   240  		}
   241  	}
   242  
   243  	if c.timers != nil {
   244  		p.Timers.Config = &gobgp.TimersConfig{
   245  			ConnectRetry:      uint64(c.timers.connect),
   246  			HoldTime:          uint64(c.timers.hold),
   247  			KeepaliveInterval: uint64(c.timers.keepalive),
   248  		}
   249  	} else {
   250  		p.Timers.Config = &gobgp.TimersConfig{
   251  			ConnectRetry:      uint64(120),
   252  			HoldTime:          uint64(90),
   253  			KeepaliveInterval: uint64(30),
   254  		}
   255  	}
   256  	p.Timers.Config.IdleHoldTimeAfterReset = idleHoldTimeAfterResetSeconds
   257  
   258  	if c.restart != nil {
   259  		p.GracefulRestart.Enabled = true
   260  		p.GracefulRestart.NotificationEnabled = true
   261  		if c.restart.time != nil {
   262  			p.GracefulRestart.RestartTime = uint32(*c.restart.time)
   263  		} else {
   264  			p.GracefulRestart.RestartTime = uint32(120)
   265  		}
   266  	}
   267  
   268  	return p
   269  }
   270  
   271  func TestGetPeerConfigV1(t *testing.T) {
   272  	table := []struct {
   273  		name     string
   274  		neighbor *v2alpha1.CiliumBGPNeighbor
   275  		expected *gobgp.Peer
   276  		expect   bool
   277  	}{
   278  		{
   279  			name:     "test nil neighbor",
   280  			neighbor: nil,
   281  			expected: nil,
   282  			expect:   false,
   283  		},
   284  		{
   285  			name:     "test default neighbor config",
   286  			neighbor: neighborFromTestConf(defaultConf),
   287  			expected: gobgpPeerFromTestConf(defaultConf),
   288  			expect:   true,
   289  		},
   290  		{
   291  			name:     "test default invalid IP neighbor config",
   292  			neighbor: neighborFromTestConf(invalidIPConf),
   293  			expected: gobgpPeerFromTestConf(invalidIPConf),
   294  			expect:   false,
   295  		},
   296  		{
   297  			name:     "test neighbor afi safi config",
   298  			neighbor: neighborFromTestConf(afiConf()),
   299  			expected: gobgpPeerFromTestConf(afiConf()),
   300  			expect:   true,
   301  		},
   302  		{
   303  			name:     "test neighbor invalid afi safi config",
   304  			neighbor: neighborFromTestConf(invalidAfiConf()),
   305  			expected: gobgpPeerFromTestConf(invalidAfiConf()),
   306  			expect:   false,
   307  		},
   308  		{
   309  			name:     "test neighbor ebgp multihop ttl config",
   310  			neighbor: neighborFromTestConf(multiHopConf()),
   311  			expected: gobgpPeerFromTestConf(multiHopConf()),
   312  			expect:   true,
   313  		},
   314  		{
   315  			name:     "test neighbor ebgp multihop timers config",
   316  			neighbor: neighborFromTestConf(timersConf()),
   317  			expected: gobgpPeerFromTestConf(timersConf()),
   318  			expect:   true,
   319  		},
   320  		{
   321  			name:     "test neighbor graceful restart timers config",
   322  			neighbor: neighborFromTestConf(restartConf()),
   323  			expected: gobgpPeerFromTestConf(restartConf()),
   324  			expect:   true,
   325  		},
   326  	}
   327  	for _, tt := range table {
   328  		t.Run(tt.name, func(t *testing.T) {
   329  			svr, err := NewGoBGPServer(context.Background(), log, testServerParameters)
   330  			require.NoError(t, err)
   331  
   332  			t.Cleanup(func() {
   333  				svr.Stop()
   334  			})
   335  
   336  			req := types.NeighborRequest{
   337  				Neighbor: tt.neighbor,
   338  			}
   339  
   340  			peer, reset, err := svr.(*GoBGPServer).getPeerConfig(context.Background(), req, false)
   341  			if tt.expect {
   342  				require.NoError(t, err)
   343  				require.Equal(t, tt.expected.Conf, peer.Conf)
   344  				require.Equal(t, tt.expected.Transport, peer.Transport)
   345  				require.Equal(t, tt.expected.Timers, peer.Timers)
   346  				require.Equal(t, tt.expected.EbgpMultihop, peer.EbgpMultihop)
   347  				require.Equal(t, reset, false)
   348  				if len(tt.expected.AfiSafis) > 0 {
   349  					for i, safi := range tt.expected.AfiSafis {
   350  						require.Equal(t, safi.Config, peer.AfiSafis[i].Config)
   351  					}
   352  				}
   353  			} else {
   354  				require.Error(t, err)
   355  			}
   356  		})
   357  	}
   358  }
   359  
   360  func TestGetPeerConfigV2(t *testing.T) {
   361  	table := []struct {
   362  		name       string
   363  		peer       *v2alpha1.CiliumBGPNodePeer
   364  		peerConfig *v2alpha1.CiliumBGPPeerConfigSpec
   365  		expected   *gobgp.Peer
   366  		expect     bool
   367  	}{
   368  		{
   369  			name:       "test nil peer and config",
   370  			peer:       nil,
   371  			peerConfig: nil,
   372  			expected:   nil,
   373  			expect:     false,
   374  		},
   375  		{
   376  			name:       "test default neighbor config",
   377  			peer:       bgpNodePeerFromTestConf(defaultConf),
   378  			peerConfig: bgpPeerConfigFromTestConf(defaultConf),
   379  			expected:   gobgpPeerFromTestConf(defaultConf),
   380  			expect:     true,
   381  		},
   382  		{
   383  			name:       "test default invalid IP neighbor config",
   384  			peer:       bgpNodePeerFromTestConf(invalidIPConf),
   385  			peerConfig: bgpPeerConfigFromTestConf(invalidIPConf),
   386  			expected:   gobgpPeerFromTestConf(invalidIPConf),
   387  			expect:     false,
   388  		},
   389  		{
   390  			name:       "test neighbor afi safi config",
   391  			peer:       bgpNodePeerFromTestConf(afiConf()),
   392  			peerConfig: bgpPeerConfigFromTestConf(afiConf()),
   393  			expected:   gobgpPeerFromTestConf(afiConf()),
   394  			expect:     true,
   395  		},
   396  		{
   397  			name:       "test neighbor invalid afi safi config",
   398  			peer:       bgpNodePeerFromTestConf(invalidAfiConf()),
   399  			peerConfig: bgpPeerConfigFromTestConf(invalidAfiConf()),
   400  			expected:   gobgpPeerFromTestConf(invalidAfiConf()),
   401  			expect:     false,
   402  		},
   403  		{
   404  			name:       "test neighbor ebgp multihop ttl config",
   405  			peer:       bgpNodePeerFromTestConf(multiHopConf()),
   406  			peerConfig: bgpPeerConfigFromTestConf(multiHopConf()),
   407  			expected:   gobgpPeerFromTestConf(multiHopConf()),
   408  			expect:     true,
   409  		},
   410  		{
   411  			name:       "test neighbor ebgp multihop timers config",
   412  			peer:       bgpNodePeerFromTestConf(timersConf()),
   413  			peerConfig: bgpPeerConfigFromTestConf(timersConf()),
   414  			expected:   gobgpPeerFromTestConf(timersConf()),
   415  			expect:     true,
   416  		},
   417  		{
   418  			name:       "test neighbor graceful restart timers config",
   419  			peer:       bgpNodePeerFromTestConf(restartConf()),
   420  			peerConfig: bgpPeerConfigFromTestConf(restartConf()),
   421  			expected:   gobgpPeerFromTestConf(restartConf()),
   422  			expect:     true,
   423  		},
   424  	}
   425  	for _, tt := range table {
   426  		t.Run(tt.name, func(t *testing.T) {
   427  			svr, err := NewGoBGPServer(context.Background(), log, testServerParameters)
   428  			require.NoError(t, err)
   429  
   430  			t.Cleanup(func() {
   431  				svr.Stop()
   432  			})
   433  
   434  			req := types.NeighborRequest{
   435  				Peer:       tt.peer,
   436  				PeerConfig: tt.peerConfig,
   437  			}
   438  
   439  			peer, reset, err := svr.(*GoBGPServer).getPeerConfig(context.Background(), req, false)
   440  			if tt.expect {
   441  				require.NoError(t, err)
   442  				require.Equal(t, tt.expected.Conf, peer.Conf)
   443  				require.Equal(t, tt.expected.Transport, peer.Transport)
   444  				require.Equal(t, tt.expected.Timers, peer.Timers)
   445  				require.Equal(t, tt.expected.EbgpMultihop, peer.EbgpMultihop)
   446  				require.Equal(t, reset, false)
   447  				if len(tt.expected.AfiSafis) > 0 {
   448  					for i, safi := range tt.expected.AfiSafis {
   449  						require.Equal(t, safi.Config, peer.AfiSafis[i].Config)
   450  					}
   451  				}
   452  			} else {
   453  				require.Error(t, err)
   454  			}
   455  		})
   456  	}
   457  }