mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
Implement tap/tun device in vfs.
PiperOrigin-RevId: 296526279
This commit is contained in:
@@ -30,6 +30,7 @@ go_library(
|
||||
"futex.go",
|
||||
"inotify.go",
|
||||
"ioctl.go",
|
||||
"ioctl_tun.go",
|
||||
"ip.go",
|
||||
"ipc.go",
|
||||
"limits.go",
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
)
|
||||
@@ -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",
|
||||
],
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ go_library(
|
||||
srcs = ["channel.go"],
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//pkg/sync",
|
||||
"//pkg/tcpip",
|
||||
"//pkg/tcpip/buffer",
|
||||
"//pkg/tcpip/stack",
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
@@ -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:]))
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user