From 540c79c8cf7ddbf265052c67d1f4f74ed199bb71 Mon Sep 17 00:00:00 2001 From: Kevin Krakauer Date: Thu, 25 Aug 2022 16:21:15 -0700 Subject: [PATCH] netstack: add --EXPERIMENTAL-afxdp flag Setting the flag creates an AF_XDP socket that will be consumed by the XDP dispatcher. PiperOrigin-RevId: 470109694 --- pkg/tcpip/link/fdbased/endpoint.go | 13 +++++++++ runsc/boot/network.go | 28 +++++++++++++++++-- runsc/config/config.go | 4 +++ runsc/config/flags.go | 1 + runsc/sandbox/network.go | 45 ++++++++++++++++++++++-------- 5 files changed, 77 insertions(+), 14 deletions(-) diff --git a/pkg/tcpip/link/fdbased/endpoint.go b/pkg/tcpip/link/fdbased/endpoint.go index 493db9faf..9a4341b83 100644 --- a/pkg/tcpip/link/fdbased/endpoint.go +++ b/pkg/tcpip/link/fdbased/endpoint.go @@ -41,6 +41,7 @@ package fdbased import ( + "errors" "fmt" "golang.org/x/sys/unix" @@ -88,6 +89,9 @@ const ( // primary use-case for this is runsc which uses an AF_PACKET FD to // receive packets from the veth device. PacketMMap + // AFXDP utilizes an AF_XDP socket to receive packets. AFXDP requires that + // the underlying FD be an AF_XDP socket. + AFXDP ) func (p PacketDispatchMode) String() string { @@ -98,6 +102,8 @@ func (p PacketDispatchMode) String() string { return "RecvMMsg" case PacketMMap: return "PacketMMap" + case AFXDP: + return "AFXDP" default: return fmt.Sprintf("unknown packet dispatch mode '%d'", p) } @@ -217,6 +223,11 @@ type Options struct { // of struct iovec, msghdr, and mmsghdr that may be passed by each host // system call. MaxSyscallHeaderBytes int + + // AFXDPFD is used with the experimental AF_XDP mode. + // TODO(b/240191988): Use multiple sockets. + // TODO(b/240191988): How do we handle the MTU issue? + AFXDPFD int } // fanoutID is used for AF_PACKET based endpoints to enable PACKET_FANOUT @@ -374,6 +385,8 @@ func createInboundDispatcher(e *endpoint, fd int, isSocket bool, fID int32) (lin if err != nil { return nil, fmt.Errorf("newRecvMMsgDispatcher(%d, %+v) = %v", fd, e, err) } + case AFXDP: + return nil, errors.New("AFXDP not yet implemented") } } return inboundDispatcher, nil diff --git a/runsc/boot/network.go b/runsc/boot/network.go index 0d4f64e16..a64085506 100644 --- a/runsc/boot/network.go +++ b/runsc/boot/network.go @@ -99,7 +99,7 @@ type FDBasedLink struct { QDisc config.QueueingDiscipline Neighbors []Neighbor - // NumChannels controls how many underlying FD's are to be used to + // NumChannels controls how many underlying FDs are to be used to // create this endpoint. NumChannels int } @@ -123,6 +123,8 @@ type CreateLinksAndRoutesArgs struct { Defaultv4Gateway DefaultRoute Defaultv6Gateway DefaultRoute + + AFXDP bool } // IPWithPrefix is an address with its subnet prefix length. @@ -162,8 +164,11 @@ func (n *Network) CreateLinksAndRoutes(args *CreateLinksAndRoutesArgs, _ *struct for _, l := range args.FDBasedLinks { wantFDs += l.NumChannels } + if args.AFXDP { + wantFDs++ + } if got := len(args.FilePayload.Files); got != wantFDs { - return fmt.Errorf("args.FilePayload.Files has %d FD's but we need %d entries based on FDBasedLinks", got, wantFDs) + return fmt.Errorf("args.FilePayload.Files has %d FDs but we need %d entries based on FDBasedLinks. AFXDP is %t", got, wantFDs, args.AFXDP) } var nicID tcpip.NICID @@ -206,13 +211,16 @@ func (n *Network) CreateLinksAndRoutes(args *CreateLinksAndRoutesArgs, _ *struct } else { log.Infof("Host kernel version < 5.6, falling back to RecvMMsg dispatch") } + if args.AFXDP { + dispatchMode = fdbased.AFXDP + } fdOffset := 0 for _, link := range args.FDBasedLinks { nicID++ nicids[link.Name] = nicID - FDs := []int{} + FDs := make([]int, 0, link.NumChannels) for j := 0; j < link.NumChannels; j++ { // Copy the underlying FD. oldFD := args.FilePayload.Files[fdOffset].Fd() @@ -224,11 +232,25 @@ func (n *Network) CreateLinksAndRoutes(args *CreateLinksAndRoutesArgs, _ *struct fdOffset++ } + // If AFXDP is enabled, we perform RX via AF_XDP and TX via + // AF_PACKET. + AFXDPFD := -1 + if args.AFXDP { + oldFD := args.FilePayload.Files[fdOffset].Fd() + newFD, err := unix.Dup(int(oldFD)) + if err != nil { + return fmt.Errorf("failed to dup AF_XDP fd %v: %v", oldFD, err) + } + AFXDPFD = newFD + fdOffset++ + } + mac := tcpip.LinkAddress(link.LinkAddress) log.Infof("gso max size is: %d", link.GSOMaxSize) linkEP, err := fdbased.New(&fdbased.Options{ FDs: FDs, + AFXDPFD: AFXDPFD, MTU: uint32(link.MTU), EthernetHeader: mac != "", Address: mac, diff --git a/runsc/config/config.go b/runsc/config/config.go index 6238f7017..3a6163138 100644 --- a/runsc/config/config.go +++ b/runsc/config/config.go @@ -225,6 +225,10 @@ type Config struct { // Use pools to manage buffer memory instead of heap. BufferPooling bool `flag:"buffer-pooling"` + // AFXDP defines whether to use an AF_XDP socket to receive packets + // (rather than AF_PACKET). Enabling it disables RX checksum offload. + AFXDP bool `flag:"EXPERIMENTAL-afxdp"` + // FDLimit specifies a limit on the number of host file descriptors that can // be open simultaneously by the sentry and gofer. It applies separately to // each. diff --git a/runsc/config/flags.go b/runsc/config/flags.go index 15fe1ae77..1b6f965c3 100644 --- a/runsc/config/flags.go +++ b/runsc/config/flags.go @@ -96,6 +96,7 @@ func RegisterFlags(flagSet *flag.FlagSet) { flagSet.Var(queueingDisciplinePtr(QDiscFIFO), "qdisc", "specifies which queueing discipline to apply by default to the non loopback nics used by the sandbox.") flagSet.Int("num-network-channels", 1, "number of underlying channels(FDs) to use for network link endpoints.") flagSet.Bool("buffer-pooling", false, "enable allocation of buffers from a shared pool instead of the heap.") + flagSet.Bool("EXPERIMENTAL-afxdp", false, "EXPERIMENTAL. Use an AF_XDP socket to receive packets.") // Test flags, not to be used outside tests, ever. flagSet.Bool("TESTONLY-unsafe-nonroot", false, "TEST ONLY; do not ever use! This skips many security measures that isolate the host from the sandbox.") diff --git a/runsc/sandbox/network.go b/runsc/sandbox/network.go index aa52189f1..1cb6584e0 100644 --- a/runsc/sandbox/network.go +++ b/runsc/sandbox/network.go @@ -63,7 +63,7 @@ func setupNetwork(conn *urpc.Client, pid int, conf *config.Config) error { // Build the path to the net namespace of the sandbox process. // This is what we will copy. nsPath := filepath.Join("/proc", strconv.Itoa(pid), "ns/net") - if err := createInterfacesAndRoutesFromNS(conn, nsPath, conf.HostGSO, conf.GvisorGSO, conf.TXChecksumOffload, conf.RXChecksumOffload, conf.NumNetworkChannels, conf.QDisc); err != nil { + if err := createInterfacesAndRoutesFromNS(conn, nsPath, conf); err != nil { return fmt.Errorf("creating interfaces from net namespace %q: %v", nsPath, err) } case config.NetworkHost: @@ -116,7 +116,7 @@ func isRootNS() (bool, error) { // createInterfacesAndRoutesFromNS scrapes the interface and routes from the // net namespace with the given path, creates them in the sandbox, and removes // them from the host. -func createInterfacesAndRoutesFromNS(conn *urpc.Client, nsPath string, hostGSO bool, gvisorGSO bool, txChecksumOffload bool, rxChecksumOffload bool, numNetworkChannels int, qDisc config.QueueingDiscipline) error { +func createInterfacesAndRoutesFromNS(conn *urpc.Client, nsPath string, conf *config.Config) error { // Join the network namespace that we will be copying. restore, err := joinNetNS(nsPath) if err != nil { @@ -213,14 +213,16 @@ func createInterfacesAndRoutesFromNS(conn *urpc.Client, nsPath string, hostGSO b args.Defaultv6Gateway.Name = iface.Name } + args.AFXDP = conf.AFXDP + link := boot.FDBasedLink{ Name: iface.Name, MTU: iface.MTU, Routes: routes, - TXChecksumOffload: txChecksumOffload, - RXChecksumOffload: rxChecksumOffload, - NumChannels: numNetworkChannels, - QDisc: qDisc, + TXChecksumOffload: conf.TXChecksumOffload, + RXChecksumOffload: conf.RXChecksumOffload, + NumChannels: conf.NumNetworkChannels, + QDisc: conf.QDisc, Neighbors: neighbors, } @@ -235,7 +237,7 @@ func createInterfacesAndRoutesFromNS(conn *urpc.Client, nsPath string, hostGSO b // Create the socket for the device. for i := 0; i < link.NumChannels; i++ { log.Debugf("Creating Channel %d", i) - socketEntry, err := createSocket(iface, ifaceLink, hostGSO) + socketEntry, err := createSocket(iface, ifaceLink, conf.HostGSO, conf.AFXDP) if err != nil { return fmt.Errorf("failed to createSocket for %s : %w", iface.Name, err) } @@ -250,7 +252,16 @@ func createInterfacesAndRoutesFromNS(conn *urpc.Client, nsPath string, hostGSO b args.FilePayload.Files = append(args.FilePayload.Files, socketEntry.deviceFile) } - if link.GSOMaxSize == 0 && gvisorGSO { + // If enabled, create an RX socket for AF_XDP. + if conf.AFXDP { + xdpSock, err := createSocketXDP(iface) + if err != nil { + return fmt.Errorf("failed to create XDP socket: %v", err) + } + args.FilePayload.Files = append(args.FilePayload.Files, xdpSock) + } + + if link.GSOMaxSize == 0 && conf.GvisorGSO { // Host GSO is disabled. Let's enable gVisor GSO. link.GSOMaxSize = stack.GvisorGSOMaxSize link.GvisorGSOEnabled = true @@ -313,9 +324,10 @@ type socketEntry struct { gsoMaxSize uint32 } -// createSocket creates an underlying AF_PACKET socket and configures it for use by -// the sentry and returns an *os.File that wraps the underlying socket fd. -func createSocket(iface net.Interface, ifaceLink netlink.Link, enableGSO bool) (*socketEntry, error) { +// createSocket creates an underlying AF_PACKET socket and configures it for +// use by the sentry and returns an *os.File that wraps the underlying socket +// fd. +func createSocket(iface net.Interface, ifaceLink netlink.Link, enableGSO bool, AFXDP bool) (*socketEntry, error) { // Create the socket. const protocol = 0x0300 // htons(ETH_P_ALL) fd, err := unix.Socket(unix.AF_PACKET, unix.SOCK_RAW, protocol) @@ -375,6 +387,17 @@ func createSocket(iface net.Interface, ifaceLink netlink.Link, enableGSO bool) ( return &socketEntry{deviceFile, gsoMaxSize}, nil } +func createSocketXDP(iface net.Interface) (*os.File, error) { + // Create an XDP socket. The sentry will mmap memory for the various + // rings and bind to the device. + fd, err := unix.Socket(unix.AF_XDP, unix.SOCK_RAW, 0) + if err != nil { + return nil, fmt.Errorf("unable to create AF_XDP socket: %v", err) + } + deviceFile := os.NewFile(uintptr(fd), "xdp-fd") + return deviceFile, nil +} + // loopbackLink returns the link with addresses and routes for a loopback // interface. func loopbackLink(iface net.Interface, addrs []net.Addr) (boot.LoopbackLink, error) {