net/tstun: add start of Linux TAP support, with DHCP+ARP server

Still very much a prototype (hard-coded IPs, etc) but should be
non-invasive enough to submit at this point and iterate from here.

Updates #2589

Co-Author: David Crawshaw <crawshaw@tailscale.com>
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
This commit is contained in:
Brad Fitzpatrick
2021-07-23 09:45:04 -07:00
committed by Brad Fitzpatrick
parent fd7b738e5b
commit a729070252
9 changed files with 498 additions and 11 deletions

View File

@@ -8,8 +8,10 @@ package tstun
import (
"errors"
"fmt"
"io"
"os"
"strings"
"sync"
"sync/atomic"
"time"
@@ -21,6 +23,7 @@ import (
"tailscale.com/tstime/mono"
"tailscale.com/types/ipproto"
"tailscale.com/types/logger"
"tailscale.com/types/pad32"
"tailscale.com/wgengine/filter"
)
@@ -35,6 +38,8 @@ const PacketStartOffset = device.MessageTransportHeaderSize
// of a packet that can be injected into a tstun.Wrapper.
const MaxPacketSize = device.MaxContentSize
const tapDebug = false // for super verbose TAP debugging
var (
// ErrClosed is returned when attempting an operation on a closed Wrapper.
ErrClosed = errors.New("device closed")
@@ -61,13 +66,16 @@ type FilterFunc func(*packet.Parsed, *Wrapper) filter.Response
type Wrapper struct {
logf logger.Logf
// tdev is the underlying Wrapper device.
tdev tun.Device
tdev tun.Device
isTAP bool // whether tdev is a TAP device
closeOnce sync.Once
_ pad32.Four
lastActivityAtomic mono.Time // time of last send or receive
destIPActivity atomic.Value // of map[netaddr.IP]func()
destMACAtomic atomic.Value // of [6]byte
// buffer stores the oldest unconsumed packet from tdev.
// It is made a static buffer in order to avoid allocations.
@@ -146,10 +154,19 @@ type tunReadResult struct {
err error
}
func WrapTAP(logf logger.Logf, tdev tun.Device) *Wrapper {
return wrap(logf, tdev, true)
}
func Wrap(logf logger.Logf, tdev tun.Device) *Wrapper {
return wrap(logf, tdev, false)
}
func wrap(logf logger.Logf, tdev tun.Device, isTAP bool) *Wrapper {
tun := &Wrapper{
logf: logger.WithPrefix(logf, "tstun: "),
tdev: tdev,
logf: logger.WithPrefix(logf, "tstun: "),
isTAP: isTAP,
tdev: tdev,
// bufferConsumed is conceptually a condition variable:
// a goroutine should not block when setting it, even with no listeners.
bufferConsumed: make(chan struct{}, 1),
@@ -284,11 +301,14 @@ func allowSendOnClosedChannel() {
panic(r)
}
const ethernetFrameSize = 14 // 2 six byte MACs, 2 bytes ethertype
// poll polls t.tdev.Read, placing the oldest unconsumed packet into t.buffer.
// This is needed because t.tdev.Read in general may block (it does on Windows),
// so packets may be stuck in t.outbound if t.Read called t.tdev.Read directly.
func (t *Wrapper) poll() {
for range t.bufferConsumed {
DoRead:
var n int
var err error
// Read may use memory in t.buffer before PacketStartOffset for mandatory headers.
@@ -303,7 +323,33 @@ func (t *Wrapper) poll() {
if t.isClosed() {
return
}
n, err = t.tdev.Read(t.buffer[:], PacketStartOffset)
if t.isTAP {
n, err = t.tdev.Read(t.buffer[:], PacketStartOffset-ethernetFrameSize)
if tapDebug {
s := fmt.Sprintf("% x", t.buffer[:])
for strings.HasSuffix(s, " 00") {
s = strings.TrimSuffix(s, " 00")
}
t.logf("TAP read %v, %v: %s", n, err, s)
}
} else {
n, err = t.tdev.Read(t.buffer[:], PacketStartOffset)
}
}
if t.isTAP {
if err == nil {
ethernetFrame := t.buffer[PacketStartOffset-ethernetFrameSize:][:n]
if t.handleTAPFrame(ethernetFrame) {
goto DoRead
}
}
// Fall through. We got an IP packet.
if n >= ethernetFrameSize {
n -= ethernetFrameSize
}
if tapDebug {
t.logf("tap regular frame: %x", t.buffer[PacketStartOffset:PacketStartOffset+n])
}
}
t.sendOutbound(tunReadResult{data: t.buffer[PacketStartOffset : PacketStartOffset+n], err: err})
}
@@ -521,6 +567,13 @@ func (t *Wrapper) Write(buf []byte, offset int) (int, error) {
}
t.noteActivity()
return t.tdevWrite(buf, offset)
}
func (t *Wrapper) tdevWrite(buf []byte, offset int) (int, error) {
if t.isTAP {
return t.tapWrite(buf, offset)
}
return t.tdev.Write(buf, offset)
}
@@ -553,7 +606,7 @@ func (t *Wrapper) InjectInboundDirect(buf []byte, offset int) error {
}
// Write to the underlying device to skip filters.
_, err := t.tdev.Write(buf, offset)
_, err := t.tdevWrite(buf, offset)
return err
}