xdp

package module
v0.3.3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 26, 2021 License: BSD-3-Clause Imports: 9 Imported by: 43

README

xdp

Go Reference

Package github.com/asavie/xdp allows one to use XDP sockets from the Go programming language.

For usage examples, see the documentation or the examples/ directory.

Performance

examples/sendudp

With the default UDP payload size of 1400 bytes, running on Linux kernel 5.1.20, on a tg3 (so no native XDP support) gigabit NIC, sendudp.go does around 980 Mb/s, so practically line rate.

examples/senddnsqueries

TL;DR: in the same environment, sending a pre-generated DNS query using an ordinary UDP socket yields around 30 MiB/s whereas sending it using the senddnsqueries.go example program yields around 77 MiB/s.

Connecting a PC with Intel Core i7-7700 CPU running Linux kernel 5.0.17 and igb driver to a laptop with Intel Core i7-5600U CPU running Linux kernel 5.0.9 with e1000e with a cat 5E gigabit ethernet cable and using the following program

package main

import (
	"net"

	"github.com/miekg/dns"
)

func main() {
	query := new(dns.Msg)
	query.SetQuestion(dns.Fqdn("asavie.com"), dns.TypeA)
	payload, err := query.Pack()
	if err != nil {
		panic(err)
	}

	conn, err := net.ListenPacket("udp", ":0")
	if err != nil {
		panic(err)
	}
	defer conn.Close()

	dst, err := net.ResolveUDPAddr("udp", "192.168.111.10:53")
	if err != nil {
		panic(err)
	}

	for {
		_, err = conn.WriteTo(payload, dst)
		if err != nil {
			panic(err)
		}
	}
}

which uses an ordinary UDP socket to send a pre-generated DNS query from PC to laptop as quickly as possible - I get about 30 MiB/s at laptop side.

Using the senddnsqueries.go example program - I get about 77 MiB/s at laptop side.

Documentation

Overview

Package xdp allows to use XDP sockets from Go.

An XDP socket allows to get packets from a network interface driver into a userspace process very fast, bypassing the Linux kernel's network stack, see https://lwn.net/Articles/750845/ for more information.

NOTE:

* If your network link device supports multiple queues - you might not see all of the incoming traffic because it might be e.g. load-balanced between multiple queues. You can use the `ethtool -L` command to control the load-balancing behaviour or even make it so that all of the incoming traffic is put on a single queue.

* On recent versions of Linux kernel, the eBPF map memory counts towards the "locked memory" user limit, so most likely you'll have to increase it. On Fedora Linux, this can be done by running `ulimit -l <new-limit>` command, or to make it permanent, by creating a file at `/etc/security/limits.d/50-lockedmem.conf` with e.g. the following contents (1MiB should be enough for this package):

  • - lockedmem 1048576

logging out and logging back in. When you hit this limit, you'll get an error that looks like this:

error: failed to create an XDP socket: ebpf.NewMap qidconf_map failed: map create: operation not permitted

Here is a minimal example of a program which receives network frames, modifies their destination MAC address in-place to broadcast address and transmits them back out the same network link:

package main

import (
	"os"
	"os/signal"
	"syscall"

	"github.com/asavie/xdp"
	"github.com/vishvananda/netlink"
)

func main() {
	const LinkName = "enp6s0"
	const QueueID = 0

	link, err := netlink.LinkByName(LinkName)
	if err != nil {
		panic(err)
	}

	program, err := xdp.NewProgram(QueueID + 1)
	if err != nil {
		panic(err)
	}
	if err := program.Attach(link.Attrs().Index); err != nil {
		panic(err)
	}

	xsk, err := xdp.NewSocket(link.Attrs().Index, QueueID, nil)
	if err != nil {
		panic(err)
	}

	if err := program.Register(QueueID, xsk.FD()); err != nil {
		panic(err)
	}

	// Remove the XDP BPF program on interrupt.
	c := make(chan os.Signal)
	signal.Notify(c, os.Interrupt, syscall.SIGTERM)
	go func() {
		<-c
		program.Detach(link.Attrs().Index)
		os.Exit(1)
	}()

	for {
		xsk.Fill(xsk.GetDescs(xsk.NumFreeFillSlots()))
		numRx, _, err := xsk.Poll(-1)
		if err != nil {
			panic(err)
		}
		rxDescs := xsk.Receive(numRx)
		for i := 0; i < len(rxDescs); i++ {
			// Set destination MAC address to
			// ff:ff:ff:ff:ff:ff
			frame := xsk.GetFrame(rxDescs[i])
			for i := 0; i < 6; i++ {
				frame[i] = byte(0xff)
			}
		}
		xsk.Transmit(rxDescs)
	}
}

Index

Constants

This section is empty.

Variables

View Source
var DefaultSocketFlags uint16

DefaultSocketFlags are the flags which are passed to bind(2) system call when the XDP socket is bound, possible values include unix.XDP_SHARED_UMEM, unix.XDP_COPY, unix.XDP_ZEROCOPY.

View Source
var DefaultSocketOptions = SocketOptions{
	NumFrames:              128,
	FrameSize:              2048,
	FillRingNumDescs:       64,
	CompletionRingNumDescs: 64,
	RxRingNumDescs:         64,
	TxRingNumDescs:         64,
}

DefaultSocketOptions is the default SocketOptions used by an xdp.Socket created without specifying options.

View Source
var DefaultXdpFlags uint32

DefaultXdpFlags are the flags which are passed when the XDP program is attached to the network link, possible values include unix.XDP_FLAGS_DRV_MODE, unix.XDP_FLAGS_HW_MODE, unix.XDP_FLAGS_SKB_MODE, unix.XDP_FLAGS_UPDATE_IF_NOEXIST.

Functions

This section is empty.

Types

type Desc added in v0.3.1

type Desc unix.XDPDesc

Desc represents an XDP Rx/Tx descriptor.

type Program added in v0.2.0

type Program struct {
	Program *ebpf.Program
	Queues  *ebpf.Map
	Sockets *ebpf.Map
}

Program represents the necessary data structures for a simple XDP program that can filter traffic based on the attached rx queue.

func NewProgram added in v0.2.0

func NewProgram(maxQueueEntries int) (*Program, error)

NewProgram returns a translation of the default eBPF XDP program found in the xsk_load_xdp_prog() function in <linux>/tools/lib/bpf/xsk.c: https://github.com/torvalds/linux/blob/master/tools/lib/bpf/xsk.c#L259

func (*Program) Attach added in v0.2.0

func (p *Program) Attach(Ifindex int) error

Attach the XDP Program to an interface.

func (*Program) Close added in v0.2.0

func (p *Program) Close() error

Close closes and frees the resources allocated for the Program.

func (*Program) Detach added in v0.2.0

func (p *Program) Detach(Ifindex int) error

Detach the XDP Program from an interface.

func (*Program) Register added in v0.2.0

func (p *Program) Register(queueID int, fd int) error

Register adds the socket file descriptor as the recipient for packets from the given queueID.

func (*Program) Unregister added in v0.2.0

func (p *Program) Unregister(queueID int) error

Unregister removes any associated mapping to sockets for the given queueID.

type Socket

type Socket struct {
	// contains filtered or unexported fields
}

A Socket is an implementation of the AF_XDP Linux socket type for reading packets from a device.

func NewSocket

func NewSocket(Ifindex int, QueueID int, options *SocketOptions) (xsk *Socket, err error)

NewSocket returns a new XDP socket attached to the network interface which has the given interface, and attached to the given queue on that network interface.

func (*Socket) Close

func (xsk *Socket) Close() error

Close closes and frees the resources allocated by the Socket.

func (*Socket) Complete

func (xsk *Socket) Complete(n int)

Complete consumes up to n descriptors from the Completion ring queue to which the kernel produces when it has actually transmitted a descriptor it got from Tx ring queue. You should use this method if you are doing polling on the xdp.Socket file descriptor yourself, rather than using the Poll() method.

func (*Socket) FD

func (xsk *Socket) FD() int

FD returns the file descriptor associated with this xdp.Socket which can be used e.g. to do polling.

func (*Socket) Fill

func (xsk *Socket) Fill(descs []Desc) int

Fill submits the given descriptors to be filled (i.e. to receive frames into) it returns how many descriptors where actually put onto Fill ring queue. The descriptors can be acquired either by calling the GetDescs() method or by calling Receive() method.

func (*Socket) GetDescs

func (xsk *Socket) GetDescs(n int) []Desc

GetDescs returns up to n descriptors which are not currently in use.

func (*Socket) GetFrame

func (xsk *Socket) GetFrame(d Desc) []byte

GetFrame returns the buffer containing the frame corresponding to the given descriptor. The returned byte slice points to the actual buffer of the corresponding frame, so modiyfing this slice modifies the frame contents.

func (*Socket) NumCompleted

func (xsk *Socket) NumCompleted() int

NumCompleted returns how many descriptors are there on the Completion ring queue which were produced by the kernel and which we have not yet consumed.

func (*Socket) NumFilled

func (xsk *Socket) NumFilled() int

NumFilled returns how many descriptors are there on the Fill ring queue which have not yet been consumed by the kernel. This method is useful if you're polling the xdp.Socket file descriptor yourself, rather than using the Poll() method - if it returns a number greater than zero it means you should set the unix.POLLIN flag.

func (*Socket) NumFreeFillSlots

func (xsk *Socket) NumFreeFillSlots() int

NumFreeFillSlots returns how many free slots are available on the Fill ring queue, i.e. the queue to which we produce descriptors which should be filled by the kernel with incoming frames.

func (*Socket) NumFreeTxSlots

func (xsk *Socket) NumFreeTxSlots() int

NumFreeTxSlots returns how many free slots are available on the Tx ring queue, i.e. the queue to which we produce descriptors which should be transmitted by the kernel to the wire.

func (*Socket) NumReceived

func (xsk *Socket) NumReceived() int

NumReceived returns how many descriptors are there on the Rx ring queue which were produced by the kernel and which we have not yet consumed.

func (*Socket) NumTransmitted

func (xsk *Socket) NumTransmitted() int

NumTransmitted returns how many descriptors are there on the Tx ring queue which have not yet been consumed by the kernel. Note that even after the descriptors are consumed by the kernel from the Tx ring queue, it doesn't mean that they have actually been sent out on the wire, that can be assumed only after the descriptors have been produced by the kernel to the Completion ring queue. This method is useful if you're polling the xdp.Socket file descriptor yourself, rather than using the Poll() method - if it returns a number greater than zero it means you should set the unix.POLLOUT flag.

func (*Socket) Poll

func (xsk *Socket) Poll(timeout int) (numReceived int, numCompleted int, err error)

Poll blocks until kernel informs us that it has either received or completed (i.e. actually sent) some frames that were previously submitted using Fill() or Transmit() methods. The numReceived return value can be used as the argument for subsequent Receive() method call.

func (*Socket) Receive

func (xsk *Socket) Receive(num int) []Desc

Receive returns the descriptors which were filled, i.e. into which frames were received into.

func (*Socket) Stats

func (xsk *Socket) Stats() (Stats, error)

Stats returns various statistics for this XDP socket.

func (*Socket) Transmit

func (xsk *Socket) Transmit(descs []Desc) (numSubmitted int)

Transmit submits the given descriptors to be sent out, it returns how many descriptors were actually pushed onto the Tx ring queue. The descriptors can be acquired either by calling the GetDescs() method or by calling Receive() method.

type SocketOptions added in v0.3.0

type SocketOptions struct {
	NumFrames              int
	FrameSize              int
	FillRingNumDescs       int
	CompletionRingNumDescs int
	RxRingNumDescs         int
	TxRingNumDescs         int
}

SocketOptions are configuration settings used to bind an XDP socket.

type Stats

type Stats struct {
	// Filled is the number of items consumed thus far by the Linux kernel
	// from the Fill ring queue.
	Filled uint64

	// Received is the number of items consumed thus far by the user of
	// this package from the Rx ring queue.
	Received uint64

	// Transmitted is the number of items consumed thus far by the Linux
	// kernel from the Tx ring queue.
	Transmitted uint64

	// Completed is the number of items consumed thus far by the user of
	// this package from the Completion ring queue.
	Completed uint64

	// KernelStats contains the in-kernel statistics of the corresponding
	// XDP socket, such as the number of invalid descriptors that were
	// submitted into Fill or Tx ring queues.
	KernelStats unix.XDPStatistics
}

Stats contains various counters of the XDP socket, such as numbers of sent/received frames.

Directories

Path Synopsis
examples
dumpframes
dumpframes demostrates how to receive frames from a network link using github.com/asavie/xdp package, it sets up an XDP socket attached to a particular network link and dumps all frames it receives to standard output.
dumpframes demostrates how to receive frames from a network link using github.com/asavie/xdp package, it sets up an XDP socket attached to a particular network link and dumps all frames it receives to standard output.
l2fwd
l2fwd is a primitive layer 2 frame forwarder, it attaches to two given network links and transmits any frames received on any of them on the other network link with the given destination MAC address.
l2fwd is a primitive layer 2 frame forwarder, it attaches to two given network links and transmits any frames received on any of them on the other network link with the given destination MAC address.
rebroadcast
rebroadcast demonstrates how to receive and transmit network frames using the github.com/asavie/xdp package, it receives frames on the given network interface using an XDP socket, prints the received frames to stdout, modifies their the destination MAC address to the broadcast address of ff:ff:ff:ff:ff:ff in-line and sends the frames back out the same network interface.
rebroadcast demonstrates how to receive and transmit network frames using the github.com/asavie/xdp package, it receives frames on the given network interface using an XDP socket, prints the received frames to stdout, modifies their the destination MAC address to the broadcast address of ff:ff:ff:ff:ff:ff in-line and sends the frames back out the same network interface.
senddnsqueries
senddnsqueries pre-generates a frame with a DNS query and starts sending it in and endless loop to given destination as fast as possible.
senddnsqueries pre-generates a frame with a DNS query and starts sending it in and endless loop to given destination as fast as possible.
sendudp
sendudp pre-generates a frame with a UDP packet with a payload of the given size and starts sending it in and endless loop to given destination as fast as possible.
sendudp pre-generates a frame with a UDP packet with a payload of the given size and starts sending it in and endless loop to given destination as fast as possible.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL