github.com/dylandreimerink/gobpfld@v0.6.1-0.20220205171531-e79c330ad608/cmd/testsuite/integration/xsk_test.go (about)

     1  //go:build bpftests
     2  // +build bpftests
     3  
     4  package integration
     5  
     6  import (
     7  	"fmt"
     8  	"net"
     9  	"strings"
    10  	"testing"
    11  	"time"
    12  
    13  	"github.com/dylandreimerink/gobpfld"
    14  	"github.com/dylandreimerink/gobpfld/bpftypes"
    15  	ebpfPkg "github.com/dylandreimerink/gobpfld/ebpf"
    16  	"github.com/dylandreimerink/gobpfld/kernelsupport"
    17  	"github.com/vishvananda/netlink"
    18  )
    19  
    20  var (
    21  	ifname = "lo"
    22  	ip     = "127.0.0.2"
    23  
    24  	arpCount int
    25  	ipCount  int
    26  )
    27  
    28  func TestIntegrationXSK(t *testing.T) {
    29  	if !kernelsupport.CurrentFeatures.Map.Has(kernelsupport.KFeatMapAFXDP) {
    30  		t.Skip("XSK not supported in by the kernel")
    31  	}
    32  
    33  	linkName := ifname
    34  	link, err := netlink.LinkByName(linkName)
    35  	if err != nil {
    36  		t.Fatalf("get link by name: %s\n", err.Error())
    37  	}
    38  
    39  	// Create an new XSK socket, bound to queue 0.
    40  	// NOTE this example only works on non-multi queue NIC's
    41  	xsksock, err := gobpfld.NewXSKSocket(gobpfld.XSKSettings{
    42  		NetDevIfIndex: link.Attrs().Index,
    43  		QueueID:       0,
    44  	})
    45  	if err != nil {
    46  		t.Fatalf("new socket: %s\n", err.Error())
    47  	}
    48  	defer func() {
    49  		err = xsksock.Close()
    50  		if err != nil {
    51  			t.Log(err)
    52  			t.Fail()
    53  		}
    54  	}()
    55  
    56  	// Block forever until we can read or write (default behavour is to never block)
    57  	xsksock.SetReadTimeout(-1)
    58  	xsksock.SetWriteTimeout(-1)
    59  
    60  	// Generate a program which will bypass all traffic to userspace
    61  	program := &gobpfld.ProgramXDP{
    62  		AbstractBPFProgram: gobpfld.AbstractBPFProgram{
    63  			Name:        gobpfld.MustNewObjName("xsk_bypass"),
    64  			ProgramType: bpftypes.BPF_PROG_TYPE_XDP,
    65  			License:     "GPL",
    66  			Maps: map[string]gobpfld.BPFMap{
    67  				"xskmap": &gobpfld.XSKMap{
    68  					AbstractMap: gobpfld.AbstractMap{
    69  						Name: gobpfld.MustNewObjName("xskmap"),
    70  						Definition: gobpfld.BPFMapDef{
    71  							Type:       bpftypes.BPF_MAP_TYPE_XSKMAP,
    72  							KeySize:    4, // SizeOf(uint32)
    73  							ValueSize:  4, // SizeOf(uint32)
    74  							MaxEntries: 5,
    75  						},
    76  					},
    77  				},
    78  			},
    79  			MapFDLocations: map[string][]uint64{
    80  				"xskmap": {
    81  					// LoadConstant64bit is the 2nd instruction in this program. So the first byte of the
    82  					// 2nd instruction is the width of a instruction * 1 to skip the first 1 instruction
    83  					uint64(ebpfPkg.BPFInstSize) * 1,
    84  				},
    85  			},
    86  			// Instructions for this program:
    87  			// int xsk_bypass(struct xdp_md *ctx)
    88  			// {
    89  			// 	return bpf_redirect_map(&xsks_map, ctx->rx_queue_index, XDP_PASS);
    90  			// }
    91  			//
    92  			// NOTE this program only works in linux kernel >= 5.3
    93  			// https://elixir.bootlin.com/linux/v5.12.2/source/tools/lib/bpf/xsk.c#L416
    94  			Instructions: ebpfPkg.MustEncode([]ebpfPkg.Instruction{
    95  				// load ((xdp_md) ctx)->rx_queue_index into R2 (used as 2nd parameter)
    96  				/* r2 = *(u32 *)(r1 + 16) */
    97  				&ebpfPkg.LoadMemory{
    98  					Dest:   ebpfPkg.BPF_REG_2,
    99  					Src:    ebpfPkg.BPF_REG_1,
   100  					Size:   ebpfPkg.BPF_W,
   101  					Offset: 16,
   102  				},
   103  				// Set R1(first parameter) to the address of the xskmap.
   104  				// Which will be set during loading
   105  				/* r1 = xskmap[] */
   106  				&ebpfPkg.LoadConstant64bit{
   107  					Dest: ebpfPkg.BPF_REG_1,
   108  				},
   109  				// Move XDP_PASS into R3 (third argument)
   110  				/* r3 = XDP_PASS */
   111  				&ebpfPkg.Mov64{
   112  					Dest:  ebpfPkg.BPF_REG_3,
   113  					Value: ebpfPkg.XDP_PASS,
   114  				},
   115  				/* call bpf_redirect_map */
   116  				&ebpfPkg.CallHelper{
   117  					Function: 51,
   118  				},
   119  				&ebpfPkg.Exit{}, // exit
   120  			}),
   121  		},
   122  	}
   123  
   124  	xskmap := program.Maps["xskmap"].(*gobpfld.XSKMap)
   125  	defer func() {
   126  		err = xskmap.Close()
   127  		if err != nil {
   128  			t.Log(xskmap)
   129  			t.Fail()
   130  		}
   131  	}()
   132  
   133  	log, err := program.Load(gobpfld.ProgXDPLoadOpts{
   134  		VerifierLogLevel: bpftypes.BPFLogLevelBasic,
   135  	})
   136  
   137  	if err != nil {
   138  		var buf strings.Builder
   139  		program.DecodeToReader(&buf)
   140  		t.Log(buf.String())
   141  		t.Log(log)
   142  		t.Fatalf("error while loading program: %s\n", err.Error())
   143  	}
   144  
   145  	// Set the xsksocket for queue ID 0
   146  	err = xskmap.Set(0, xsksock)
   147  	if err != nil {
   148  		t.Fatalf("error while setting xsksock in map: %s\n", err.Error())
   149  	}
   150  
   151  	err = program.Attach(gobpfld.ProgXDPAttachOpts{
   152  		InterfaceName: linkName,
   153  		Replace:       true,
   154  	})
   155  	if err != nil {
   156  		t.Fatalf("error while attaching program to loopback device: %s\n", err.Error())
   157  	}
   158  	defer func() {
   159  		program.XDPLinkDetach(gobpfld.BPFProgramXDPLinkDetachSettings{
   160  			All: true,
   161  		})
   162  	}()
   163  
   164  	// Read frames from XSK socket and increment counter
   165  	go func() {
   166  		for {
   167  			lease, err := xsksock.ReadLease()
   168  			if err != nil {
   169  				t.Logf("read lease: %s\n", err.Error())
   170  				t.Fail()
   171  				break
   172  			}
   173  
   174  			if lease == nil {
   175  				break
   176  			}
   177  
   178  			err = HandleFrame(lease)
   179  			if err != nil {
   180  				t.Logf("handle frame: %s\n", err.Error())
   181  				t.Fail()
   182  			}
   183  		}
   184  	}()
   185  
   186  	udpConn, err := net.Dial("udp", "127.0.0.1:123")
   187  	if err != nil {
   188  		t.Fatal(err)
   189  	}
   190  
   191  	// Write some bogus UDP packets to localhost to generate traffic
   192  	ticker := time.Tick(1 * time.Second)
   193  	endTimer := time.NewTimer(5 * time.Second)
   194  	stop := false
   195  	for !stop {
   196  		select {
   197  		case <-ticker:
   198  			udpConn.Write([]byte("Hello World"))
   199  
   200  		case <-endTimer.C:
   201  			stop = true
   202  		}
   203  	}
   204  
   205  	if ipCount == 0 && arpCount == 0 {
   206  		t.Fatal("no frames received")
   207  	}
   208  }
   209  
   210  func HandleFrame(lease *gobpfld.XSKLease) error {
   211  	var err error
   212  	defer func() {
   213  		err = lease.Release()
   214  		if err != nil {
   215  			err = fmt.Errorf("release lease: %w", err)
   216  		}
   217  	}()
   218  
   219  	// If EtherType == 0x0806(ARP)
   220  	if lease.Data[12] == 0x08 && lease.Data[13] == 0x06 {
   221  		arpCount++
   222  	}
   223  
   224  	// If EtherType == 0x0800(IPv4)
   225  	if lease.Data[12] == 0x08 && lease.Data[13] == 0x00 {
   226  		ipCount++
   227  	}
   228  
   229  	return err
   230  }