net/tstun: restore tap mode functionality

It had bit-rotted likely during the transition to vector io in
76389d8baf. Tested on Ubuntu 24.04
by creating a netns and doing the DHCP dance to get an IP.

Updates #2589

Signed-off-by: Maisem Ali <maisem@tailscale.com>
This commit is contained in:
Maisem Ali
2024-10-21 16:17:28 -07:00
committed by Maisem Ali
parent 0f4c9c0ecb
commit d4d21a0bbf
6 changed files with 88 additions and 90 deletions
+74 -47
View File
@@ -6,6 +6,7 @@
package tstun
import (
"bytes"
"fmt"
"net"
"net/netip"
@@ -20,10 +21,13 @@ import (
"gvisor.dev/gvisor/pkg/tcpip/checksum"
"gvisor.dev/gvisor/pkg/tcpip/header"
"gvisor.dev/gvisor/pkg/tcpip/network/ipv4"
"gvisor.dev/gvisor/pkg/tcpip/network/ipv6"
"gvisor.dev/gvisor/pkg/tcpip/transport/udp"
"tailscale.com/net/netaddr"
"tailscale.com/net/packet"
"tailscale.com/syncs"
"tailscale.com/types/ipproto"
"tailscale.com/types/logger"
"tailscale.com/util/multierr"
)
@@ -35,13 +39,13 @@ var ourMAC = net.HardwareAddr{0x30, 0x2D, 0x66, 0xEC, 0x7A, 0x93}
func init() { createTAP = createTAPLinux }
func createTAPLinux(tapName, bridgeName string) (tun.Device, error) {
func createTAPLinux(logf logger.Logf, tapName, bridgeName string) (tun.Device, error) {
fd, err := unix.Open("/dev/net/tun", unix.O_RDWR, 0)
if err != nil {
return nil, err
}
dev, err := openDevice(fd, tapName, bridgeName)
dev, err := openDevice(logf, fd, tapName, bridgeName)
if err != nil {
unix.Close(fd)
return nil, err
@@ -50,7 +54,7 @@ func createTAPLinux(tapName, bridgeName string) (tun.Device, error) {
return dev, nil
}
func openDevice(fd int, tapName, bridgeName string) (tun.Device, error) {
func openDevice(logf logger.Logf, fd int, tapName, bridgeName string) (tun.Device, error) {
ifr, err := unix.NewIfreq(tapName)
if err != nil {
return nil, err
@@ -71,7 +75,7 @@ func openDevice(fd int, tapName, bridgeName string) (tun.Device, error) {
}
}
return newTAPDevice(fd, tapName)
return newTAPDevice(logf, fd, tapName)
}
type etherType [2]byte
@@ -91,7 +95,7 @@ const (
// handleTAPFrame handles receiving a raw TAP ethernet frame and reports whether
// it's been handled (that is, whether it should NOT be passed to wireguard).
func (t *Wrapper) handleTAPFrame(ethBuf []byte) bool {
func (t *tapDevice) handleTAPFrame(ethBuf []byte) bool {
if len(ethBuf) < ethernetFrameSize {
// Corrupt. Ignore.
@@ -164,8 +168,7 @@ func (t *Wrapper) handleTAPFrame(ethBuf []byte) bool {
copy(res.HardwareAddressTarget(), req.HardwareAddressSender())
copy(res.ProtocolAddressTarget(), req.ProtocolAddressSender())
// TODO(raggi): reduce allocs!
n, err := t.tdev.Write([][]byte{buf}, 0)
n, err := t.WriteEthernet(buf)
if tapDebug {
t.logf("tap: wrote ARP reply %v, %v", n, err)
}
@@ -182,7 +185,7 @@ const routerIP = "100.70.145.1" // must be in same netmask (currently hack at
// handleDHCPRequest handles receiving a raw TAP ethernet frame and reports whether
// it's been handled as a DHCP request. That is, it reports whether the frame should
// be ignored by the caller and not passed on.
func (t *Wrapper) handleDHCPRequest(ethBuf []byte) bool {
func (t *tapDevice) handleDHCPRequest(ethBuf []byte) bool {
const udpHeader = 8
if len(ethBuf) < ethernetFrameSize+ipv4HeaderLen+udpHeader {
if tapDebug {
@@ -207,7 +210,7 @@ func (t *Wrapper) handleDHCPRequest(ethBuf []byte) bool {
if p.IPProto != ipproto.UDP || p.Src.Port() != 68 || p.Dst.Port() != 67 {
// Not a DHCP request.
if tapDebug {
t.logf("tap: DHCP wrong meta")
t.logf("tap: DHCP wrong meta: %+v", p)
}
return passOnPacket
}
@@ -250,8 +253,7 @@ func (t *Wrapper) handleDHCPRequest(ethBuf []byte) bool {
netip.AddrPortFrom(netaddr.IPv4(255, 255, 255, 255), 68), // dst
)
// TODO(raggi): reduce allocs!
n, err := t.tdev.Write([][]byte{pkt}, 0)
n, err := t.WriteEthernet(pkt)
if tapDebug {
t.logf("tap: wrote DHCP OFFER %v, %v", n, err)
}
@@ -278,8 +280,7 @@ func (t *Wrapper) handleDHCPRequest(ethBuf []byte) bool {
netip.AddrPortFrom(netaddr.IPv4(100, 100, 100, 100), 67), // src
netip.AddrPortFrom(netaddr.IPv4(255, 255, 255, 255), 68), // dst
)
// TODO(raggi): reduce allocs!
n, err := t.tdev.Write([][]byte{pkt}, 0)
n, err := t.WriteEthernet(pkt)
if tapDebug {
t.logf("tap: wrote DHCP ACK %v, %v", n, err)
}
@@ -291,6 +292,16 @@ func (t *Wrapper) handleDHCPRequest(ethBuf []byte) bool {
return consumePacket
}
func writeEthernetFrame(buf []byte, srcMAC, dstMAC net.HardwareAddr, proto tcpip.NetworkProtocolNumber) {
// Ethernet header
eth := header.Ethernet(buf)
eth.Encode(&header.EthernetFields{
SrcAddr: tcpip.LinkAddress(srcMAC),
DstAddr: tcpip.LinkAddress(dstMAC),
Type: proto,
})
}
func packLayer2UDP(payload []byte, srcMAC, dstMAC net.HardwareAddr, src, dst netip.AddrPort) []byte {
buf := make([]byte, header.EthernetMinimumSize+header.UDPMinimumSize+header.IPv4MinimumSize+len(payload))
payloadStart := len(buf) - len(payload)
@@ -300,12 +311,7 @@ func packLayer2UDP(payload []byte, srcMAC, dstMAC net.HardwareAddr, src, dst net
dstB := dst.Addr().As4()
dstIP := tcpip.AddrFromSlice(dstB[:])
// Ethernet header
eth := header.Ethernet(buf)
eth.Encode(&header.EthernetFields{
SrcAddr: tcpip.LinkAddress(srcMAC),
DstAddr: tcpip.LinkAddress(dstMAC),
Type: ipv4.ProtocolNumber,
})
writeEthernetFrame(buf, srcMAC, dstMAC, ipv4.ProtocolNumber)
// IP header
ipbuf := buf[header.EthernetMinimumSize:]
ip := header.IPv4(ipbuf)
@@ -342,17 +348,18 @@ func run(prog string, args ...string) error {
return nil
}
func (t *Wrapper) destMAC() [6]byte {
func (t *tapDevice) destMAC() [6]byte {
return t.destMACAtomic.Load()
}
func newTAPDevice(fd int, tapName string) (tun.Device, error) {
func newTAPDevice(logf logger.Logf, fd int, tapName string) (tun.Device, error) {
err := unix.SetNonblock(fd, true)
if err != nil {
return nil, err
}
file := os.NewFile(uintptr(fd), "/dev/tap")
d := &tapDevice{
logf: logf,
file: file,
events: make(chan tun.Event),
name: tapName,
@@ -360,20 +367,14 @@ func newTAPDevice(fd int, tapName string) (tun.Device, error) {
return d, nil
}
var (
_ setWrapperer = &tapDevice{}
)
type tapDevice struct {
file *os.File
logf func(format string, args ...any)
events chan tun.Event
name string
wrapper *Wrapper
closeOnce sync.Once
}
func (t *tapDevice) setWrapper(wrapper *Wrapper) {
t.wrapper = wrapper
destMACAtomic syncs.AtomicValue[[6]byte]
}
func (t *tapDevice) File() *os.File {
@@ -384,36 +385,63 @@ func (t *tapDevice) Name() (string, error) {
return t.name, nil
}
// Read reads an IP packet from the TAP device. It strips the ethernet frame header.
func (t *tapDevice) Read(buffs [][]byte, sizes []int, offset int) (int, error) {
n, err := t.ReadEthernet(buffs, sizes, offset)
if err != nil || n == 0 {
return n, err
}
// Strip the ethernet frame header.
copy(buffs[0][offset:], buffs[0][offset+ethernetFrameSize:offset+sizes[0]])
sizes[0] -= ethernetFrameSize
return 1, nil
}
// ReadEthernet reads a raw ethernet frame from the TAP device.
func (t *tapDevice) ReadEthernet(buffs [][]byte, sizes []int, offset int) (int, error) {
n, err := t.file.Read(buffs[0][offset:])
if err != nil {
return 0, err
}
if t.handleTAPFrame(buffs[0][offset : offset+n]) {
return 0, nil
}
sizes[0] = n
return 1, nil
}
// WriteEthernet writes a raw ethernet frame to the TAP device.
func (t *tapDevice) WriteEthernet(buf []byte) (int, error) {
return t.file.Write(buf)
}
// ethBufPool holds a pool of bytes.Buffers for use in [tapDevice.Write].
var ethBufPool = syncs.Pool[*bytes.Buffer]{New: func() *bytes.Buffer { return new(bytes.Buffer) }}
// Write writes a raw IP packet to the TAP device. It adds the ethernet frame header.
func (t *tapDevice) Write(buffs [][]byte, offset int) (int, error) {
errs := make([]error, 0)
wrote := 0
m := t.destMAC()
dstMac := net.HardwareAddr(m[:])
buf := ethBufPool.Get()
defer ethBufPool.Put(buf)
for _, buff := range buffs {
if offset < ethernetFrameSize {
errs = append(errs, fmt.Errorf("[unexpected] weird offset %d for TAP write", offset))
return 0, multierr.New(errs...)
buf.Reset()
buf.Grow(header.EthernetMinimumSize + len(buff) - offset)
var ebuf [14]byte
switch buff[offset] >> 4 {
case 4:
writeEthernetFrame(ebuf[:], ourMAC, dstMac, ipv4.ProtocolNumber)
case 6:
writeEthernetFrame(ebuf[:], ourMAC, dstMac, ipv6.ProtocolNumber)
default:
continue
}
eth := buff[offset-ethernetFrameSize:]
dst := t.wrapper.destMAC()
copy(eth[:6], dst[:])
copy(eth[6:12], ourMAC[:])
et := etherTypeIPv4
if buff[offset]>>4 == 6 {
et = etherTypeIPv6
}
eth[12], eth[13] = et[0], et[1]
if tapDebug {
t.wrapper.logf("tap: tapWrite off=%v % x", offset, buff)
}
_, err := t.file.Write(buff[offset-ethernetFrameSize:])
buf.Write(ebuf[:])
buf.Write(buff[offset:])
_, err := t.WriteEthernet(buf.Bytes())
if err != nil {
errs = append(errs, err)
} else {
@@ -428,8 +456,7 @@ func (t *tapDevice) MTU() (int, error) {
if err != nil {
return 0, err
}
err = unix.IoctlIfreq(int(t.file.Fd()), unix.SIOCGIFMTU, ifr)
if err != nil {
if err := unix.IoctlIfreq(int(t.file.Fd()), unix.SIOCGIFMTU, ifr); err != nil {
return 0, err
}
return int(ifr.Uint32()), nil