Implement tap/tun device in vfs.

PiperOrigin-RevId: 296526279
This commit is contained in:
Ting-Yu Wang
2020-02-21 15:42:56 -08:00
committed by gVisor bot
parent 10aa4d3b34
commit b8f56c79be
21 changed files with 1490 additions and 39 deletions
+1
View File
@@ -30,6 +30,7 @@ go_library(
"futex.go",
"inotify.go",
"ioctl.go",
"ioctl_tun.go",
"ip.go",
"ipc.go",
"limits.go",
+26
View File
@@ -72,3 +72,29 @@ const (
SIOCGMIIPHY = 0x8947
SIOCGMIIREG = 0x8948
)
// ioctl(2) directions. Used to calculate requests number.
// Constants from asm-generic/ioctl.h.
const (
_IOC_NONE = 0
_IOC_WRITE = 1
_IOC_READ = 2
)
// Constants from asm-generic/ioctl.h.
const (
_IOC_NRBITS = 8
_IOC_TYPEBITS = 8
_IOC_SIZEBITS = 14
_IOC_DIRBITS = 2
_IOC_NRSHIFT = 0
_IOC_TYPESHIFT = _IOC_NRSHIFT + _IOC_NRBITS
_IOC_SIZESHIFT = _IOC_TYPESHIFT + _IOC_TYPEBITS
_IOC_DIRSHIFT = _IOC_SIZESHIFT + _IOC_SIZEBITS
)
// IOC outputs the result of _IOC macro in asm-generic/ioctl.h.
func IOC(dir, typ, nr, size uint32) uint32 {
return uint32(dir)<<_IOC_DIRSHIFT | typ<<_IOC_TYPESHIFT | nr<<_IOC_NRSHIFT | size<<_IOC_SIZESHIFT
}
+29
View File
@@ -0,0 +1,29 @@
// Copyright 2020 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 linux
// ioctl(2) request numbers from linux/if_tun.h
var (
TUNSETIFF = IOC(_IOC_WRITE, 'T', 202, 4)
TUNGETIFF = IOC(_IOC_READ, 'T', 210, 4)
)
// Flags from net/if_tun.h
const (
IFF_TUN = 0x0001
IFF_TAP = 0x0002
IFF_NO_PI = 0x1000
IFF_NOFILTER = 0x1000
)
+5
View File
@@ -9,6 +9,7 @@ go_library(
"device.go",
"fs.go",
"full.go",
"net_tun.go",
"null.go",
"random.go",
"tty.go",
@@ -19,15 +20,19 @@ go_library(
"//pkg/context",
"//pkg/rand",
"//pkg/safemem",
"//pkg/sentry/arch",
"//pkg/sentry/device",
"//pkg/sentry/fs",
"//pkg/sentry/fs/fsutil",
"//pkg/sentry/fs/ramfs",
"//pkg/sentry/fs/tmpfs",
"//pkg/sentry/kernel",
"//pkg/sentry/memmap",
"//pkg/sentry/mm",
"//pkg/sentry/pgalloc",
"//pkg/sentry/socket/netstack",
"//pkg/syserror",
"//pkg/tcpip/link/tun",
"//pkg/usermem",
"//pkg/waiter",
],
+7 -3
View File
@@ -66,8 +66,8 @@ func newMemDevice(ctx context.Context, iops fs.InodeOperations, msrc *fs.MountSo
})
}
func newDirectory(ctx context.Context, msrc *fs.MountSource) *fs.Inode {
iops := ramfs.NewDir(ctx, nil, fs.RootOwner, fs.FilePermsFromMode(0555))
func newDirectory(ctx context.Context, contents map[string]*fs.Inode, msrc *fs.MountSource) *fs.Inode {
iops := ramfs.NewDir(ctx, contents, fs.RootOwner, fs.FilePermsFromMode(0555))
return fs.NewInode(ctx, iops, msrc, fs.StableAttr{
DeviceID: devDevice.DeviceID(),
InodeID: devDevice.NextIno(),
@@ -111,7 +111,7 @@ func New(ctx context.Context, msrc *fs.MountSource) *fs.Inode {
// A devpts is typically mounted at /dev/pts to provide
// pseudoterminal support. Place an empty directory there for
// the devpts to be mounted over.
"pts": newDirectory(ctx, msrc),
"pts": newDirectory(ctx, nil, msrc),
// Similarly, applications expect a ptmx device at /dev/ptmx
// connected to the terminals provided by /dev/pts/. Rather
// than creating a device directly (which requires a hairy
@@ -124,6 +124,10 @@ func New(ctx context.Context, msrc *fs.MountSource) *fs.Inode {
"ptmx": newSymlink(ctx, "pts/ptmx", msrc),
"tty": newCharacterDevice(ctx, newTTYDevice(ctx, fs.RootOwner, 0666), msrc, ttyDevMajor, ttyDevMinor),
"net": newDirectory(ctx, map[string]*fs.Inode{
"tun": newCharacterDevice(ctx, newNetTunDevice(ctx, fs.RootOwner, 0666), msrc, netTunDevMajor, netTunDevMinor),
}, msrc),
}
iops := ramfs.NewDir(ctx, contents, fs.RootOwner, fs.FilePermsFromMode(0555))
+170
View File
@@ -0,0 +1,170 @@
// Copyright 2020 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 dev
import (
"gvisor.dev/gvisor/pkg/abi/linux"
"gvisor.dev/gvisor/pkg/context"
"gvisor.dev/gvisor/pkg/sentry/arch"
"gvisor.dev/gvisor/pkg/sentry/fs"
"gvisor.dev/gvisor/pkg/sentry/fs/fsutil"
"gvisor.dev/gvisor/pkg/sentry/kernel"
"gvisor.dev/gvisor/pkg/sentry/socket/netstack"
"gvisor.dev/gvisor/pkg/syserror"
"gvisor.dev/gvisor/pkg/tcpip/link/tun"
"gvisor.dev/gvisor/pkg/usermem"
"gvisor.dev/gvisor/pkg/waiter"
)
const (
netTunDevMajor = 10
netTunDevMinor = 200
)
// +stateify savable
type netTunInodeOperations struct {
fsutil.InodeGenericChecker `state:"nosave"`
fsutil.InodeNoExtendedAttributes `state:"nosave"`
fsutil.InodeNoopAllocate `state:"nosave"`
fsutil.InodeNoopRelease `state:"nosave"`
fsutil.InodeNoopTruncate `state:"nosave"`
fsutil.InodeNoopWriteOut `state:"nosave"`
fsutil.InodeNotDirectory `state:"nosave"`
fsutil.InodeNotMappable `state:"nosave"`
fsutil.InodeNotSocket `state:"nosave"`
fsutil.InodeNotSymlink `state:"nosave"`
fsutil.InodeVirtual `state:"nosave"`
fsutil.InodeSimpleAttributes
}
var _ fs.InodeOperations = (*netTunInodeOperations)(nil)
func newNetTunDevice(ctx context.Context, owner fs.FileOwner, mode linux.FileMode) *netTunInodeOperations {
return &netTunInodeOperations{
InodeSimpleAttributes: fsutil.NewInodeSimpleAttributes(ctx, owner, fs.FilePermsFromMode(mode), linux.TMPFS_MAGIC),
}
}
// GetFile implements fs.InodeOperations.GetFile.
func (iops *netTunInodeOperations) GetFile(ctx context.Context, d *fs.Dirent, flags fs.FileFlags) (*fs.File, error) {
return fs.NewFile(ctx, d, flags, &netTunFileOperations{}), nil
}
// +stateify savable
type netTunFileOperations struct {
fsutil.FileNoSeek `state:"nosave"`
fsutil.FileNoMMap `state:"nosave"`
fsutil.FileNoSplice `state:"nosave"`
fsutil.FileNoopFlush `state:"nosave"`
fsutil.FileNoopFsync `state:"nosave"`
fsutil.FileNotDirReaddir `state:"nosave"`
fsutil.FileUseInodeUnstableAttr `state:"nosave"`
device tun.Device
}
var _ fs.FileOperations = (*netTunFileOperations)(nil)
// Release implements fs.FileOperations.Release.
func (fops *netTunFileOperations) Release() {
fops.device.Release()
}
// Ioctl implements fs.FileOperations.Ioctl.
func (fops *netTunFileOperations) Ioctl(ctx context.Context, file *fs.File, io usermem.IO, args arch.SyscallArguments) (uintptr, error) {
request := args[1].Uint()
data := args[2].Pointer()
switch request {
case linux.TUNSETIFF:
t := kernel.TaskFromContext(ctx)
if t == nil {
panic("Ioctl should be called from a task context")
}
if !t.HasCapability(linux.CAP_NET_ADMIN) {
return 0, syserror.EPERM
}
stack, ok := t.NetworkContext().(*netstack.Stack)
if !ok {
return 0, syserror.EINVAL
}
var req linux.IFReq
if _, err := usermem.CopyObjectIn(ctx, io, data, &req, usermem.IOOpts{
AddressSpaceActive: true,
}); err != nil {
return 0, err
}
flags := usermem.ByteOrder.Uint16(req.Data[:])
return 0, fops.device.SetIff(stack.Stack, req.Name(), flags)
case linux.TUNGETIFF:
var req linux.IFReq
copy(req.IFName[:], fops.device.Name())
// Linux adds IFF_NOFILTER (the same value as IFF_NO_PI unfortunately) when
// there is no sk_filter. See __tun_chr_ioctl() in net/drivers/tun.c.
flags := fops.device.Flags() | linux.IFF_NOFILTER
usermem.ByteOrder.PutUint16(req.Data[:], flags)
_, err := usermem.CopyObjectOut(ctx, io, data, &req, usermem.IOOpts{
AddressSpaceActive: true,
})
return 0, err
default:
return 0, syserror.ENOTTY
}
}
// Write implements fs.FileOperations.Write.
func (fops *netTunFileOperations) Write(ctx context.Context, file *fs.File, src usermem.IOSequence, offset int64) (int64, error) {
data := make([]byte, src.NumBytes())
if _, err := src.CopyIn(ctx, data); err != nil {
return 0, err
}
return fops.device.Write(data)
}
// Read implements fs.FileOperations.Read.
func (fops *netTunFileOperations) Read(ctx context.Context, file *fs.File, dst usermem.IOSequence, offset int64) (int64, error) {
data, err := fops.device.Read()
if err != nil {
return 0, err
}
n, err := dst.CopyOut(ctx, data)
if n > 0 && n < len(data) {
// Not an error for partial copying. Packet truncated.
err = nil
}
return int64(n), err
}
// Readiness implements watier.Waitable.Readiness.
func (fops *netTunFileOperations) Readiness(mask waiter.EventMask) waiter.EventMask {
return fops.device.Readiness(mask)
}
// EventRegister implements watier.Waitable.EventRegister.
func (fops *netTunFileOperations) EventRegister(e *waiter.Entry, mask waiter.EventMask) {
fops.device.EventRegister(e, mask)
}
// EventUnregister implements watier.Waitable.EventUnregister.
func (fops *netTunFileOperations) EventUnregister(e *waiter.Entry) {
fops.device.EventUnregister(e)
}
+1
View File
@@ -29,6 +29,7 @@ var (
EACCES = error(syscall.EACCES)
EAGAIN = error(syscall.EAGAIN)
EBADF = error(syscall.EBADF)
EBADFD = error(syscall.EBADFD)
EBUSY = error(syscall.EBUSY)
ECHILD = error(syscall.ECHILD)
ECONNREFUSED = error(syscall.ECONNREFUSED)
+6
View File
@@ -156,3 +156,9 @@ func (vv *VectorisedView) Append(vv2 VectorisedView) {
vv.views = append(vv.views, vv2.views...)
vv.size += vv2.size
}
// AppendView appends the given view into this vectorised view.
func (vv *VectorisedView) AppendView(v View) {
vv.views = append(vv.views, v)
vv.size += len(v)
}
+1
View File
@@ -7,6 +7,7 @@ go_library(
srcs = ["channel.go"],
visibility = ["//visibility:public"],
deps = [
"//pkg/sync",
"//pkg/tcpip",
"//pkg/tcpip/buffer",
"//pkg/tcpip/stack",
+145 -35
View File
@@ -20,6 +20,7 @@ package channel
import (
"context"
"gvisor.dev/gvisor/pkg/sync"
"gvisor.dev/gvisor/pkg/tcpip"
"gvisor.dev/gvisor/pkg/tcpip/buffer"
"gvisor.dev/gvisor/pkg/tcpip/stack"
@@ -33,6 +34,118 @@ type PacketInfo struct {
Route stack.Route
}
// Notification is the interface for receiving notification from the packet
// queue.
type Notification interface {
// WriteNotify will be called when a write happens to the queue.
WriteNotify()
}
// NotificationHandle is an opaque handle to the registered notification target.
// It can be used to unregister the notification when no longer interested.
//
// +stateify savable
type NotificationHandle struct {
n Notification
}
type queue struct {
// mu protects fields below.
mu sync.RWMutex
// c is the outbound packet channel. Sending to c should hold mu.
c chan PacketInfo
numWrite int
numRead int
notify []*NotificationHandle
}
func (q *queue) Close() {
close(q.c)
}
func (q *queue) Read() (PacketInfo, bool) {
q.mu.Lock()
defer q.mu.Unlock()
select {
case p := <-q.c:
q.numRead++
return p, true
default:
return PacketInfo{}, false
}
}
func (q *queue) ReadContext(ctx context.Context) (PacketInfo, bool) {
// We have to receive from channel without holding the lock, since it can
// block indefinitely. This will cause a window that numWrite - numRead
// produces a larger number, but won't go to negative. numWrite >= numRead
// still holds.
select {
case pkt := <-q.c:
q.mu.Lock()
defer q.mu.Unlock()
q.numRead++
return pkt, true
case <-ctx.Done():
return PacketInfo{}, false
}
}
func (q *queue) Write(p PacketInfo) bool {
wrote := false
// It's important to make sure nobody can see numWrite until we increment it,
// so numWrite >= numRead holds.
q.mu.Lock()
select {
case q.c <- p:
wrote = true
q.numWrite++
default:
}
notify := q.notify
q.mu.Unlock()
if wrote {
// Send notification outside of lock.
for _, h := range notify {
h.n.WriteNotify()
}
}
return wrote
}
func (q *queue) Num() int {
q.mu.RLock()
defer q.mu.RUnlock()
n := q.numWrite - q.numRead
if n < 0 {
panic("numWrite < numRead")
}
return n
}
func (q *queue) AddNotify(notify Notification) *NotificationHandle {
q.mu.Lock()
defer q.mu.Unlock()
h := &NotificationHandle{n: notify}
q.notify = append(q.notify, h)
return h
}
func (q *queue) RemoveNotify(handle *NotificationHandle) {
q.mu.Lock()
defer q.mu.Unlock()
// Make a copy, since we reads the array outside of lock when notifying.
notify := make([]*NotificationHandle, 0, len(q.notify))
for _, h := range q.notify {
if h != handle {
notify = append(notify, h)
}
}
q.notify = notify
}
// Endpoint is link layer endpoint that stores outbound packets in a channel
// and allows injection of inbound packets.
type Endpoint struct {
@@ -41,14 +154,16 @@ type Endpoint struct {
linkAddr tcpip.LinkAddress
LinkEPCapabilities stack.LinkEndpointCapabilities
// c is where outbound packets are queued.
c chan PacketInfo
// Outbound packet queue.
q *queue
}
// New creates a new channel endpoint.
func New(size int, mtu uint32, linkAddr tcpip.LinkAddress) *Endpoint {
return &Endpoint{
c: make(chan PacketInfo, size),
q: &queue{
c: make(chan PacketInfo, size),
},
mtu: mtu,
linkAddr: linkAddr,
}
@@ -57,43 +172,36 @@ func New(size int, mtu uint32, linkAddr tcpip.LinkAddress) *Endpoint {
// Close closes e. Further packet injections will panic. Reads continue to
// succeed until all packets are read.
func (e *Endpoint) Close() {
close(e.c)
e.q.Close()
}
// Read does non-blocking read for one packet from the outbound packet queue.
// Read does non-blocking read one packet from the outbound packet queue.
func (e *Endpoint) Read() (PacketInfo, bool) {
select {
case pkt := <-e.c:
return pkt, true
default:
return PacketInfo{}, false
}
return e.q.Read()
}
// ReadContext does blocking read for one packet from the outbound packet queue.
// It can be cancelled by ctx, and in this case, it returns false.
func (e *Endpoint) ReadContext(ctx context.Context) (PacketInfo, bool) {
select {
case pkt := <-e.c:
return pkt, true
case <-ctx.Done():
return PacketInfo{}, false
}
return e.q.ReadContext(ctx)
}
// Drain removes all outbound packets from the channel and counts them.
func (e *Endpoint) Drain() int {
c := 0
for {
select {
case <-e.c:
c++
default:
if _, ok := e.Read(); !ok {
return c
}
c++
}
}
// NumQueued returns the number of packet queued for outbound.
func (e *Endpoint) NumQueued() int {
return e.q.Num()
}
// InjectInbound injects an inbound packet.
func (e *Endpoint) InjectInbound(protocol tcpip.NetworkProtocolNumber, pkt tcpip.PacketBuffer) {
e.InjectLinkAddr(protocol, "", pkt)
@@ -155,10 +263,7 @@ func (e *Endpoint) WritePacket(r *stack.Route, gso *stack.GSO, protocol tcpip.Ne
Route: route,
}
select {
case e.c <- p:
default:
}
e.q.Write(p)
return nil
}
@@ -171,7 +276,6 @@ func (e *Endpoint) WritePackets(r *stack.Route, gso *stack.GSO, pkts []tcpip.Pac
route.Release()
payloadView := pkts[0].Data.ToView()
n := 0
packetLoop:
for _, pkt := range pkts {
off := pkt.DataOffset
size := pkt.DataSize
@@ -185,12 +289,10 @@ packetLoop:
Route: route,
}
select {
case e.c <- p:
n++
default:
break packetLoop
if !e.q.Write(p) {
break
}
n++
}
return n, nil
@@ -204,13 +306,21 @@ func (e *Endpoint) WriteRawPacket(vv buffer.VectorisedView) *tcpip.Error {
GSO: nil,
}
select {
case e.c <- p:
default:
}
e.q.Write(p)
return nil
}
// Wait implements stack.LinkEndpoint.Wait.
func (*Endpoint) Wait() {}
// AddNotify adds a notification target for receiving event about outgoing
// packets.
func (e *Endpoint) AddNotify(notify Notification) *NotificationHandle {
return e.q.AddNotify(notify)
}
// RemoveNotify removes handle from the list of notification targets.
func (e *Endpoint) RemoveNotify(handle *NotificationHandle) {
e.q.RemoveNotify(handle)
}
+17 -1
View File
@@ -4,6 +4,22 @@ package(licenses = ["notice"])
go_library(
name = "tun",
srcs = ["tun_unsafe.go"],
srcs = [
"device.go",
"protocol.go",
"tun_unsafe.go",
],
visibility = ["//visibility:public"],
deps = [
"//pkg/abi/linux",
"//pkg/refs",
"//pkg/sync",
"//pkg/syserror",
"//pkg/tcpip",
"//pkg/tcpip/buffer",
"//pkg/tcpip/header",
"//pkg/tcpip/link/channel",
"//pkg/tcpip/stack",
"//pkg/waiter",
],
)
+352
View File
@@ -0,0 +1,352 @@
// Copyright 2020 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 tun
import (
"fmt"
"gvisor.dev/gvisor/pkg/abi/linux"
"gvisor.dev/gvisor/pkg/refs"
"gvisor.dev/gvisor/pkg/sync"
"gvisor.dev/gvisor/pkg/syserror"
"gvisor.dev/gvisor/pkg/tcpip"
"gvisor.dev/gvisor/pkg/tcpip/buffer"
"gvisor.dev/gvisor/pkg/tcpip/header"
"gvisor.dev/gvisor/pkg/tcpip/link/channel"
"gvisor.dev/gvisor/pkg/tcpip/stack"
"gvisor.dev/gvisor/pkg/waiter"
)
const (
// drivers/net/tun.c:tun_net_init()
defaultDevMtu = 1500
// Queue length for outbound packet, arriving at fd side for read. Overflow
// causes packet drops. gVisor implementation-specific.
defaultDevOutQueueLen = 1024
)
var zeroMAC [6]byte
// Device is an opened /dev/net/tun device.
//
// +stateify savable
type Device struct {
waiter.Queue
mu sync.RWMutex `state:"nosave"`
endpoint *tunEndpoint
notifyHandle *channel.NotificationHandle
flags uint16
}
// beforeSave is invoked by stateify.
func (d *Device) beforeSave() {
d.mu.Lock()
defer d.mu.Unlock()
// TODO(b/110961832): Restore the device to stack. At this moment, the stack
// is not savable.
if d.endpoint != nil {
panic("/dev/net/tun does not support save/restore when a device is associated with it.")
}
}
// Release implements fs.FileOperations.Release.
func (d *Device) Release() {
d.mu.Lock()
defer d.mu.Unlock()
// Decrease refcount if there is an endpoint associated with this file.
if d.endpoint != nil {
d.endpoint.RemoveNotify(d.notifyHandle)
d.endpoint.DecRef()
d.endpoint = nil
}
}
// SetIff services TUNSETIFF ioctl(2) request.
func (d *Device) SetIff(s *stack.Stack, name string, flags uint16) error {
d.mu.Lock()
defer d.mu.Unlock()
if d.endpoint != nil {
return syserror.EINVAL
}
// Input validations.
isTun := flags&linux.IFF_TUN != 0
isTap := flags&linux.IFF_TAP != 0
supportedFlags := uint16(linux.IFF_TUN | linux.IFF_TAP | linux.IFF_NO_PI)
if isTap && isTun || !isTap && !isTun || flags&^supportedFlags != 0 {
return syserror.EINVAL
}
prefix := "tun"
if isTap {
prefix = "tap"
}
endpoint, err := attachOrCreateNIC(s, name, prefix)
if err != nil {
return syserror.EINVAL
}
d.endpoint = endpoint
d.notifyHandle = d.endpoint.AddNotify(d)
d.flags = flags
return nil
}
func attachOrCreateNIC(s *stack.Stack, name, prefix string) (*tunEndpoint, error) {
for {
// 1. Try to attach to an existing NIC.
if name != "" {
if nic, found := s.GetNICByName(name); found {
endpoint, ok := nic.LinkEndpoint().(*tunEndpoint)
if !ok {
// Not a NIC created by tun device.
return nil, syserror.EOPNOTSUPP
}
if !endpoint.TryIncRef() {
// Race detected: NIC got deleted in between.
continue
}
return endpoint, nil
}
}
// 2. Creating a new NIC.
id := tcpip.NICID(s.UniqueID())
endpoint := &tunEndpoint{
Endpoint: channel.New(defaultDevOutQueueLen, defaultDevMtu, ""),
stack: s,
nicID: id,
name: name,
}
if endpoint.name == "" {
endpoint.name = fmt.Sprintf("%s%d", prefix, id)
}
err := s.CreateNICWithOptions(endpoint.nicID, endpoint, stack.NICOptions{
Name: endpoint.name,
})
switch err {
case nil:
return endpoint, nil
case tcpip.ErrDuplicateNICID:
// Race detected: A NIC has been created in between.
continue
default:
return nil, syserror.EINVAL
}
}
}
// Write inject one inbound packet to the network interface.
func (d *Device) Write(data []byte) (int64, error) {
d.mu.RLock()
endpoint := d.endpoint
d.mu.RUnlock()
if endpoint == nil {
return 0, syserror.EBADFD
}
if !endpoint.IsAttached() {
return 0, syserror.EIO
}
dataLen := int64(len(data))
// Packet information.
var pktInfoHdr PacketInfoHeader
if !d.hasFlags(linux.IFF_NO_PI) {
if len(data) < PacketInfoHeaderSize {
// Ignore bad packet.
return dataLen, nil
}
pktInfoHdr = PacketInfoHeader(data[:PacketInfoHeaderSize])
data = data[PacketInfoHeaderSize:]
}
// Ethernet header (TAP only).
var ethHdr header.Ethernet
if d.hasFlags(linux.IFF_TAP) {
if len(data) < header.EthernetMinimumSize {
// Ignore bad packet.
return dataLen, nil
}
ethHdr = header.Ethernet(data[:header.EthernetMinimumSize])
data = data[header.EthernetMinimumSize:]
}
// Try to determine network protocol number, default zero.
var protocol tcpip.NetworkProtocolNumber
switch {
case pktInfoHdr != nil:
protocol = pktInfoHdr.Protocol()
case ethHdr != nil:
protocol = ethHdr.Type()
}
// Try to determine remote link address, default zero.
var remote tcpip.LinkAddress
switch {
case ethHdr != nil:
remote = ethHdr.SourceAddress()
default:
remote = tcpip.LinkAddress(zeroMAC[:])
}
pkt := tcpip.PacketBuffer{
Data: buffer.View(data).ToVectorisedView(),
}
if ethHdr != nil {
pkt.LinkHeader = buffer.View(ethHdr)
}
endpoint.InjectLinkAddr(protocol, remote, pkt)
return dataLen, nil
}
// Read reads one outgoing packet from the network interface.
func (d *Device) Read() ([]byte, error) {
d.mu.RLock()
endpoint := d.endpoint
d.mu.RUnlock()
if endpoint == nil {
return nil, syserror.EBADFD
}
for {
info, ok := endpoint.Read()
if !ok {
return nil, syserror.ErrWouldBlock
}
v, ok := d.encodePkt(&info)
if !ok {
// Ignore unsupported packet.
continue
}
return v, nil
}
}
// encodePkt encodes packet for fd side.
func (d *Device) encodePkt(info *channel.PacketInfo) (buffer.View, bool) {
var vv buffer.VectorisedView
// Packet information.
if !d.hasFlags(linux.IFF_NO_PI) {
hdr := make(PacketInfoHeader, PacketInfoHeaderSize)
hdr.Encode(&PacketInfoFields{
Protocol: info.Proto,
})
vv.AppendView(buffer.View(hdr))
}
// If the packet does not already have link layer header, and the route
// does not exist, we can't compute it. This is possibly a raw packet, tun
// device doesn't support this at the moment.
if info.Pkt.LinkHeader == nil && info.Route.RemoteLinkAddress == "" {
return nil, false
}
// Ethernet header (TAP only).
if d.hasFlags(linux.IFF_TAP) {
// Add ethernet header if not provided.
if info.Pkt.LinkHeader == nil {
hdr := &header.EthernetFields{
SrcAddr: info.Route.LocalLinkAddress,
DstAddr: info.Route.RemoteLinkAddress,
Type: info.Proto,
}
if hdr.SrcAddr == "" {
hdr.SrcAddr = d.endpoint.LinkAddress()
}
eth := make(header.Ethernet, header.EthernetMinimumSize)
eth.Encode(hdr)
vv.AppendView(buffer.View(eth))
} else {
vv.AppendView(info.Pkt.LinkHeader)
}
}
// Append upper headers.
vv.AppendView(buffer.View(info.Pkt.Header.View()[len(info.Pkt.LinkHeader):]))
// Append data payload.
vv.Append(info.Pkt.Data)
return vv.ToView(), true
}
// Name returns the name of the attached network interface. Empty string if
// unattached.
func (d *Device) Name() string {
d.mu.RLock()
defer d.mu.RUnlock()
if d.endpoint != nil {
return d.endpoint.name
}
return ""
}
// Flags returns the flags set for d. Zero value if unset.
func (d *Device) Flags() uint16 {
d.mu.RLock()
defer d.mu.RUnlock()
return d.flags
}
func (d *Device) hasFlags(flags uint16) bool {
return d.flags&flags == flags
}
// Readiness implements watier.Waitable.Readiness.
func (d *Device) Readiness(mask waiter.EventMask) waiter.EventMask {
if mask&waiter.EventIn != 0 {
d.mu.RLock()
endpoint := d.endpoint
d.mu.RUnlock()
if endpoint != nil && endpoint.NumQueued() == 0 {
mask &= ^waiter.EventIn
}
}
return mask & (waiter.EventIn | waiter.EventOut)
}
// WriteNotify implements channel.Notification.WriteNotify.
func (d *Device) WriteNotify() {
d.Notify(waiter.EventIn)
}
// tunEndpoint is the link endpoint for the NIC created by the tun device.
//
// It is ref-counted as multiple opening files can attach to the same NIC.
// The last owner is responsible for deleting the NIC.
type tunEndpoint struct {
*channel.Endpoint
refs.AtomicRefCount
stack *stack.Stack
nicID tcpip.NICID
name string
}
// DecRef decrements refcount of e, removes NIC if refcount goes to 0.
func (e *tunEndpoint) DecRef() {
e.DecRefWithDestructor(func() {
e.stack.RemoveNIC(e.nicID)
})
}
+56
View File
@@ -0,0 +1,56 @@
// Copyright 2020 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 tun
import (
"encoding/binary"
"gvisor.dev/gvisor/pkg/tcpip"
)
const (
// PacketInfoHeaderSize is the size of the packet information header.
PacketInfoHeaderSize = 4
offsetFlags = 0
offsetProtocol = 2
)
// PacketInfoFields contains fields sent through the wire if IFF_NO_PI flag is
// not set.
type PacketInfoFields struct {
Flags uint16
Protocol tcpip.NetworkProtocolNumber
}
// PacketInfoHeader is the wire representation of the packet information sent if
// IFF_NO_PI flag is not set.
type PacketInfoHeader []byte
// Encode encodes f into h.
func (h PacketInfoHeader) Encode(f *PacketInfoFields) {
binary.BigEndian.PutUint16(h[offsetFlags:][:2], f.Flags)
binary.BigEndian.PutUint16(h[offsetProtocol:][:2], uint16(f.Protocol))
}
// Flags returns the flag field in h.
func (h PacketInfoHeader) Flags() uint16 {
return binary.BigEndian.Uint16(h[offsetFlags:])
}
// Protocol returns the protocol field in h.
func (h PacketInfoHeader) Protocol() tcpip.NetworkProtocolNumber {
return tcpip.NetworkProtocolNumber(binary.BigEndian.Uint16(h[offsetProtocol:]))
}
+32
View File
@@ -298,6 +298,33 @@ func (n *NIC) enable() *tcpip.Error {
return nil
}
// remove detaches NIC from the link endpoint, and marks existing referenced
// network endpoints expired. This guarantees no packets between this NIC and
// the network stack.
func (n *NIC) remove() *tcpip.Error {
n.mu.Lock()
defer n.mu.Unlock()
// Detach from link endpoint, so no packet comes in.
n.linkEP.Attach(nil)
// Remove permanent and permanentTentative addresses, so no packet goes out.
var errs []*tcpip.Error
for nid, ref := range n.mu.endpoints {
switch ref.getKind() {
case permanentTentative, permanent:
if err := n.removePermanentAddressLocked(nid.LocalAddress); err != nil {
errs = append(errs, err)
}
}
}
if len(errs) > 0 {
return errs[0]
}
return nil
}
// becomeIPv6Router transitions n into an IPv6 router.
//
// When transitioning into an IPv6 router, host-only state (NDP discovered
@@ -1302,6 +1329,11 @@ func (n *NIC) Stack() *Stack {
return n.stack
}
// LinkEndpoint returns the link endpoint of n.
func (n *NIC) LinkEndpoint() LinkEndpoint {
return n.linkEP
}
// isAddrTentative returns true if addr is tentative on n.
//
// Note that if addr is not associated with n, then this function will return
+39
View File
@@ -916,6 +916,18 @@ func (s *Stack) CreateNIC(id tcpip.NICID, ep LinkEndpoint) *tcpip.Error {
return s.CreateNICWithOptions(id, ep, NICOptions{})
}
// GetNICByName gets the NIC specified by name.
func (s *Stack) GetNICByName(name string) (*NIC, bool) {
s.mu.RLock()
defer s.mu.RUnlock()
for _, nic := range s.nics {
if nic.Name() == name {
return nic, true
}
}
return nil, false
}
// EnableNIC enables the given NIC so that the link-layer endpoint can start
// delivering packets to it.
func (s *Stack) EnableNIC(id tcpip.NICID) *tcpip.Error {
@@ -956,6 +968,33 @@ func (s *Stack) CheckNIC(id tcpip.NICID) bool {
return nic.enabled()
}
// RemoveNIC removes NIC and all related routes from the network stack.
func (s *Stack) RemoveNIC(id tcpip.NICID) *tcpip.Error {
s.mu.Lock()
defer s.mu.Unlock()
nic, ok := s.nics[id]
if !ok {
return tcpip.ErrUnknownNICID
}
delete(s.nics, id)
// Remove routes in-place. n tracks the number of routes written.
n := 0
for i, r := range s.routeTable {
if r.NIC != id {
// Keep this route.
if i > n {
s.routeTable[n] = r
}
n++
}
}
s.routeTable = s.routeTable[:n]
return nic.remove()
}
// NICAddressRanges returns a map of NICIDs to their associated subnets.
func (s *Stack) NICAddressRanges() map[tcpip.NICID][]tcpip.Subnet {
s.mu.RLock()
+2
View File
@@ -678,6 +678,8 @@ syscall_test(
test = "//test/syscalls/linux:truncate_test",
)
syscall_test(test = "//test/syscalls/linux:tuntap_test")
syscall_test(test = "//test/syscalls/linux:udp_bind_test")
syscall_test(
+30
View File
@@ -131,6 +131,17 @@ cc_library(
],
)
cc_library(
name = "socket_netlink_route_util",
testonly = 1,
srcs = ["socket_netlink_route_util.cc"],
hdrs = ["socket_netlink_route_util.h"],
deps = [
":socket_netlink_util",
"@com_google_absl//absl/types:optional",
],
)
cc_library(
name = "socket_test_util",
testonly = 1,
@@ -3430,6 +3441,25 @@ cc_binary(
],
)
cc_binary(
name = "tuntap_test",
testonly = 1,
srcs = ["tuntap.cc"],
linkstatic = 1,
deps = [
":socket_test_util",
gtest,
"//test/syscalls/linux:socket_netlink_route_util",
"//test/util:capability_util",
"//test/util:file_descriptor",
"//test/util:fs_util",
"//test/util:posix_error",
"//test/util:test_main",
"//test/util:test_util",
"@com_google_absl//absl/strings",
],
)
cc_library(
name = "udp_socket_test_cases",
testonly = 1,
+7
View File
@@ -153,6 +153,13 @@ TEST(DevTest, TTYExists) {
EXPECT_EQ(statbuf.st_mode, S_IFCHR | 0666);
}
TEST(DevTest, NetTunExists) {
struct stat statbuf = {};
ASSERT_THAT(stat("/dev/net/tun", &statbuf), SyscallSucceeds());
// Check that it's a character device with rw-rw-rw- permissions.
EXPECT_EQ(statbuf.st_mode, S_IFCHR | 0666);
}
} // namespace
} // namespace testing
@@ -0,0 +1,163 @@
// Copyright 2020 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.
#include "test/syscalls/linux/socket_netlink_route_util.h"
#include <linux/if.h>
#include <linux/netlink.h>
#include <linux/rtnetlink.h>
#include "absl/types/optional.h"
#include "test/syscalls/linux/socket_netlink_util.h"
namespace gvisor {
namespace testing {
namespace {
constexpr uint32_t kSeq = 12345;
} // namespace
PosixError DumpLinks(
const FileDescriptor& fd, uint32_t seq,
const std::function<void(const struct nlmsghdr* hdr)>& fn) {
struct request {
struct nlmsghdr hdr;
struct ifinfomsg ifm;
};
struct request req = {};
req.hdr.nlmsg_len = sizeof(req);
req.hdr.nlmsg_type = RTM_GETLINK;
req.hdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP;
req.hdr.nlmsg_seq = seq;
req.ifm.ifi_family = AF_UNSPEC;
return NetlinkRequestResponse(fd, &req, sizeof(req), fn, false);
}
PosixErrorOr<std::vector<Link>> DumpLinks() {
ASSIGN_OR_RETURN_ERRNO(FileDescriptor fd, NetlinkBoundSocket(NETLINK_ROUTE));
std::vector<Link> links;
RETURN_IF_ERRNO(DumpLinks(fd, kSeq, [&](const struct nlmsghdr* hdr) {
if (hdr->nlmsg_type != RTM_NEWLINK ||
hdr->nlmsg_len < NLMSG_SPACE(sizeof(struct ifinfomsg))) {
return;
}
const struct ifinfomsg* msg =
reinterpret_cast<const struct ifinfomsg*>(NLMSG_DATA(hdr));
const auto* rta = FindRtAttr(hdr, msg, IFLA_IFNAME);
if (rta == nullptr) {
// Ignore links that do not have a name.
return;
}
links.emplace_back();
links.back().index = msg->ifi_index;
links.back().type = msg->ifi_type;
links.back().name =
std::string(reinterpret_cast<const char*>(RTA_DATA(rta)));
}));
return links;
}
PosixErrorOr<absl::optional<Link>> FindLoopbackLink() {
ASSIGN_OR_RETURN_ERRNO(auto links, DumpLinks());
for (const auto& link : links) {
if (link.type == ARPHRD_LOOPBACK) {
return absl::optional<Link>(link);
}
}
return absl::optional<Link>();
}
PosixError LinkAddLocalAddr(int index, int family, int prefixlen,
const void* addr, int addrlen) {
ASSIGN_OR_RETURN_ERRNO(FileDescriptor fd, NetlinkBoundSocket(NETLINK_ROUTE));
struct request {
struct nlmsghdr hdr;
struct ifaddrmsg ifaddr;
char attrbuf[512];
};
struct request req = {};
req.hdr.nlmsg_len = NLMSG_LENGTH(sizeof(req.ifaddr));
req.hdr.nlmsg_type = RTM_NEWADDR;
req.hdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
req.hdr.nlmsg_seq = kSeq;
req.ifaddr.ifa_index = index;
req.ifaddr.ifa_family = family;
req.ifaddr.ifa_prefixlen = prefixlen;
struct rtattr* rta = reinterpret_cast<struct rtattr*>(
reinterpret_cast<int8_t*>(&req) + NLMSG_ALIGN(req.hdr.nlmsg_len));
rta->rta_type = IFA_LOCAL;
rta->rta_len = RTA_LENGTH(addrlen);
req.hdr.nlmsg_len = NLMSG_ALIGN(req.hdr.nlmsg_len) + RTA_LENGTH(addrlen);
memcpy(RTA_DATA(rta), addr, addrlen);
return NetlinkRequestAckOrError(fd, kSeq, &req, req.hdr.nlmsg_len);
}
PosixError LinkChangeFlags(int index, unsigned int flags, unsigned int change) {
ASSIGN_OR_RETURN_ERRNO(FileDescriptor fd, NetlinkBoundSocket(NETLINK_ROUTE));
struct request {
struct nlmsghdr hdr;
struct ifinfomsg ifinfo;
char pad[NLMSG_ALIGNTO];
};
struct request req = {};
req.hdr.nlmsg_len = NLMSG_LENGTH(sizeof(req.ifinfo));
req.hdr.nlmsg_type = RTM_NEWLINK;
req.hdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
req.hdr.nlmsg_seq = kSeq;
req.ifinfo.ifi_index = index;
req.ifinfo.ifi_flags = flags;
req.ifinfo.ifi_change = change;
return NetlinkRequestAckOrError(fd, kSeq, &req, req.hdr.nlmsg_len);
}
PosixError LinkSetMacAddr(int index, const void* addr, int addrlen) {
ASSIGN_OR_RETURN_ERRNO(FileDescriptor fd, NetlinkBoundSocket(NETLINK_ROUTE));
struct request {
struct nlmsghdr hdr;
struct ifinfomsg ifinfo;
char attrbuf[512];
};
struct request req = {};
req.hdr.nlmsg_len = NLMSG_LENGTH(sizeof(req.ifinfo));
req.hdr.nlmsg_type = RTM_NEWLINK;
req.hdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
req.hdr.nlmsg_seq = kSeq;
req.ifinfo.ifi_index = index;
struct rtattr* rta = reinterpret_cast<struct rtattr*>(
reinterpret_cast<int8_t*>(&req) + NLMSG_ALIGN(req.hdr.nlmsg_len));
rta->rta_type = IFLA_ADDRESS;
rta->rta_len = RTA_LENGTH(addrlen);
req.hdr.nlmsg_len = NLMSG_ALIGN(req.hdr.nlmsg_len) + RTA_LENGTH(addrlen);
memcpy(RTA_DATA(rta), addr, addrlen);
return NetlinkRequestAckOrError(fd, kSeq, &req, req.hdr.nlmsg_len);
}
} // namespace testing
} // namespace gvisor
@@ -0,0 +1,55 @@
// Copyright 2020 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.
#ifndef GVISOR_TEST_SYSCALLS_LINUX_SOCKET_NETLINK_ROUTE_UTIL_H_
#define GVISOR_TEST_SYSCALLS_LINUX_SOCKET_NETLINK_ROUTE_UTIL_H_
#include <linux/netlink.h>
#include <linux/rtnetlink.h>
#include <vector>
#include "absl/types/optional.h"
#include "test/syscalls/linux/socket_netlink_util.h"
namespace gvisor {
namespace testing {
struct Link {
int index;
int16_t type;
std::string name;
};
PosixError DumpLinks(const FileDescriptor& fd, uint32_t seq,
const std::function<void(const struct nlmsghdr* hdr)>& fn);
PosixErrorOr<std::vector<Link>> DumpLinks();
PosixErrorOr<absl::optional<Link>> FindLoopbackLink();
// LinkAddLocalAddr sets IFA_LOCAL attribute on the interface.
PosixError LinkAddLocalAddr(int index, int family, int prefixlen,
const void* addr, int addrlen);
// LinkChangeFlags changes interface flags. E.g. IFF_UP.
PosixError LinkChangeFlags(int index, unsigned int flags, unsigned int change);
// LinkSetMacAddr sets IFLA_ADDRESS attribute of the interface.
PosixError LinkSetMacAddr(int index, const void* addr, int addrlen);
} // namespace testing
} // namespace gvisor
#endif // GVISOR_TEST_SYSCALLS_LINUX_SOCKET_NETLINK_ROUTE_UTIL_H_

Some files were not shown because too many files have changed in this diff Show More