@ -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 ... )
}
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 )
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
}
_ , 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