github.com/dylandreimerink/gobpfld@v0.6.1-0.20220205171531-e79c330ad608/cmd/examples/xsk_echo_reply/main.go (about) 1 package main 2 3 import ( 4 "flag" 5 "fmt" 6 "net" 7 "os" 8 "os/signal" 9 10 "github.com/dylandreimerink/gobpfld" 11 "github.com/dylandreimerink/gobpfld/bpftypes" 12 "github.com/dylandreimerink/gobpfld/ebpf" 13 "github.com/vishvananda/netlink" 14 "golang.org/x/sys/unix" 15 16 _ "net/http/pprof" 17 ) 18 19 var ( 20 ifname = flag.String("ifname", "eth0", "name of the network interface to bind to") 21 ip = flag.String("ip", "", "the ipv4 ip we will use as ping target") 22 ) 23 24 func main() { 25 flag.Parse() 26 27 if ifname == nil || *ifname == "" { 28 fmt.Fprint(os.Stderr, "flag 'ifname' is required\n") 29 os.Exit(1) 30 } 31 32 if ip == nil || *ip == "" { 33 fmt.Fprint(os.Stderr, "flag 'ip' is required\n") 34 os.Exit(1) 35 } 36 37 linkip := net.ParseIP(*ip).To4() 38 if linkip.Equal(net.IPv4zero) { 39 fmt.Fprint(os.Stderr, "flag 'ip' contains an invalid IPv4\n") 40 os.Exit(1) 41 } 42 43 linkName := *ifname 44 link, err := netlink.LinkByName(linkName) 45 if err != nil { 46 fmt.Fprintf(os.Stderr, "get link by name: %s\n", err.Error()) 47 os.Exit(1) 48 } 49 linkMAC := link.Attrs().HardwareAddr 50 51 // Create an new XSK socket, bound to queue 0. 52 // NOTE this example only works on non-multi queue NIC's 53 xsksock, err := gobpfld.NewXSKSocket(gobpfld.XSKSettings{ 54 NetDevIfIndex: link.Attrs().Index, 55 QueueID: 0, 56 }) 57 if err != nil { 58 fmt.Fprintf(os.Stderr, "new socket: %s\n", err.Error()) 59 os.Exit(1) 60 } 61 62 // Block forever until we can read or write (default behavour is to never block) 63 xsksock.SetReadTimeout(-1) 64 xsksock.SetWriteTimeout(-1) 65 66 // Generate a program which will bypass all traffic to userspace 67 program := &gobpfld.ProgramXDP{ 68 AbstractBPFProgram: gobpfld.AbstractBPFProgram{ 69 Name: gobpfld.MustNewObjName("xsk_bypass"), 70 ProgramType: bpftypes.BPF_PROG_TYPE_XDP, 71 License: "GPL", 72 Maps: map[string]gobpfld.BPFMap{ 73 "xskmap": &gobpfld.XSKMap{ 74 AbstractMap: gobpfld.AbstractMap{ 75 Name: gobpfld.MustNewObjName("xskmap"), 76 Definition: gobpfld.BPFMapDef{ 77 Type: bpftypes.BPF_MAP_TYPE_XSKMAP, 78 KeySize: 4, // SizeOf(uint32) 79 ValueSize: 4, // SizeOf(uint32) 80 MaxEntries: 5, 81 }, 82 }, 83 }, 84 }, 85 MapFDLocations: map[string][]uint64{ 86 "xskmap": { 87 // LoadConstant64bit is the 2nd instruction in this program. So the first byte of the 88 // 2nd instruction is the width of a instruction * 1 to skip the first 1 instruction 89 uint64(ebpf.BPFInstSize) * 1, 90 }, 91 }, 92 // Instructions for this program: 93 // int xsk_bypass(struct xdp_md *ctx) 94 // { 95 // return bpf_redirect_map(&xsks_map, ctx->rx_queue_index, XDP_PASS); 96 // } 97 // 98 // NOTE this program only works in linux kernel >= 5.3 99 // https://elixir.bootlin.com/linux/v5.12.2/source/tools/lib/bpf/xsk.c#L416 100 Instructions: ebpf.MustEncode([]ebpf.Instruction{ 101 // load ((xdp_md) ctx)->rx_queue_index into R2 (used as 2nd parameter) 102 /* r2 = *(u32 *)(r1 + 16) */ 103 &ebpf.LoadMemory{ 104 Dest: ebpf.BPF_REG_2, 105 Src: ebpf.BPF_REG_1, 106 Size: ebpf.BPF_W, 107 Offset: 16, 108 }, 109 // Set R1(first parameter) to the address of the xskmap. 110 // Which will be set during loading 111 /* r1 = xskmap[] */ 112 &ebpf.LoadConstant64bit{ 113 Dest: ebpf.BPF_REG_1, 114 }, 115 // Move XDP_PASS into R3 (third argument) 116 /* r3 = XDP_PASS */ 117 &ebpf.Mov64{ 118 Dest: ebpf.BPF_REG_3, 119 Value: ebpf.XDP_PASS, 120 }, 121 /* call bpf_redirect_map */ 122 &ebpf.CallHelper{ 123 Function: 51, 124 }, 125 &ebpf.Exit{}, // exit 126 }), 127 }, 128 } 129 130 xskmap := program.Maps["xskmap"].(*gobpfld.XSKMap) 131 log, err := program.Load(gobpfld.ProgXDPLoadOpts{ 132 VerifierLogLevel: bpftypes.BPFLogLevelBasic, 133 }) 134 135 program.DecodeToReader(os.Stdout) 136 fmt.Fprintln(os.Stderr, log) 137 if err != nil { 138 fmt.Fprintf(os.Stderr, "error while loading program: %s\n", err.Error()) 139 os.Exit(1) 140 } 141 142 // Set the xsksocket for queue ID 0 143 err = xskmap.Set(0, xsksock) 144 if err != nil { 145 fmt.Fprintf(os.Stderr, "error while setting xsksock in map: %s\n", err.Error()) 146 os.Exit(1) 147 } 148 149 sigChan := make(chan os.Signal, 1) 150 signal.Notify(sigChan, os.Interrupt, unix.SIGTERM, unix.SIGINT) 151 152 err = program.Attach(gobpfld.ProgXDPAttachOpts{ 153 InterfaceName: linkName, 154 Replace: true, 155 }) 156 157 if err != nil { 158 fmt.Fprintf(os.Stderr, "error while attaching program to loopback device: %s\n", err.Error()) 159 os.Exit(1) 160 } 161 162 done := false 163 for !done { 164 select { 165 case <-sigChan: 166 done = true 167 168 default: 169 // Seperator to distinguish between frames 170 fmt.Println("------") 171 lease, err := xsksock.ReadLease() 172 if err != nil { 173 fmt.Fprintf(os.Stderr, "read lease: %s\n", err.Error()) 174 break 175 } 176 177 if lease == nil { 178 break 179 } 180 181 err = HandleFrame(lease, linkMAC, linkip) 182 if err != nil { 183 fmt.Fprintf(os.Stderr, "echo reply: %s", err.Error()) 184 } 185 } 186 } 187 188 fmt.Println("Detaching XPD program and stopping") 189 190 err = program.XDPLinkDetach(gobpfld.BPFProgramXDPLinkDetachSettings{ 191 All: true, 192 }) 193 if err != nil { 194 fmt.Fprintf(os.Stderr, "error while detaching program: %s\n", err.Error()) 195 os.Exit(1) 196 } 197 198 os.Exit(0) 199 } 200 201 func HandleFrame(lease *gobpfld.XSKLease, linkMac net.HardwareAddr, linkIP net.IP) error { 202 var err error 203 204 // Swap the MAC addresses 205 fmt.Printf("Src MAC: %X, Dst MAC: %X, EthType: %X\n", lease.Data[0:6], lease.Data[6:12], lease.Data[12:14]) 206 swapMac := make([]byte, 6) 207 copy(swapMac, lease.Data[0:6]) 208 copy(lease.Data[0:6], lease.Data[6:12]) 209 copy(lease.Data[6:12], swapMac) 210 211 // If EtherType == 0x0806(ARP) 212 if lease.Data[12] == 0x08 && lease.Data[13] == 0x06 { 213 return HandleARP(lease, linkMac, linkIP) 214 } 215 216 // If EtherType == 0x0800(IPv4) 217 if lease.Data[12] == 0x08 && lease.Data[13] == 0x00 { 218 return EchoReply(lease, linkIP) 219 } 220 221 err = lease.Release() 222 if err != nil { 223 return fmt.Errorf("release lease: %w", err) 224 } 225 return nil 226 } 227 228 // Since we have no network stack we also need to respond to ARP messages 229 func HandleARP(lease *gobpfld.XSKLease, linkMac net.HardwareAddr, linkIP net.IP) error { 230 // 14-20 left unchanged 231 232 // Change opcode from request to reply 233 lease.Data[21] = 2 234 235 // Copy the MAC of our link into the ethernet SRC 236 copy(lease.Data[6:12], linkMac) 237 // Copy the MAC of our link into the ARP target (will be swapped to source) 238 copy(lease.Data[32:38], linkMac) 239 240 // Ignore ARP requests whic hare not for us, otherwise we are ARP spoofing 241 if !net.IP(lease.Data[38:42]).Equal(linkIP) { 242 err := lease.Release() 243 if err != nil { 244 return fmt.Errorf("release lease: %w", err) 245 } 246 return nil 247 } 248 249 fmt.Println("respond to arp") 250 251 // Swap sender and target fields 252 swap := make([]byte, 10) 253 copy(swap, lease.Data[22:32]) 254 copy(lease.Data[22:32], lease.Data[32:42]) 255 copy(lease.Data[32:42], swap) 256 257 err := lease.Write() 258 if err != nil { 259 return fmt.Errorf("release lease: %w", err) 260 } 261 return nil 262 } 263 264 func EchoReply(lease *gobpfld.XSKLease, linkIP net.IP) error { 265 var err error 266 267 // Ignore IP traffic that is not for us 268 if !net.IP(lease.Data[30:34]).Equal(linkIP) { 269 err = lease.Release() 270 if err != nil { 271 return fmt.Errorf("release lease: %w", err) 272 } 273 return nil 274 } 275 276 fmt.Printf("IPv4 Src: %X, Dst: %X, Proto: %X\n", lease.Data[26:30], lease.Data[30:34], lease.Data[23]) 277 swapIP := make([]byte, 4) 278 copy(swapIP, lease.Data[26:30]) 279 copy(lease.Data[26:30], lease.Data[30:34]) 280 copy(lease.Data[30:34], swapIP) 281 282 fmt.Printf("IPv4 Checksum in: %X\n", lease.Data[24:26]) 283 284 // Zero the checksum 285 lease.Data[24] = 0x00 286 lease.Data[25] = 0x00 287 288 // Calculate header checksum 289 // https://en.wikipedia.org/wiki/IPv4_header_checksum 290 var sum uint32 291 for i := 14; i < 34; i += 2 { 292 sum += uint32(lease.Data[i]) << 8 293 sum += uint32(lease.Data[i+1]) 294 } 295 for { 296 // Break when sum is less or equals to 0xFFFF 297 if sum <= 65535 { 298 break 299 } 300 // Add carry to the sum 301 sum = (sum >> 16) + uint32(uint16(sum)) 302 } 303 checkSum := ^uint16(sum) 304 lease.Data[24] = byte(checkSum >> 8) 305 lease.Data[25] = byte(checkSum & 0xFF) 306 307 fmt.Printf("IPv4 Checksum out: %X\n", lease.Data[24:26]) 308 309 // If Protocol != 0x01(ICMPv4) 310 if lease.Data[23] != 0x01 { 311 err = lease.Release() 312 if err != nil { 313 return fmt.Errorf("release lease: %w", err) 314 } 315 return nil 316 } 317 318 fmt.Printf("ICMPv4 Type: %X, Code: %X\n", lease.Data[34], lease.Data[35]) 319 // Set type to 0 to get 0,0 = Echo Reply 320 lease.Data[34] = 0 321 322 fmt.Printf("ICMPv4 Checksum in: %X\n", lease.Data[36:38]) 323 324 // clear icmp checksum 325 lease.Data[36] = 0x00 326 lease.Data[37] = 0x00 327 328 // Calculate ICMP checksum 329 sum = 0 330 for i := 34; i < len(lease.Data); i += 2 { 331 sum += uint32(lease.Data[i]) << 8 332 sum += uint32(lease.Data[i+1]) 333 } 334 for { 335 // Break when sum is less or equals to 0xFFFF 336 if sum <= 65535 { 337 break 338 } 339 // Add carry to the sum 340 sum = (sum >> 16) + uint32(uint16(sum)) 341 } 342 checkSum = ^uint16(sum) 343 lease.Data[36] = byte(checkSum >> 8) 344 lease.Data[37] = byte(checkSum & 0xFF) 345 346 fmt.Printf("ICMPv4 Checksum out: %X\n", lease.Data[36:38]) 347 348 // Now that we have converted the request into a reply in the same memory buffer 349 // we can just write this buffer back to the network interface 350 err = lease.Write() 351 if err != nil { 352 return fmt.Errorf("write lease: %w", err) 353 } 354 return nil 355 }