netstack: add --EXPERIMENTAL-afxdp flag

Setting the flag creates an AF_XDP socket that will be consumed by the XDP
dispatcher.

PiperOrigin-RevId: 470109694
This commit is contained in:
Kevin Krakauer
2022-08-25 16:23:33 -07:00
committed by gVisor bot
parent 385afe9f2c
commit 540c79c8cf
5 changed files with 77 additions and 14 deletions
+13
View File
@@ -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
+25 -3
View File
@@ -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,
+4
View File
@@ -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.
+1
View File
@@ -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.")
+34 -11
View File
@@ -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) {