diff --git a/pkg/xdp/xdp.go b/pkg/xdp/xdp.go index 193a1b094..941401a33 100644 --- a/pkg/xdp/xdp.go +++ b/pkg/xdp/xdp.go @@ -67,10 +67,11 @@ type ControlBlock struct { // ReadOnlySocketOpts configure a read-only AF_XDP socket. type ReadOnlySocketOpts struct { - NFrames uint32 - FrameSize uint32 - NDescriptors uint32 - Bind bool + NFrames uint32 + FrameSize uint32 + NDescriptors uint32 + Bind bool + UseNeedWakeup bool } // DefaultReadOnlyOpts provides recommended default options for initializing a @@ -287,30 +288,38 @@ func ReadOnlyFromSocket(sockfd int, ifaceIdx, queueID uint32, opts ReadOnlySocke // device. In those cases, another process with the same socket will // bind for us. if opts.Bind { - addr := unix.SockaddrXDP{ - // XDP_USE_NEED_WAKEUP lets the driver sleep if there is no - // work to do. It will need to be woken by poll. It is expected - // that this improves performance by preventing the driver from - // burning cycles. - // - // By not setting either XDP_COPY or XDP_ZEROCOPY, we instruct - // the kernel to use zerocopy if available and then fallback to - // copy mode. - Flags: unix.XDP_USE_NEED_WAKEUP, - Ifindex: ifaceIdx, - // AF_XDP sockets are per device RX queue, although multiple - // sockets on multiple queues (or devices) can share a single - // UMEM. - QueueID: queueID, - // We're not using shared mode, so the value here is irrelevant. - SharedUmemFD: 0, - } - - if err := unix.Bind(sockfd, &addr); err != nil { - return nil, fmt.Errorf("failed to bind with addr %+v: %v", addr, err) + if err := Bind(sockfd, ifaceIdx, queueID, opts.UseNeedWakeup); err != nil { + return nil, fmt.Errorf("failed to bind to interface %d: %v", ifaceIdx, err) } } cleanup.Release() return &cb, nil } + +// Bind binds a socket to a particular network interface and queue. +func Bind(sockfd int, ifindex, queueID uint32, useNeedWakeup bool) error { + var flags uint16 + if useNeedWakeup { + flags |= unix.XDP_USE_NEED_WAKEUP + } + addr := unix.SockaddrXDP{ + // XDP_USE_NEED_WAKEUP lets the driver sleep if there is no + // work to do. It will need to be woken by poll. It is expected + // that this improves performance by preventing the driver from + // burning cycles. + // + // By not setting either XDP_COPY or XDP_ZEROCOPY, we instruct + // the kernel to use zerocopy if available and then fallback to + // copy mode. + Flags: flags, + Ifindex: ifindex, + // AF_XDP sockets are per device RX queue, although multiple + // sockets on multiple queues (or devices) can share a single + // UMEM. + QueueID: queueID, + // We're not using shared mode, so the value here is irrelevant. + SharedUmemFD: 0, + } + return unix.Bind(sockfd, &addr) +} diff --git a/runsc/config/config.go b/runsc/config/config.go index 6fb4933b8..910db74c6 100644 --- a/runsc/config/config.go +++ b/runsc/config/config.go @@ -284,6 +284,10 @@ type Config struct { // eBPF map that runsc hooks into. AFXDPRedirectHost string `flag:"EXPERIMENTAL-xdp-redirect-host"` + // AFXDPUseNeedWakeup determines whether XDP_USE_NEED_WAKEUP is set + // when using AF_XDP sockets. + AFXDPUseNeedWakeup bool `flag:"EXPERIMENTAL-xdp-need-wakeup"` + // 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 82e8fc365..0cbcdfce5 100644 --- a/runsc/config/flags.go +++ b/runsc/config/flags.go @@ -121,6 +121,7 @@ func RegisterFlags(flagSet *flag.FlagSet) { flagSet.Bool("buffer-pooling", true, "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.") flagSet.String("EXPERIMENTAL-xdp-redirect-host", "", "EXPERIMENTAL. Use an AF_XDP socket attached to . Use the IP of that interface.") + flagSet.Bool("EXPERIMENTAL-xdp-need-wakeup", true, "EXPERIMENTAL. Use XDP_USE_NEED_WAKEUP with XDP sockets.") flagSet.Bool("reproduce-nat", false, "Scrape the host netns NAT table and reproduce it in the sandbox.") flagSet.Bool("reproduce-nftables", false, "Attempt to scrape and reproduce nftable rules inside the sandbox. Overrides reproduce-nat when true.") diff --git a/runsc/sandbox/BUILD b/runsc/sandbox/BUILD index 6532bb3b0..c1a5ecfa1 100644 --- a/runsc/sandbox/BUILD +++ b/runsc/sandbox/BUILD @@ -38,6 +38,7 @@ go_library( "//pkg/tcpip/header", "//pkg/tcpip/stack", "//pkg/urpc", + "//pkg/xdp", "//runsc/boot", "//runsc/boot/procfs", "//runsc/cgroup", diff --git a/runsc/sandbox/xdp.go b/runsc/sandbox/xdp.go index fa0669a8e..9a8607770 100644 --- a/runsc/sandbox/xdp.go +++ b/runsc/sandbox/xdp.go @@ -26,6 +26,7 @@ import ( "golang.org/x/sys/unix" "gvisor.dev/gvisor/pkg/log" "gvisor.dev/gvisor/pkg/urpc" + "gvisor.dev/gvisor/pkg/xdp" "gvisor.dev/gvisor/runsc/boot" "gvisor.dev/gvisor/runsc/config" "gvisor.dev/gvisor/runsc/sandbox/bpf" @@ -114,29 +115,10 @@ func createRedirectInterfacesAndRoutes(conn *urpc.Client, conf *config.Config) e } // Bind to the device. - sockAddr := unix.SockaddrXDP{ - // XDP_USE_NEED_WAKEUP lets the driver sleep if there is no - // work to do. It will need to be woken by poll. It is expected - // that this improves performance by preventing the driver from - // burning cycles. - // - // By not setting either XDP_COPY or XDP_ZEROCOPY, we instruct - // the kernel to use zerocopy if available and then fallback to - // copy mode. - Flags: unix.XDP_USE_NEED_WAKEUP, - Ifindex: uint32(iface.Index), - // AF_XDP sockets are per device RX queue, although multiple - // sockets on multiple queues (or devices) can share a single - // UMEM. - // - // TODO(b/240191988): We can't assume there's only one queue, - // but this appears to be the case on gVNIC instances. - QueueID: 0, - // We're not using shared mode, so the value here is irrelevant. - SharedUmemFD: 0, - } - if err := unix.Bind(xdpSockFD, &sockAddr); err != nil { - return fmt.Errorf("failed to bind to interface %q with addr %+v: %v", iface.Name, sockAddr, err) + // TODO(b/240191988): We can't assume there's only one queue, but this + // appears to be the case on gVNIC instances. + if err := xdp.Bind(xdpSockFD, uint32(iface.Index), 0 /* queueID */, conf.AFXDPUseNeedWakeup); err != nil { + return fmt.Errorf("failed to bind to interface %q: %v", iface.Name, err) } return nil