Implement veth devices

The veth devices are virtual Ethernet devices. They can act as
tunnels between network namespaces to create a bridge to a
physical network device in another namespace, but can also be
used as standalone network devices.

More information can be found here:
https://man7.org/linux/man-pages/man4/veth.4.html

PiperOrigin-RevId: 638853289
This commit is contained in:
Andrei Vagin
2024-05-30 18:25:30 -07:00
committed by gVisor bot
parent 8c3abba800
commit 04e7902bb5
12 changed files with 530 additions and 13 deletions
+15
View File
@@ -168,6 +168,21 @@ const (
IFLA_GSO_MAX_SIZE = 41
)
// Interface link info attributes, from uapi/linux/if_link.h.
const (
IFLA_INFO_UNSPEC = 0
IFLA_INFO_KIND = 1
IFLA_INFO_DATA = 2
IFLA_INFO_XSTATS = 3
IFLA_INFO_SLAVE_KIND = 4
IFLA_INFO_SLAVE_DATA = 5
)
// Virtuall ethernet attributes, from uapi/linux/veth.h.
const (
VETH_INFO_PEER = 1
)
// InterfaceAddrMessage is struct ifaddrmsg, from uapi/linux/if_addr.h.
//
// +marshal
+15
View File
@@ -24,6 +24,8 @@ type contextID int
const (
// CtxStack is a Context.Value key for a network stack.
CtxStack contextID = iota
// CtxNamespaceByFD is a Context.Value key for NamespaceByFD.
CtxNamespaceByFD
)
// StackFromContext returns the network stack associated with ctx.
@@ -33,3 +35,16 @@ func StackFromContext(ctx context.Context) Stack {
}
return nil
}
// NamespaceByFD returns the network namespace associated with the specified
// file descriptor.
type NamespaceByFD = func(fd int32) (*Namespace, error)
// NamespaceByFDFromContext returns NamespaceByFD to lookup the network
// namespace associated with the specified file descriptor.
func NamespaceByFDFromContext(ctx context.Context) NamespaceByFD {
if v := ctx.Value(CtxNamespaceByFD); v != nil {
return v.(NamespaceByFD)
}
return nil
}
+22
View File
@@ -247,3 +247,25 @@ const (
TCP_RACK_STATIC_REO_WND
TCP_RACK_NO_DUPTHRESH
)
// InterfaceRequest contains information about an adding interface.
type InterfaceRequest struct {
// Kind is the link type.
Kind string
// Name is the interface name.
Name string
// Addr is the hardware device address.
Addr []byte
// MTU is the maximum transmission unit.
MTU uint32
// Data is link type specific device properties.
Data any
}
// VethPeerReq contains information about a second interface of a new veth pair.
type VethPeerReq struct {
// Req is information about the second end of the new veth pair.
Req InterfaceRequest
// Stack is the stack where the second end has to be added.
Stack Stack
}
+2
View File
@@ -108,6 +108,8 @@ func (t *Task) contextValue(key any, isTaskGoroutine bool) any {
return t.k.GetDevGoferClient(t.k.ContainerName(t.containerID))
case inet.CtxStack:
return t.NetworkContext()
case inet.CtxNamespaceByFD:
return t.NetworkNamespaceByFD
case ktime.CtxRealtimeClock:
return t.k.RealtimeClock()
case limits.CtxLimits:
+27
View File
@@ -15,6 +15,9 @@
package kernel
import (
"gvisor.dev/gvisor/pkg/errors/linuxerr"
"gvisor.dev/gvisor/pkg/sentry/fsimpl/kernfs"
"gvisor.dev/gvisor/pkg/sentry/fsimpl/nsfs"
"gvisor.dev/gvisor/pkg/sentry/inet"
)
@@ -50,3 +53,27 @@ func (t *Task) GetNetworkNamespace() *inet.Namespace {
t.mu.Unlock()
return netns
}
// NetworkNamespaceByFD returns the network namespace associated with the specified descriptor.
func (t *Task) NetworkNamespaceByFD(fd int32) (*inet.Namespace, error) {
file := t.GetFile(fd)
if file == nil {
return nil, linuxerr.EBADF
}
defer file.DecRef(t)
d, ok := file.Dentry().Impl().(*kernfs.Dentry)
if !ok {
return nil, linuxerr.EINVAL
}
i, ok := d.Inode().(*nsfs.Inode)
if !ok {
return nil, linuxerr.EINVAL
}
ns, ok := i.Namespace().(*inet.Namespace)
if !ok {
return nil, linuxerr.EINVAL
}
ns.IncRef()
return ns, nil
}
+3
View File
@@ -45,7 +45,10 @@ go_library(
"//pkg/syserr",
"//pkg/tcpip",
"//pkg/tcpip/header",
"//pkg/tcpip/link/ethernet",
"//pkg/tcpip/link/packetsocket",
"//pkg/tcpip/link/tun",
"//pkg/tcpip/link/veth",
"//pkg/tcpip/network/ipv4",
"//pkg/tcpip/network/ipv6",
"//pkg/tcpip/stack",
+134 -12
View File
@@ -27,6 +27,9 @@ import (
"gvisor.dev/gvisor/pkg/syserr"
"gvisor.dev/gvisor/pkg/tcpip"
"gvisor.dev/gvisor/pkg/tcpip/header"
"gvisor.dev/gvisor/pkg/tcpip/link/ethernet"
"gvisor.dev/gvisor/pkg/tcpip/link/packetsocket"
"gvisor.dev/gvisor/pkg/tcpip/link/veth"
"gvisor.dev/gvisor/pkg/tcpip/network/ipv4"
"gvisor.dev/gvisor/pkg/tcpip/network/ipv6"
"gvisor.dev/gvisor/pkg/tcpip/stack"
@@ -104,18 +107,18 @@ func (s *Stack) RemoveInterface(idx int32) error {
// SetInterface implements inet.Stack.SetInterface.
func (s *Stack) SetInterface(ctx context.Context, msg *nlmsg.Message) *syserr.Error {
var ifinfomsg linux.InterfaceInfoMessage
attrs, ok := msg.GetData(&ifinfomsg)
attrsView, ok := msg.GetData(&ifinfomsg)
if !ok {
return syserr.ErrInvalidArgument
}
for !attrs.Empty() {
// The index is unspecified, search by the interface name.
ahdr, value, rest, ok := attrs.ParseFirst()
if !ok {
return syserr.ErrInvalidArgument
}
attrs = rest
switch ahdr.Type {
attrs, ok := attrsView.Parse()
if !ok {
return syserr.ErrInvalidArgument
}
ifname := ""
for attr := range attrs {
value := attrs[attr]
switch attr {
case linux.IFLA_IFNAME:
if len(value) < 1 {
return syserr.ErrInvalidArgument
@@ -124,23 +127,28 @@ func (s *Stack) SetInterface(ctx context.Context, msg *nlmsg.Message) *syserr.Er
// Device name changing isn't supported yet.
return syserr.ErrNotSupported
}
ifname := string(value[:len(value)-1])
ifname = value.String()
for idx, ifa := range s.Interfaces() {
if ifname == ifa.Name {
ifinfomsg.Index = idx
break
}
}
case linux.IFLA_MASTER:
case linux.IFLA_LINKINFO:
default:
ctx.Warningf("unexpected attribute: %x", ahdr.Type)
ctx.Warningf("unexpected attribute: %x", attr)
return syserr.ErrNotSupported
}
}
flags := msg.Header().Flags
if ifinfomsg.Index == 0 {
if flags&linux.NLM_F_CREATE != 0 {
return s.newInterface(ctx, msg, attrs)
}
return syserr.ErrNoDevice
}
flags := msg.Header().Flags
if flags&(linux.NLM_F_EXCL|linux.NLM_F_REPLACE) != 0 {
return syserr.ErrExists
}
@@ -156,9 +164,123 @@ func (s *Stack) SetInterface(ctx context.Context, msg *nlmsg.Message) *syserr.Er
}
// Netstack interfaces are always up.
}
return nil
}
const defaultMTU = 1500
func (s *Stack) newVeth(ctx context.Context, linkAttrs map[uint16]nlmsg.BytesView, linkInfoAttrs map[uint16]nlmsg.BytesView) *syserr.Error {
var (
linkInfoData map[uint16]nlmsg.BytesView
ifinfomsg linux.InterfaceInfoMessage
peerLinkAttrs map[uint16]nlmsg.BytesView
)
peerStack := s
peerName := ""
ifname := ""
if v, ok := linkAttrs[linux.IFLA_IFNAME]; ok {
ifname = v.String()
}
if value, ok := linkInfoAttrs[linux.IFLA_INFO_DATA]; ok {
linkInfoData, ok = nlmsg.AttrsView(value).Parse()
if !ok {
return syserr.ErrInvalidArgument
}
if v, ok := linkInfoData[linux.VETH_INFO_PEER]; ok {
attrsView := nlmsg.AttrsView(v[ifinfomsg.SizeBytes():])
if !ok {
return syserr.ErrInvalidArgument
}
peerLinkAttrs, ok = attrsView.Parse()
if !ok {
return syserr.ErrInvalidArgument
}
if v, ok = peerLinkAttrs[linux.IFLA_IFNAME]; ok {
peerName = v.String()
}
if v, ok = peerLinkAttrs[linux.IFLA_NET_NS_FD]; ok {
fd, ok := v.Uint32()
if !ok {
return syserr.ErrInvalidArgument
}
f := inet.NamespaceByFDFromContext(ctx)
if f == nil {
return syserr.ErrInvalidArgument
}
ns, err := f(int32(fd))
if err != nil {
return syserr.FromError(err)
}
defer ns.DecRef(ctx)
peerStack = ns.Stack().(*Stack)
}
}
}
ep, peerEP := veth.NewPair(defaultMTU)
id := tcpip.NICID(s.Stack.UniqueID())
peerID := tcpip.NICID(peerStack.Stack.UniqueID())
if ifname == "" {
ifname = fmt.Sprintf("veth%d", id)
}
err := s.Stack.CreateNICWithOptions(id, packetsocket.New(ethernet.New(ep)), stack.NICOptions{
Name: ifname,
})
if err != nil {
return syserr.TranslateNetstackError(err)
}
ep.SetStack(s.Stack, id)
if peerName == "" {
peerName = fmt.Sprintf("veth%d", peerID)
}
err = peerStack.Stack.CreateNICWithOptions(peerID, packetsocket.New(ethernet.New(peerEP)), stack.NICOptions{
Name: peerName,
})
if err != nil {
peerEP.Close()
return syserr.TranslateNetstackError(err)
}
peerEP.SetStack(peerStack.Stack, id)
return nil
}
func (s *Stack) newInterface(ctx context.Context, msg *nlmsg.Message, linkAttrs map[uint16]nlmsg.BytesView) *syserr.Error {
var (
linkInfoAttrs map[uint16]nlmsg.BytesView
kind string
)
if v, ok := linkAttrs[linux.IFLA_LINKINFO]; ok {
linkInfoAttrs, ok = nlmsg.AttrsView(v).Parse()
if !ok {
return syserr.ErrInvalidArgument
}
for attr := range linkInfoAttrs {
value := linkInfoAttrs[attr]
switch attr {
case linux.IFLA_INFO_KIND:
kind = value.String()
case linux.IFLA_INFO_DATA:
default:
ctx.Warningf("unexpected link info attribute: %x", attr)
return syserr.ErrNotSupported
}
}
}
switch kind {
case "":
return syserr.ErrInvalidArgument
case "veth":
return s.newVeth(ctx, linkAttrs, linkInfoAttrs)
}
return syserr.ErrNotSupported
}
// InterfaceAddrs implements inet.Stack.InterfaceAddrs.
func (s *Stack) InterfaceAddrs() map[int32][]inet.InterfaceAddr {
nicAddrs := make(map[int32][]inet.InterfaceAddr)
+18
View File
@@ -0,0 +1,18 @@
load("//tools:defs.bzl", "go_library")
package(
default_applicable_licenses = ["//:license"],
licenses = ["notice"],
)
go_library(
name = "veth",
srcs = ["veth.go"],
visibility = ["//visibility:public"],
deps = [
"//pkg/sync",
"//pkg/tcpip",
"//pkg/tcpip/header",
"//pkg/tcpip/stack",
],
)
+207
View File
@@ -0,0 +1,207 @@
// Copyright 2024 The gVisor Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package veth provides the implementation of virtual ethernet device pair.
package veth
import (
"gvisor.dev/gvisor/pkg/sync"
"gvisor.dev/gvisor/pkg/tcpip"
"gvisor.dev/gvisor/pkg/tcpip/header"
"gvisor.dev/gvisor/pkg/tcpip/stack"
)
var _ stack.LinkEndpoint = (*Endpoint)(nil)
var _ stack.GSOEndpoint = (*Endpoint)(nil)
// +stateify savable
type vethPacket struct {
e *Endpoint
protocol tcpip.NetworkProtocolNumber
pkt *stack.PacketBuffer
}
const backlogQueueSize = 64
// Endpoint is link layer endpoint that redirects packets to a pair veth endpoint.
//
// +stateify savable
type Endpoint struct {
pair *Endpoint
mtu uint32
backlogQueue *chan vethPacket
// linkAddr is the local address of this endpoint.
// linkaddr is immutable.
linkAddr tcpip.LinkAddress
mu sync.RWMutex `state:"nosave"`
// +checklocks:mu
dispatcher stack.NetworkDispatcher
// +checklocks:mu
stack *stack.Stack
// +checklocks:mu
idx tcpip.NICID
}
// NewPair creates a new veth pair.
func NewPair(mtu uint32) (*Endpoint, *Endpoint) {
backlogQueue := make(chan vethPacket, backlogQueueSize)
a := &Endpoint{
mtu: mtu,
linkAddr: tcpip.GetRandMacAddr(),
backlogQueue: &backlogQueue,
}
b := &Endpoint{
mtu: mtu,
pair: a,
linkAddr: tcpip.GetRandMacAddr(),
backlogQueue: &backlogQueue,
}
a.pair = b
go func() {
for t := range backlogQueue {
t.e.InjectInbound(t.protocol, t.pkt)
t.pkt.DecRef()
}
}()
return a, b
}
// SetStack stores the stack and the device index.
func (e *Endpoint) SetStack(s *stack.Stack, idx tcpip.NICID) {
e.mu.Lock()
defer e.mu.Unlock()
e.stack = s
e.idx = idx
}
// Close closes e. Further packet injections will return an error, and all pending
// packets are discarded. Close may be called concurrently with WritePackets.
func (e *Endpoint) Close() {
e.mu.Lock()
stack := e.stack
e.stack = nil
e.mu.Unlock()
if stack == nil {
return
}
e = e.pair
e.mu.Lock()
stack = e.stack
idx := e.idx
e.stack = nil
e.mu.Unlock()
if stack != nil {
stack.RemoveNIC(idx)
}
close(*e.backlogQueue)
}
// InjectInbound injects an inbound packet. If the endpoint is not attached, the
// packet is not delivered.
func (e *Endpoint) InjectInbound(protocol tcpip.NetworkProtocolNumber, pkt *stack.PacketBuffer) {
e.mu.RLock()
d := e.dispatcher
e.mu.RUnlock()
if d != nil {
d.DeliverNetworkPacket(protocol, pkt)
}
}
// Attach saves the stack network-layer dispatcher for use later when packets
// are injected.
func (e *Endpoint) Attach(dispatcher stack.NetworkDispatcher) {
e.mu.Lock()
defer e.mu.Unlock()
e.dispatcher = dispatcher
}
// IsAttached implements stack.LinkEndpoint.IsAttached.
func (e *Endpoint) IsAttached() bool {
e.mu.RLock()
defer e.mu.RUnlock()
return e.dispatcher != nil
}
// MTU implements stack.LinkEndpoint.MTU. It returns the value initialized
// during construction.
func (e *Endpoint) MTU() uint32 {
return e.mtu
}
// Capabilities implements stack.LinkEndpoint.Capabilities.
func (e *Endpoint) Capabilities() stack.LinkEndpointCapabilities {
return stack.CapabilityRXChecksumOffload | stack.CapabilityTXChecksumOffload | stack.CapabilitySaveRestore
}
// GSOMaxSize implements stack.GSOEndpoint.
func (*Endpoint) GSOMaxSize() uint32 {
return stack.GVisorGSOMaxSize
}
// SupportedGSO implements stack.GSOEndpoint.
func (e *Endpoint) SupportedGSO() stack.SupportedGSO {
return stack.GVisorGSOSupported
}
// MaxHeaderLength returns the maximum size of the link layer header. Given it
// doesn't have a header, it just returns 0.
func (*Endpoint) MaxHeaderLength() uint16 {
return 0
}
// LinkAddress returns the link address of this endpoint.
func (e *Endpoint) LinkAddress() tcpip.LinkAddress {
return e.linkAddr
}
// WritePackets stores outbound packets into the channel.
// Multiple concurrent calls are permitted.
func (e *Endpoint) WritePackets(pkts stack.PacketBufferList) (int, tcpip.Error) {
n := 0
for _, pkt := range pkts.AsSlice() {
// In order to properly loop back to the inbound side we must create a
// fresh packet that only contains the underlying payload with no headers
// or struct fields set.
newPkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
Payload: pkt.ToBuffer(),
})
(*e.backlogQueue) <- vethPacket{
e: e.pair,
protocol: pkt.NetworkProtocolNumber,
pkt: newPkt,
}
n++
}
return n, nil
}
// Wait implements stack.LinkEndpoint.Wait.
func (*Endpoint) Wait() {}
// ARPHardwareType implements stack.LinkEndpoint.ARPHardwareType.
func (*Endpoint) ARPHardwareType() header.ARPHardwareType {
return header.ARPHardwareNone
}
// AddHeader implements stack.LinkEndpoint.AddHeader.
func (e *Endpoint) AddHeader(pkt *stack.PacketBuffer) {}
// ParseHeader implements stack.LinkEndpoint.ParseHeader.
func (e *Endpoint) ParseHeader(pkt *stack.PacketBuffer) bool { return true }
+11
View File
@@ -35,6 +35,8 @@ import (
"io"
"math"
"math/bits"
"math/rand"
"net"
"reflect"
"strconv"
"strings"
@@ -2710,6 +2712,15 @@ func ParseMACAddress(s string) (LinkAddress, error) {
return LinkAddress(addr), nil
}
// GetRandMacAddr returns a mac address that can be used for local virtual devices.
func GetRandMacAddr() LinkAddress {
mac := make(net.HardwareAddr, 6)
rand.Read(mac) // Fill with random data.
mac[0] &^= 0x1 // Clear multicast bit.
mac[0] |= 0x2 // Set local assignment bit (IEEE802).
return LinkAddress(mac)
}
// AddressWithPrefix is an address with its subnet prefix length.
//
// +stateify savable
+1 -1
View File
@@ -255,7 +255,7 @@ func (n *Network) CreateLinksAndRoutes(args *CreateLinksAndRoutesArgs, _ *struct
// Loopback normally appear before other interfaces.
for _, link := range args.LoopbackLinks {
nicID++
nicID = tcpip.NICID(n.Stack.UniqueID())
nicids[link.Name] = nicID
linkEP := ethernet.New(loopback.New())
@@ -19,6 +19,8 @@
#include <linux/if.h>
#include <linux/netlink.h>
#include <linux/rtnetlink.h>
#include <linux/veth.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
@@ -1282,6 +1284,79 @@ TEST(NetlinkRouteTest, PasscredCreds) {
EXPECT_THAT(creds.gid, AnyOf(Eq(0), Eq(65534)));
}
#ifndef NLMSG_TAIL
#define NLMSG_TAIL(nmsg) \
((struct rtattr*)(((char*)(nmsg)) + NLMSG_ALIGN((nmsg)->nlmsg_len)))
#endif
void addattr(struct nlmsghdr* n, int maxlen, int type, const void* data,
int alen) {
int len = NLA_HDRLEN + alen;
struct rtattr* rta;
ASSERT_LE(NLMSG_ALIGN(n->nlmsg_len) + RTA_ALIGN(len), maxlen);
rta = NLMSG_TAIL(n);
rta->rta_type = type;
rta->rta_len = len;
memcpy(RTA_DATA(rta), data, alen);
n->nlmsg_len = NLMSG_ALIGN(n->nlmsg_len) + RTA_ALIGN(len);
}
TEST(NetlinkRouteTest, VethAdd) {
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_ADMIN)));
SKIP_IF(IsRunningWithHostinet());
Link loopback_link = ASSERT_NO_ERRNO_AND_VALUE(LoopbackLink());
FileDescriptor fd =
ASSERT_NO_ERRNO_AND_VALUE(NetlinkBoundSocket(NETLINK_ROUTE));
struct request {
struct nlmsghdr hdr;
struct ifinfomsg ifm;
char buf[1024];
};
struct request req = {};
req.hdr.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg));
req.hdr.nlmsg_type = RTM_NEWLINK;
req.hdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK | NLM_F_CREATE;
req.hdr.nlmsg_seq = kSeq;
req.ifm.ifi_family = AF_UNSPEC;
req.ifm.ifi_index = 0;
req.ifm.ifi_change = IFF_UP;
req.ifm.ifi_flags = IFF_UP;
const char veth_first[] = "veth_first";
addattr(&req.hdr, sizeof(req), IFLA_IFNAME, veth_first, strlen(veth_first));
struct rtattr* linkinfo;
linkinfo = NLMSG_TAIL(&req.hdr);
{
addattr(&req.hdr, sizeof(req), IFLA_LINKINFO, nullptr, 0);
addattr(&req.hdr, sizeof(req), IFLA_INFO_KIND, "veth", 4);
struct rtattr *veth_data, *peer_data;
veth_data = NLMSG_TAIL(&req.hdr);
{
addattr(&req.hdr, sizeof(req), IFLA_INFO_DATA, NULL, 0);
peer_data = NLMSG_TAIL(&req.hdr);
{
struct ifinfomsg ifm = {};
addattr(&req.hdr, sizeof(req), VETH_INFO_PEER, &ifm, sizeof(ifm));
const char veth_second[] = "veth_second";
addattr(&req.hdr, sizeof(req), IFLA_IFNAME, veth_second,
strlen(veth_second));
}
peer_data->rta_len = (uint64_t)NLMSG_TAIL(&req.hdr) - (uint64_t)peer_data;
}
veth_data->rta_len = (uint64_t)NLMSG_TAIL(&req.hdr) - (uint64_t)veth_data;
}
linkinfo->rta_len = (uint64_t)NLMSG_TAIL(&req.hdr) - (uint64_t)linkinfo;
EXPECT_NO_ERRNO(NetlinkRequestAckOrError(fd, kSeq, &req, req.hdr.nlmsg_len));
}
} // namespace
} // namespace testing