github.com/cilium/cilium@v1.16.2/pkg/datapath/loader/loader_test.go (about)

     1  // SPDX-License-Identifier: Apache-2.0
     2  // Copyright Authors of Cilium
     3  
     4  package loader
     5  
     6  import (
     7  	"context"
     8  	"fmt"
     9  	"net/netip"
    10  	"os"
    11  	"path/filepath"
    12  	"testing"
    13  	"time"
    14  
    15  	"github.com/cilium/ebpf/rlimit"
    16  	"github.com/stretchr/testify/assert"
    17  	"github.com/stretchr/testify/require"
    18  	"github.com/vishvananda/netlink"
    19  
    20  	"github.com/cilium/cilium/pkg/bpf"
    21  	"github.com/cilium/cilium/pkg/datapath/loader/metrics"
    22  	"github.com/cilium/cilium/pkg/datapath/tables"
    23  	"github.com/cilium/cilium/pkg/defaults"
    24  	"github.com/cilium/cilium/pkg/maps/callsmap"
    25  	"github.com/cilium/cilium/pkg/option"
    26  	"github.com/cilium/cilium/pkg/testutils"
    27  )
    28  
    29  var (
    30  	contextTimeout = 10 * time.Second
    31  	benchTimeout   = 5*time.Minute + 5*time.Second
    32  
    33  	bpfDir = filepath.Join("..", "..", "..", "bpf")
    34  )
    35  
    36  func initEndpoint(tb testing.TB, ep *testutils.TestEndpoint) {
    37  	testutils.PrivilegedTest(tb)
    38  
    39  	require.Nil(tb, rlimit.RemoveMemlock())
    40  
    41  	ep.State = tb.TempDir()
    42  	for _, iface := range []string{ep.InterfaceName(), defaults.SecondHostDevice} {
    43  		link := netlink.Dummy{
    44  			LinkAttrs: netlink.LinkAttrs{
    45  				Name: iface,
    46  			},
    47  		}
    48  		if err := netlink.LinkAdd(&link); err != nil {
    49  			if !os.IsExist(err) {
    50  				tb.Fatalf("Failed to add link: %s", err)
    51  			}
    52  		}
    53  		tb.Cleanup(func() {
    54  			if err := netlink.LinkDel(&link); err != nil {
    55  				tb.Fatalf("Failed to delete link: %s", err)
    56  			}
    57  		})
    58  	}
    59  
    60  	tb.Cleanup(func() {
    61  		files, err := filepath.Glob("/sys/fs/bpf/tc/globals/test_*")
    62  		require.Nil(tb, err)
    63  		for _, f := range files {
    64  			assert.Nil(tb, os.Remove(f))
    65  		}
    66  	})
    67  }
    68  
    69  func getDirs(tb testing.TB) *directoryInfo {
    70  	return &directoryInfo{
    71  		Library: bpfDir,
    72  		Runtime: bpfDir,
    73  		State:   bpfDir,
    74  		Output:  tb.TempDir(),
    75  	}
    76  }
    77  
    78  func getEpDirs(ep *testutils.TestEndpoint) *directoryInfo {
    79  	return &directoryInfo{
    80  		Library: bpfDir,
    81  		Runtime: bpfDir,
    82  		State:   ep.StateDir(),
    83  		Output:  ep.StateDir(),
    84  	}
    85  }
    86  
    87  func testReloadDatapath(t *testing.T, ep *testutils.TestEndpoint) {
    88  	ctx, cancel := context.WithTimeout(context.Background(), contextTimeout)
    89  	defer cancel()
    90  	stats := &metrics.SpanStat{}
    91  
    92  	l := newTestLoader(t)
    93  	_, err := l.ReloadDatapath(ctx, ep, stats)
    94  	require.NoError(t, err)
    95  }
    96  
    97  // TestCompileOrLoadDefaultEndpoint checks that the datapath can be compiled
    98  // and loaded.
    99  func TestCompileOrLoadDefaultEndpoint(t *testing.T) {
   100  	ep := testutils.NewTestEndpoint()
   101  	initEndpoint(t, &ep)
   102  	testReloadDatapath(t, &ep)
   103  }
   104  
   105  // TestCompileOrLoadHostEndpoint is the same as
   106  // TestCompileAndLoadDefaultEndpoint, but for the host endpoint.
   107  func TestCompileOrLoadHostEndpoint(t *testing.T) {
   108  
   109  	callsmap.HostMapName = fmt.Sprintf("test_%s", callsmap.MapName)
   110  	callsmap.NetdevMapName = fmt.Sprintf("test_%s", callsmap.MapName)
   111  
   112  	hostEp := testutils.NewTestHostEndpoint()
   113  	initEndpoint(t, &hostEp)
   114  
   115  	testReloadDatapath(t, &hostEp)
   116  }
   117  
   118  // TestReload compiles and attaches the datapath.
   119  func TestReload(t *testing.T) {
   120  	ctx, cancel := context.WithTimeout(context.Background(), contextTimeout)
   121  	defer cancel()
   122  
   123  	ep := testutils.NewTestEndpoint()
   124  	initEndpoint(t, &ep)
   125  
   126  	dirInfo := getEpDirs(&ep)
   127  	err := compileDatapath(ctx, dirInfo, false, log)
   128  	require.NoError(t, err)
   129  
   130  	l, err := netlink.LinkByName(ep.InterfaceName())
   131  	require.NoError(t, err)
   132  
   133  	objPath := fmt.Sprintf("%s/%s", dirInfo.Output, endpointObj)
   134  	linkDir := testutils.TempBPFFS(t)
   135  
   136  	for range 2 {
   137  		spec, err := bpf.LoadCollectionSpec(objPath)
   138  		require.NoError(t, err)
   139  
   140  		coll, commit, err := loadDatapath(spec, nil, nil)
   141  		require.NoError(t, err)
   142  
   143  		require.NoError(t, attachSKBProgram(l, coll.Programs[symbolFromEndpoint],
   144  			symbolFromEndpoint, linkDir, netlink.HANDLE_MIN_INGRESS, true))
   145  		require.NoError(t, attachSKBProgram(l, coll.Programs[symbolToEndpoint],
   146  			symbolToEndpoint, linkDir, netlink.HANDLE_MIN_EGRESS, true))
   147  
   148  		require.NoError(t, commit())
   149  
   150  		coll.Close()
   151  	}
   152  }
   153  
   154  func testCompileFailure(t *testing.T, ep *testutils.TestEndpoint) {
   155  	ctx, cancel := context.WithTimeout(context.Background(), contextTimeout)
   156  	defer cancel()
   157  
   158  	exit := make(chan struct{})
   159  	defer close(exit)
   160  	go func() {
   161  		select {
   162  		case <-time.After(100 * time.Millisecond):
   163  			cancel()
   164  		case <-exit:
   165  			break
   166  		}
   167  	}()
   168  
   169  	l := newTestLoader(t)
   170  	timeout := time.Now().Add(contextTimeout)
   171  	var err error
   172  	stats := &metrics.SpanStat{}
   173  	for err == nil && time.Now().Before(timeout) {
   174  		_, err = l.ReloadDatapath(ctx, ep, stats)
   175  	}
   176  	require.Error(t, err)
   177  }
   178  
   179  // TestCompileFailureDefaultEndpoint attempts to compile then cancels the
   180  // context and ensures that the failure paths may be hit.
   181  func TestCompileFailureDefaultEndpoint(t *testing.T) {
   182  	ep := testutils.NewTestEndpoint()
   183  	initEndpoint(t, &ep)
   184  	testCompileFailure(t, &ep)
   185  }
   186  
   187  // TestCompileFailureHostEndpoint is the same as
   188  // TestCompileFailureDefaultEndpoint, but for the host endpoint.
   189  func TestCompileFailureHostEndpoint(t *testing.T) {
   190  	hostEp := testutils.NewTestHostEndpoint()
   191  	initEndpoint(t, &hostEp)
   192  	testCompileFailure(t, &hostEp)
   193  }
   194  
   195  func TestBPFMasqAddrs(t *testing.T) {
   196  	old4 := option.Config.EnableIPv4Masquerade
   197  	option.Config.EnableIPv4Masquerade = true
   198  	old6 := option.Config.EnableIPv4Masquerade
   199  	option.Config.EnableIPv6Masquerade = true
   200  	t.Cleanup(func() {
   201  		option.Config.EnableIPv4Masquerade = old4
   202  		option.Config.EnableIPv6Masquerade = old6
   203  	})
   204  
   205  	l := newTestLoader(t)
   206  
   207  	masq4, masq6 := l.bpfMasqAddrs("test")
   208  	require.Equal(t, masq4.IsValid(), false)
   209  	require.Equal(t, masq6.IsValid(), false)
   210  
   211  	newConfig := *l.nodeConfig.Load()
   212  
   213  	newConfig.NodeAddresses = []tables.NodeAddress{
   214  		{
   215  			Addr:       netip.MustParseAddr("1.0.0.1"),
   216  			NodePort:   true,
   217  			Primary:    true,
   218  			DeviceName: "test",
   219  		},
   220  		{
   221  			Addr:       netip.MustParseAddr("1000::1"),
   222  			NodePort:   true,
   223  			Primary:    true,
   224  			DeviceName: "test",
   225  		},
   226  		{
   227  			Addr:       netip.MustParseAddr("2.0.0.2"),
   228  			NodePort:   false,
   229  			Primary:    true,
   230  			DeviceName: tables.WildcardDeviceName,
   231  		},
   232  		{
   233  			Addr:       netip.MustParseAddr("2000::2"),
   234  			NodePort:   false,
   235  			Primary:    true,
   236  			DeviceName: tables.WildcardDeviceName,
   237  		},
   238  	}
   239  	l.nodeConfig.Store(&newConfig)
   240  
   241  	masq4, masq6 = l.bpfMasqAddrs("test")
   242  	require.Equal(t, masq4.String(), "1.0.0.1")
   243  	require.Equal(t, masq6.String(), "1000::1")
   244  
   245  	masq4, masq6 = l.bpfMasqAddrs("unknown")
   246  	require.Equal(t, masq4.String(), "2.0.0.2")
   247  	require.Equal(t, masq6.String(), "2000::2")
   248  }
   249  
   250  // BenchmarkCompileOnly benchmarks the just the entire compilation process.
   251  func BenchmarkCompileOnly(b *testing.B) {
   252  	ctx, cancel := context.WithTimeout(context.Background(), benchTimeout)
   253  	defer cancel()
   254  
   255  	dirInfo := getDirs(b)
   256  	option.Config.Debug = true
   257  
   258  	b.ResetTimer()
   259  	for i := 0; i < b.N; i++ {
   260  		if err := compileDatapath(ctx, dirInfo, false, log); err != nil {
   261  			b.Fatal(err)
   262  		}
   263  	}
   264  }
   265  
   266  // BenchmarkReplaceDatapath compiles the datapath program, then benchmarks only
   267  // the loading of the program into the kernel.
   268  func BenchmarkReplaceDatapath(b *testing.B) {
   269  	ctx, cancel := context.WithTimeout(context.Background(), benchTimeout)
   270  	defer cancel()
   271  
   272  	ep := testutils.NewTestEndpoint()
   273  	initEndpoint(b, &ep)
   274  
   275  	dirInfo := getEpDirs(&ep)
   276  
   277  	if err := compileDatapath(ctx, dirInfo, false, log); err != nil {
   278  		b.Fatal(err)
   279  	}
   280  
   281  	objPath := fmt.Sprintf("%s/%s", dirInfo.Output, endpointObj)
   282  	b.ResetTimer()
   283  	for i := 0; i < b.N; i++ {
   284  		spec, err := bpf.LoadCollectionSpec(objPath)
   285  		if err != nil {
   286  			b.Fatal(err)
   287  		}
   288  
   289  		coll, commit, err := loadDatapath(spec, nil, nil)
   290  		if err != nil {
   291  			b.Fatal(err)
   292  		}
   293  		if err := commit(); err != nil {
   294  			b.Fatalf("committing bpf pins: %s", err)
   295  		}
   296  		coll.Close()
   297  	}
   298  }