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 }