mirror of
				https://github.com/tailscale/tailscale.git
				synced 2025-11-04 02:01:14 +01:00 
			
		
		
		
	Few changes to resolve TODOs in the code: - Instead of using a hardcoded IP, get it from the netmap. - Use 100.100.100.100 as the gateway IP - Use the /10 CGNAT range instead of a random /24 Updates #2589 Signed-off-by: Maisem Ali <maisem@tailscale.com>
		
			
				
	
	
		
			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
 | 
						|
}
 |