mirror of https://github.com/tailscale/tailscale/
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
503 lines
13 KiB
Go
503 lines
13 KiB
Go
// Copyright (c) Tailscale Inc & AUTHORS
|
|
// SPDX-License-Identifier: BSD-3-Clause
|
|
|
|
//go:build !ts_omit_tap
|
|
|
|
package tstun
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"net"
|
|
"net/netip"
|
|
"os"
|
|
"os/exec"
|
|
"sync"
|
|
|
|
"github.com/insomniacslk/dhcp/dhcpv4"
|
|
"github.com/tailscale/wireguard-go/tun"
|
|
"golang.org/x/sys/unix"
|
|
"gvisor.dev/gvisor/pkg/tcpip"
|
|
"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/net/tsaddr"
|
|
"tailscale.com/syncs"
|
|
"tailscale.com/types/ipproto"
|
|
"tailscale.com/types/logger"
|
|
"tailscale.com/util/multierr"
|
|
)
|
|
|
|
// TODO: this was randomly generated once. Maybe do it per process start? But
|
|
// then an upgraded tailscaled would be visible to devices behind it. So
|
|
// maybe instead make it a function of the tailscaled's wireguard public key?
|
|
// For now just hard code it.
|
|
var ourMAC = net.HardwareAddr{0x30, 0x2D, 0x66, 0xEC, 0x7A, 0x93}
|
|
|
|
func init() { createTAP = createTAPLinux }
|
|
|
|
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(logf, fd, tapName, bridgeName)
|
|
if err != nil {
|
|
unix.Close(fd)
|
|
return nil, err
|
|
}
|
|
|
|
return dev, nil
|
|
}
|
|
|
|
func openDevice(logf logger.Logf, fd int, tapName, bridgeName string) (tun.Device, error) {
|
|
ifr, err := unix.NewIfreq(tapName)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Flags are stored as a uint16 in the ifreq union.
|
|
ifr.SetUint16(unix.IFF_TAP | unix.IFF_NO_PI)
|
|
if err := unix.IoctlIfreq(fd, unix.TUNSETIFF, ifr); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if err := run("ip", "link", "set", "dev", tapName, "up"); err != nil {
|
|
return nil, err
|
|
}
|
|
if bridgeName != "" {
|
|
if err := run("brctl", "addif", bridgeName, tapName); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
return newTAPDevice(logf, fd, tapName)
|
|
}
|
|
|
|
type etherType [2]byte
|
|
|
|
var (
|
|
etherTypeARP = etherType{0x08, 0x06}
|
|
etherTypeIPv4 = etherType{0x08, 0x00}
|
|
etherTypeIPv6 = etherType{0x86, 0xDD}
|
|
)
|
|
|
|
const ipv4HeaderLen = 20
|
|
|
|
const (
|
|
consumePacket = true
|
|
passOnPacket = false
|
|
)
|
|
|
|
// 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 *tapDevice) handleTAPFrame(ethBuf []byte) bool {
|
|
|
|
if len(ethBuf) < ethernetFrameSize {
|
|
// Corrupt. Ignore.
|
|
if tapDebug {
|
|
t.logf("tap: short TAP frame")
|
|
}
|
|
return consumePacket
|
|
}
|
|
ethDstMAC, ethSrcMAC := ethBuf[:6], ethBuf[6:12]
|
|
_ = ethDstMAC
|
|
et := etherType{ethBuf[12], ethBuf[13]}
|
|
switch et {
|
|
default:
|
|
if tapDebug {
|
|
t.logf("tap: ignoring etherType %v", et)
|
|
}
|
|
return consumePacket // filter out packet we should ignore
|
|
case etherTypeIPv6:
|
|
// TODO: support DHCPv6/ND/etc later. For now pass all to WireGuard.
|
|
if tapDebug {
|
|
t.logf("tap: ignoring IPv6 %v", et)
|
|
}
|
|
return passOnPacket
|
|
case etherTypeIPv4:
|
|
if len(ethBuf) < ethernetFrameSize+ipv4HeaderLen {
|
|
// Bogus IPv4. Eat.
|
|
if tapDebug {
|
|
t.logf("tap: short ipv4")
|
|
}
|
|
return consumePacket
|
|
}
|
|
return t.handleDHCPRequest(ethBuf)
|
|
case etherTypeARP:
|
|
arpPacket := header.ARP(ethBuf[ethernetFrameSize:])
|
|
if !arpPacket.IsValid() {
|
|
// Bogus ARP. Eat.
|
|
return consumePacket
|
|
}
|
|
switch arpPacket.Op() {
|
|
case header.ARPRequest:
|
|
req := arpPacket // better name at this point
|
|
buf := make([]byte, header.EthernetMinimumSize+header.ARPSize)
|
|
|
|
// Our ARP "Table" of one:
|
|
var srcMAC [6]byte
|
|
copy(srcMAC[:], ethSrcMAC)
|
|
if old := t.destMAC(); old != srcMAC {
|
|
t.destMACAtomic.Store(srcMAC)
|
|
}
|
|
|
|
eth := header.Ethernet(buf)
|
|
eth.Encode(&header.EthernetFields{
|
|
SrcAddr: tcpip.LinkAddress(ourMAC[:]),
|
|
DstAddr: tcpip.LinkAddress(ethSrcMAC),
|
|
Type: 0x0806, // arp
|
|
})
|
|
res := header.ARP(buf[header.EthernetMinimumSize:])
|
|
res.SetIPv4OverEthernet()
|
|
res.SetOp(header.ARPReply)
|
|
|
|
// If the client's asking about their own IP, tell them it's
|
|
// their own MAC. TODO(bradfitz): remove String allocs.
|
|
if net.IP(req.ProtocolAddressTarget()).String() == t.clientIPv4.Load() {
|
|
copy(res.HardwareAddressSender(), ethSrcMAC)
|
|
} else {
|
|
copy(res.HardwareAddressSender(), ourMAC[:])
|
|
}
|
|
|
|
copy(res.ProtocolAddressSender(), req.ProtocolAddressTarget())
|
|
copy(res.HardwareAddressTarget(), req.HardwareAddressSender())
|
|
copy(res.ProtocolAddressTarget(), req.ProtocolAddressSender())
|
|
|
|
n, err := t.WriteEthernet(buf)
|
|
if tapDebug {
|
|
t.logf("tap: wrote ARP reply %v, %v", n, err)
|
|
}
|
|
}
|
|
|
|
return consumePacket
|
|
}
|
|
}
|
|
|
|
var (
|
|
// routerIP is the IP address of the DHCP server.
|
|
routerIP = net.ParseIP(tsaddr.TailscaleServiceIPString)
|
|
// cgnatNetMask is the netmask of the 100.64.0.0/10 CGNAT range.
|
|
cgnatNetMask = net.IPMask(net.ParseIP("255.192.0.0").To4())
|
|
)
|
|
|
|
// 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 *tapDevice) handleDHCPRequest(ethBuf []byte) bool {
|
|
const udpHeader = 8
|
|
if len(ethBuf) < ethernetFrameSize+ipv4HeaderLen+udpHeader {
|
|
if tapDebug {
|
|
t.logf("tap: DHCP short")
|
|
}
|
|
return passOnPacket
|
|
}
|
|
ethDstMAC, ethSrcMAC := ethBuf[:6], ethBuf[6:12]
|
|
|
|
if string(ethDstMAC) != "\xff\xff\xff\xff\xff\xff" {
|
|
// Not a broadcast
|
|
if tapDebug {
|
|
t.logf("tap: dhcp no broadcast")
|
|
}
|
|
return passOnPacket
|
|
}
|
|
|
|
p := parsedPacketPool.Get().(*packet.Parsed)
|
|
defer parsedPacketPool.Put(p)
|
|
p.Decode(ethBuf[ethernetFrameSize:])
|
|
|
|
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: %+v", p)
|
|
}
|
|
return passOnPacket
|
|
}
|
|
|
|
dp, err := dhcpv4.FromBytes(ethBuf[ethernetFrameSize+ipv4HeaderLen+udpHeader:])
|
|
if err != nil {
|
|
// Bogus. Trash it.
|
|
if tapDebug {
|
|
t.logf("tap: DHCP FromBytes bad")
|
|
}
|
|
return consumePacket
|
|
}
|
|
if tapDebug {
|
|
t.logf("tap: DHCP request: %+v", dp)
|
|
}
|
|
switch dp.MessageType() {
|
|
case dhcpv4.MessageTypeDiscover:
|
|
ips := t.clientIPv4.Load()
|
|
if ips == "" {
|
|
t.logf("tap: DHCP no client IP")
|
|
return consumePacket
|
|
}
|
|
offer, err := dhcpv4.New(
|
|
dhcpv4.WithReply(dp),
|
|
dhcpv4.WithMessageType(dhcpv4.MessageTypeOffer),
|
|
dhcpv4.WithRouter(routerIP), // the default route
|
|
dhcpv4.WithDNS(routerIP),
|
|
dhcpv4.WithServerIP(routerIP), // TODO: what is this?
|
|
dhcpv4.WithOption(dhcpv4.OptServerIdentifier(routerIP)),
|
|
dhcpv4.WithYourIP(net.ParseIP(ips)),
|
|
dhcpv4.WithLeaseTime(3600), // hour works
|
|
//dhcpv4.WithHwAddr(ethSrcMAC),
|
|
dhcpv4.WithNetmask(cgnatNetMask),
|
|
//dhcpv4.WithTransactionID(dp.TransactionID),
|
|
)
|
|
if err != nil {
|
|
t.logf("error building DHCP offer: %v", err)
|
|
return consumePacket
|
|
}
|
|
// Make a layer 2 packet to write out:
|
|
pkt := packLayer2UDP(
|
|
offer.ToBytes(),
|
|
ourMAC, ethSrcMAC,
|
|
netip.AddrPortFrom(netaddr.IPv4(100, 100, 100, 100), 67), // src
|
|
netip.AddrPortFrom(netaddr.IPv4(255, 255, 255, 255), 68), // dst
|
|
)
|
|
|
|
n, err := t.WriteEthernet(pkt)
|
|
if tapDebug {
|
|
t.logf("tap: wrote DHCP OFFER %v, %v", n, err)
|
|
}
|
|
case dhcpv4.MessageTypeRequest:
|
|
ips := t.clientIPv4.Load()
|
|
if ips == "" {
|
|
t.logf("tap: DHCP no client IP")
|
|
return consumePacket
|
|
}
|
|
ack, err := dhcpv4.New(
|
|
dhcpv4.WithReply(dp),
|
|
dhcpv4.WithMessageType(dhcpv4.MessageTypeAck),
|
|
dhcpv4.WithDNS(routerIP),
|
|
dhcpv4.WithRouter(routerIP), // the default route
|
|
dhcpv4.WithServerIP(routerIP), // TODO: what is this?
|
|
dhcpv4.WithOption(dhcpv4.OptServerIdentifier(routerIP)),
|
|
dhcpv4.WithYourIP(net.ParseIP(ips)), // Hello world
|
|
dhcpv4.WithLeaseTime(3600), // hour works
|
|
dhcpv4.WithNetmask(cgnatNetMask),
|
|
)
|
|
if err != nil {
|
|
t.logf("error building DHCP ack: %v", err)
|
|
return consumePacket
|
|
}
|
|
// Make a layer 2 packet to write out:
|
|
pkt := packLayer2UDP(
|
|
ack.ToBytes(),
|
|
ourMAC, ethSrcMAC,
|
|
netip.AddrPortFrom(netaddr.IPv4(100, 100, 100, 100), 67), // src
|
|
netip.AddrPortFrom(netaddr.IPv4(255, 255, 255, 255), 68), // dst
|
|
)
|
|
n, err := t.WriteEthernet(pkt)
|
|
if tapDebug {
|
|
t.logf("tap: wrote DHCP ACK %v, %v", n, err)
|
|
}
|
|
default:
|
|
if tapDebug {
|
|
t.logf("tap: unknown DHCP type")
|
|
}
|
|
}
|
|
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)
|
|
copy(buf[payloadStart:], payload)
|
|
srcB := src.Addr().As4()
|
|
srcIP := tcpip.AddrFromSlice(srcB[:])
|
|
dstB := dst.Addr().As4()
|
|
dstIP := tcpip.AddrFromSlice(dstB[:])
|
|
// Ethernet header
|
|
writeEthernetFrame(buf, srcMAC, dstMAC, ipv4.ProtocolNumber)
|
|
// IP header
|
|
ipbuf := buf[header.EthernetMinimumSize:]
|
|
ip := header.IPv4(ipbuf)
|
|
ip.Encode(&header.IPv4Fields{
|
|
TotalLength: uint16(len(ipbuf)),
|
|
TTL: 65,
|
|
Protocol: uint8(udp.ProtocolNumber),
|
|
SrcAddr: srcIP,
|
|
DstAddr: dstIP,
|
|
})
|
|
ip.SetChecksum(^ip.CalculateChecksum())
|
|
// UDP header
|
|
u := header.UDP(buf[header.EthernetMinimumSize+header.IPv4MinimumSize:])
|
|
u.Encode(&header.UDPFields{
|
|
SrcPort: src.Port(),
|
|
DstPort: dst.Port(),
|
|
Length: uint16(header.UDPMinimumSize + len(payload)),
|
|
})
|
|
// Calculate the UDP pseudo-header checksum.
|
|
xsum := header.PseudoHeaderChecksum(udp.ProtocolNumber, srcIP, dstIP, uint16(len(u)))
|
|
// Calculate the UDP checksum and set it.
|
|
xsum = checksum.Checksum(payload, xsum)
|
|
u.SetChecksum(^u.CalculateChecksum(xsum))
|
|
return []byte(buf)
|
|
}
|
|
|
|
func run(prog string, args ...string) error {
|
|
cmd := exec.Command(prog, args...)
|
|
cmd.Stdout = os.Stdout
|
|
cmd.Stderr = os.Stderr
|
|
if err := cmd.Run(); err != nil {
|
|
return fmt.Errorf("error running %v: %v", cmd, err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (t *tapDevice) destMAC() [6]byte {
|
|
return t.destMACAtomic.Load()
|
|
}
|
|
|
|
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,
|
|
}
|
|
return d, nil
|
|
}
|
|
|
|
type tapDevice struct {
|
|
file *os.File
|
|
logf func(format string, args ...any)
|
|
events chan tun.Event
|
|
name string
|
|
closeOnce sync.Once
|
|
clientIPv4 syncs.AtomicValue[string]
|
|
|
|
destMACAtomic syncs.AtomicValue[[6]byte]
|
|
}
|
|
|
|
var _ setIPer = (*tapDevice)(nil)
|
|
|
|
func (t *tapDevice) SetIP(ipV4, ipV6TODO netip.Addr) error {
|
|
t.clientIPv4.Store(ipV4.String())
|
|
return nil
|
|
}
|
|
|
|
func (t *tapDevice) File() *os.File {
|
|
return t.file
|
|
}
|
|
|
|
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 {
|
|
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
|
|
}
|
|
buf.Write(ebuf[:])
|
|
buf.Write(buff[offset:])
|
|
_, err := t.WriteEthernet(buf.Bytes())
|
|
if err != nil {
|
|
errs = append(errs, err)
|
|
} else {
|
|
wrote++
|
|
}
|
|
}
|
|
return wrote, multierr.New(errs...)
|
|
}
|
|
|
|
func (t *tapDevice) MTU() (int, error) {
|
|
ifr, err := unix.NewIfreq(t.name)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
if err := unix.IoctlIfreq(int(t.file.Fd()), unix.SIOCGIFMTU, ifr); err != nil {
|
|
return 0, err
|
|
}
|
|
return int(ifr.Uint32()), nil
|
|
}
|
|
|
|
func (t *tapDevice) Events() <-chan tun.Event {
|
|
return t.events
|
|
}
|
|
|
|
func (t *tapDevice) Close() error {
|
|
var err error
|
|
t.closeOnce.Do(func() {
|
|
close(t.events)
|
|
err = t.file.Close()
|
|
})
|
|
return err
|
|
}
|
|
|
|
func (t *tapDevice) BatchSize() int {
|
|
return 1
|
|
}
|