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  }