mirror of
https://github.com/SagerNet/sing-tun.git
synced 2025-03-31 10:27:39 +03:00
1078 lines
26 KiB
Go
1078 lines
26 KiB
Go
package tun
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"math/rand"
|
|
"net"
|
|
"net/netip"
|
|
"os"
|
|
"os/exec"
|
|
"runtime"
|
|
"sync"
|
|
"syscall"
|
|
"unsafe"
|
|
|
|
"github.com/sagernet/netlink"
|
|
"github.com/sagernet/sing-tun/internal/gtcpip/checksum"
|
|
"github.com/sagernet/sing-tun/internal/gtcpip/header"
|
|
"github.com/sagernet/sing/common"
|
|
"github.com/sagernet/sing/common/buf"
|
|
"github.com/sagernet/sing/common/bufio"
|
|
"github.com/sagernet/sing/common/control"
|
|
E "github.com/sagernet/sing/common/exceptions"
|
|
N "github.com/sagernet/sing/common/network"
|
|
"github.com/sagernet/sing/common/rw"
|
|
"github.com/sagernet/sing/common/shell"
|
|
"github.com/sagernet/sing/common/x/list"
|
|
|
|
"golang.org/x/sys/unix"
|
|
)
|
|
|
|
var _ LinuxTUN = (*NativeTun)(nil)
|
|
|
|
type NativeTun struct {
|
|
tunFd int
|
|
tunFile *os.File
|
|
tunWriter N.VectorisedWriter
|
|
interfaceCallback *list.Element[DefaultInterfaceUpdateCallback]
|
|
options Options
|
|
ruleIndex6 []int
|
|
readAccess sync.Mutex
|
|
writeAccess sync.Mutex
|
|
vnetHdr bool
|
|
writeBuffer []byte
|
|
gsoToWrite []int
|
|
tcpGROTable *tcpGROTable
|
|
udpGroAccess sync.Mutex
|
|
udpGROTable *udpGROTable
|
|
gro groDisablementFlags
|
|
txChecksumOffload bool
|
|
}
|
|
|
|
func New(options Options) (Tun, error) {
|
|
var nativeTun *NativeTun
|
|
if options.FileDescriptor == 0 {
|
|
tunFd, err := open(options.Name, options.GSO)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
tunLink, err := netlink.LinkByName(options.Name)
|
|
if err != nil {
|
|
return nil, E.Errors(err, unix.Close(tunFd))
|
|
}
|
|
nativeTun = &NativeTun{
|
|
tunFd: tunFd,
|
|
tunFile: os.NewFile(uintptr(tunFd), "tun"),
|
|
options: options,
|
|
}
|
|
err = nativeTun.configure(tunLink)
|
|
if err != nil {
|
|
return nil, E.Errors(err, unix.Close(tunFd))
|
|
}
|
|
} else {
|
|
nativeTun = &NativeTun{
|
|
tunFd: options.FileDescriptor,
|
|
tunFile: os.NewFile(uintptr(options.FileDescriptor), "tun"),
|
|
options: options,
|
|
}
|
|
}
|
|
var ok bool
|
|
nativeTun.tunWriter, ok = bufio.CreateVectorisedWriter(nativeTun.tunFile)
|
|
if !ok {
|
|
panic("create vectorised writer")
|
|
}
|
|
return nativeTun, nil
|
|
}
|
|
|
|
var controlPath string
|
|
|
|
func init() {
|
|
const defaultTunPath = "/dev/net/tun"
|
|
const androidTunPath = "/dev/tun"
|
|
if rw.IsFile(androidTunPath) {
|
|
controlPath = androidTunPath
|
|
} else {
|
|
controlPath = defaultTunPath
|
|
}
|
|
}
|
|
|
|
func open(name string, vnetHdr bool) (int, error) {
|
|
fd, err := unix.Open(controlPath, unix.O_RDWR, 0)
|
|
if err != nil {
|
|
return -1, err
|
|
}
|
|
ifr, err := unix.NewIfreq(name)
|
|
if err != nil {
|
|
unix.Close(fd)
|
|
return 0, err
|
|
}
|
|
flags := unix.IFF_TUN | unix.IFF_NO_PI
|
|
if vnetHdr {
|
|
flags |= unix.IFF_VNET_HDR
|
|
}
|
|
ifr.SetUint16(uint16(flags))
|
|
err = unix.IoctlIfreq(fd, unix.TUNSETIFF, ifr)
|
|
if err != nil {
|
|
unix.Close(fd)
|
|
return 0, err
|
|
}
|
|
err = unix.SetNonblock(fd, true)
|
|
if err != nil {
|
|
unix.Close(fd)
|
|
return 0, err
|
|
}
|
|
return fd, nil
|
|
}
|
|
|
|
func (t *NativeTun) configure(tunLink netlink.Link) error {
|
|
err := netlink.LinkSetMTU(tunLink, int(t.options.MTU))
|
|
if errors.Is(err, unix.EPERM) {
|
|
return nil
|
|
} else if err != nil {
|
|
return err
|
|
}
|
|
|
|
if len(t.options.Inet4Address) > 0 {
|
|
for _, address := range t.options.Inet4Address {
|
|
addr4, _ := netlink.ParseAddr(address.String())
|
|
err = netlink.AddrAdd(tunLink, addr4)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
if len(t.options.Inet6Address) > 0 {
|
|
for _, address := range t.options.Inet6Address {
|
|
addr6, _ := netlink.ParseAddr(address.String())
|
|
err = netlink.AddrAdd(tunLink, addr6)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
|
|
if t.options.GSO {
|
|
err = t.enableGSO()
|
|
if err != nil {
|
|
t.options.Logger.Warn(err)
|
|
}
|
|
}
|
|
|
|
var rxChecksumOffload bool
|
|
rxChecksumOffload, err = checkChecksumOffload(t.options.Name, unix.ETHTOOL_GRXCSUM)
|
|
if err == nil && !rxChecksumOffload {
|
|
_ = setChecksumOffload(t.options.Name, unix.ETHTOOL_SRXCSUM)
|
|
}
|
|
|
|
if t.options._TXChecksumOffload {
|
|
var txChecksumOffload bool
|
|
txChecksumOffload, err = checkChecksumOffload(t.options.Name, unix.ETHTOOL_GTXCSUM)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !txChecksumOffload {
|
|
err = setChecksumOffload(t.options.Name, unix.ETHTOOL_STXCSUM)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
t.txChecksumOffload = true
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (t *NativeTun) enableGSO() error {
|
|
vnetHdrEnabled, err := checkVNETHDREnabled(t.tunFd, t.options.Name)
|
|
if err != nil {
|
|
return E.Cause(err, "enable offload: check IFF_VNET_HDR enabled")
|
|
}
|
|
if !vnetHdrEnabled {
|
|
return E.Cause(err, "enable offload: IFF_VNET_HDR not enabled")
|
|
}
|
|
err = setTCPOffload(t.tunFd)
|
|
if err != nil {
|
|
return E.Cause(err, "enable TCP offload")
|
|
}
|
|
t.vnetHdr = true
|
|
t.writeBuffer = make([]byte, virtioNetHdrLen+int(gsoMaxSize))
|
|
t.tcpGROTable = newTCPGROTable()
|
|
t.udpGROTable = newUDPGROTable()
|
|
err = setUDPOffload(t.tunFd)
|
|
if err != nil {
|
|
t.gro.disableUDPGRO()
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (t *NativeTun) probeTCPGRO() error {
|
|
ipPort := netip.AddrPortFrom(t.options.Inet4Address[0].Addr(), 0)
|
|
fingerprint := []byte("sing-tun-probe-tun-gro")
|
|
segmentSize := len(fingerprint)
|
|
iphLen := 20
|
|
tcphLen := 20
|
|
totalLen := iphLen + tcphLen + segmentSize
|
|
bufs := make([][]byte, 2)
|
|
for i := range bufs {
|
|
bufs[i] = make([]byte, virtioNetHdrLen+totalLen, virtioNetHdrLen+(totalLen*2))
|
|
ipv4H := header.IPv4(bufs[i][virtioNetHdrLen:])
|
|
ipv4H.Encode(&header.IPv4Fields{
|
|
SrcAddr: ipPort.Addr(),
|
|
DstAddr: ipPort.Addr(),
|
|
Protocol: unix.IPPROTO_TCP,
|
|
// Use a zero value TTL as best effort means to reduce chance of
|
|
// probe packet leaking further than it needs to.
|
|
TTL: 0,
|
|
TotalLength: uint16(totalLen),
|
|
})
|
|
tcpH := header.TCP(bufs[i][virtioNetHdrLen+iphLen:])
|
|
tcpH.Encode(&header.TCPFields{
|
|
SrcPort: ipPort.Port(),
|
|
DstPort: ipPort.Port(),
|
|
SeqNum: 1 + uint32(i*segmentSize),
|
|
AckNum: 1,
|
|
DataOffset: 20,
|
|
Flags: header.TCPFlagAck,
|
|
WindowSize: 3000,
|
|
})
|
|
copy(bufs[i][virtioNetHdrLen+iphLen+tcphLen:], fingerprint)
|
|
ipv4H.SetChecksum(^ipv4H.CalculateChecksum())
|
|
pseudoCsum := header.PseudoHeaderChecksum(unix.IPPROTO_TCP, ipv4H.SourceAddressSlice(), ipv4H.DestinationAddressSlice(), uint16(tcphLen+segmentSize))
|
|
pseudoCsum = checksum.Checksum(bufs[i][virtioNetHdrLen+iphLen+tcphLen:], pseudoCsum)
|
|
tcpH.SetChecksum(^tcpH.CalculateChecksum(pseudoCsum))
|
|
}
|
|
_, err := t.BatchWrite(bufs, virtioNetHdrLen)
|
|
return err
|
|
}
|
|
|
|
func (t *NativeTun) Name() (string, error) {
|
|
var ifr [unix.IFNAMSIZ + 64]byte
|
|
_, _, errno := unix.Syscall(
|
|
unix.SYS_IOCTL,
|
|
uintptr(t.tunFd),
|
|
uintptr(unix.TUNGETIFF),
|
|
uintptr(unsafe.Pointer(&ifr[0])),
|
|
)
|
|
if errno != 0 {
|
|
return "", os.NewSyscallError("ioctl TUNGETIFF", errno)
|
|
}
|
|
return unix.ByteSliceToString(ifr[:]), nil
|
|
}
|
|
|
|
func (t *NativeTun) Start() error {
|
|
if t.options.FileDescriptor != 0 {
|
|
return nil
|
|
}
|
|
|
|
tunLink, err := netlink.LinkByName(t.options.Name)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = netlink.LinkSetUp(tunLink)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if t.vnetHdr && len(t.options.Inet4Address) > 0 {
|
|
err = t.probeTCPGRO()
|
|
if err != nil {
|
|
t.gro.disableTCPGRO()
|
|
t.gro.disableUDPGRO()
|
|
t.options.Logger.Warn(E.Cause(err, "disabled TUN TCP & UDP GRO due to GRO probe error"))
|
|
}
|
|
}
|
|
|
|
if t.options.IPRoute2TableIndex == 0 {
|
|
for {
|
|
t.options.IPRoute2TableIndex = int(rand.Uint32())
|
|
routeList, fErr := netlink.RouteListFiltered(netlink.FAMILY_ALL, &netlink.Route{Table: t.options.IPRoute2TableIndex}, netlink.RT_FILTER_TABLE)
|
|
if len(routeList) == 0 || fErr != nil {
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
err = t.setRoute(tunLink)
|
|
if err != nil {
|
|
_ = t.unsetRoute0(tunLink)
|
|
return err
|
|
}
|
|
|
|
err = t.unsetRules()
|
|
if err != nil {
|
|
return E.Cause(err, "cleanup rules")
|
|
}
|
|
err = t.setRules()
|
|
if err != nil {
|
|
_ = t.unsetRules()
|
|
return err
|
|
}
|
|
|
|
t.setSearchDomainForSystemdResolved()
|
|
|
|
if t.options.AutoRoute && runtime.GOOS == "android" {
|
|
t.interfaceCallback = t.options.InterfaceMonitor.RegisterCallback(t.routeUpdate)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (t *NativeTun) Close() error {
|
|
if t.interfaceCallback != nil {
|
|
t.options.InterfaceMonitor.UnregisterCallback(t.interfaceCallback)
|
|
}
|
|
return E.Errors(t.unsetRoute(), t.unsetRules(), common.Close(common.PtrOrNil(t.tunFile)))
|
|
}
|
|
|
|
func (t *NativeTun) Read(p []byte) (n int, err error) {
|
|
if t.vnetHdr {
|
|
n, err = t.tunFile.Read(t.writeBuffer)
|
|
if err != nil {
|
|
if errors.Is(err, syscall.EBADFD) {
|
|
err = os.ErrClosed
|
|
}
|
|
return
|
|
}
|
|
var sizes [1]int
|
|
n, err = handleVirtioRead(t.writeBuffer[:n], [][]byte{p}, sizes[:], 0)
|
|
if err != nil {
|
|
return
|
|
}
|
|
if n == 0 {
|
|
return
|
|
}
|
|
n = sizes[0]
|
|
return
|
|
} else {
|
|
return t.tunFile.Read(p)
|
|
}
|
|
}
|
|
|
|
// handleVirtioRead splits in into bufs, leaving offset bytes at the front of
|
|
// each buffer. It mutates sizes to reflect the size of each element of bufs,
|
|
// and returns the number of packets read.
|
|
func handleVirtioRead(in []byte, bufs [][]byte, sizes []int, offset int) (int, error) {
|
|
var hdr virtioNetHdr
|
|
err := hdr.decode(in)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
in = in[virtioNetHdrLen:]
|
|
|
|
options, err := hdr.toGSOOptions()
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
// Don't trust HdrLen from the kernel as it can be equal to the length
|
|
// of the entire first packet when the kernel is handling it as part of a
|
|
// FORWARD path. Instead, parse the transport header length and add it onto
|
|
// CsumStart, which is synonymous for IP header length.
|
|
if options.GSOType == GSOUDPL4 {
|
|
options.HdrLen = options.CsumStart + 8
|
|
} else if options.GSOType != GSONone {
|
|
if len(in) <= int(options.CsumStart+12) {
|
|
return 0, errors.New("packet is too short")
|
|
}
|
|
|
|
tcpHLen := uint16(in[options.CsumStart+12] >> 4 * 4)
|
|
if tcpHLen < 20 || tcpHLen > 60 {
|
|
// A TCP header must be between 20 and 60 bytes in length.
|
|
return 0, fmt.Errorf("tcp header len is invalid: %d", tcpHLen)
|
|
}
|
|
options.HdrLen = options.CsumStart + tcpHLen
|
|
}
|
|
|
|
return GSOSplit(in, options, bufs, sizes, offset)
|
|
}
|
|
|
|
func (t *NativeTun) Write(p []byte) (n int, err error) {
|
|
if t.vnetHdr {
|
|
buffer := buf.Get(virtioNetHdrLen + len(p))
|
|
copy(buffer[virtioNetHdrLen:], p)
|
|
_, err = t.BatchWrite([][]byte{buffer}, virtioNetHdrLen)
|
|
buf.Put(buffer)
|
|
if err != nil {
|
|
return
|
|
}
|
|
n = len(p)
|
|
return
|
|
}
|
|
return t.tunFile.Write(p)
|
|
}
|
|
|
|
func (t *NativeTun) WriteVectorised(buffers []*buf.Buffer) error {
|
|
if t.vnetHdr {
|
|
n := buf.LenMulti(buffers)
|
|
buffer := buf.NewSize(virtioNetHdrLen + n)
|
|
buffer.Truncate(virtioNetHdrLen)
|
|
buf.CopyMulti(buffer.Extend(n), buffers)
|
|
_, err := t.tunFile.Write(buffer.Bytes())
|
|
buffer.Release()
|
|
return err
|
|
} else {
|
|
return t.tunWriter.WriteVectorised(buffers)
|
|
}
|
|
}
|
|
|
|
func (t *NativeTun) FrontHeadroom() int {
|
|
if t.vnetHdr {
|
|
return virtioNetHdrLen
|
|
}
|
|
return 0
|
|
}
|
|
|
|
func (t *NativeTun) BatchSize() int {
|
|
if !t.vnetHdr {
|
|
return 1
|
|
}
|
|
/* // Not works on some devices: https://github.com/SagerNet/sing-box/issues/1605
|
|
batchSize := int(gsoMaxSize/t.options.MTU) * 2
|
|
if batchSize > idealBatchSize {
|
|
batchSize = idealBatchSize
|
|
}
|
|
return batchSize*/
|
|
return idealBatchSize
|
|
}
|
|
|
|
func (t *NativeTun) BatchRead(buffers [][]byte, offset int, readN []int) (n int, err error) {
|
|
t.readAccess.Lock()
|
|
defer t.readAccess.Unlock()
|
|
n, err = t.tunFile.Read(t.writeBuffer)
|
|
if err != nil {
|
|
return
|
|
}
|
|
return handleVirtioRead(t.writeBuffer[:n], buffers, readN, offset)
|
|
}
|
|
|
|
func (t *NativeTun) BatchWrite(buffers [][]byte, offset int) (int, error) {
|
|
t.writeAccess.Lock()
|
|
defer func() {
|
|
t.tcpGROTable.reset()
|
|
t.udpGROTable.reset()
|
|
t.writeAccess.Unlock()
|
|
}()
|
|
var (
|
|
errs error
|
|
total int
|
|
)
|
|
t.gsoToWrite = t.gsoToWrite[:0]
|
|
if t.vnetHdr {
|
|
err := handleGRO(buffers, offset, t.tcpGROTable, t.udpGROTable, t.gro, &t.gsoToWrite)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
offset -= virtioNetHdrLen
|
|
} else {
|
|
for i := range buffers {
|
|
t.gsoToWrite = append(t.gsoToWrite, i)
|
|
}
|
|
}
|
|
for _, toWrite := range t.gsoToWrite {
|
|
n, err := t.tunFile.Write(buffers[toWrite][offset:])
|
|
if errors.Is(err, syscall.EBADFD) {
|
|
return total, os.ErrClosed
|
|
}
|
|
if err != nil {
|
|
errs = errors.Join(errs, err)
|
|
} else {
|
|
total += n
|
|
}
|
|
}
|
|
return total, errs
|
|
}
|
|
|
|
func (t *NativeTun) TXChecksumOffload() bool {
|
|
return t.txChecksumOffload
|
|
}
|
|
|
|
func prefixToIPNet(prefix netip.Prefix) *net.IPNet {
|
|
return &net.IPNet{
|
|
IP: prefix.Addr().AsSlice(),
|
|
Mask: net.CIDRMask(prefix.Bits(), prefix.Addr().BitLen()),
|
|
}
|
|
}
|
|
|
|
func (t *NativeTun) UpdateRouteOptions(tunOptions Options) error {
|
|
if t.options.FileDescriptor > 0 {
|
|
return nil
|
|
} else if !t.options.AutoRoute {
|
|
t.options = tunOptions
|
|
return nil
|
|
}
|
|
tunLink, err := netlink.LinkByName(t.options.Name)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = t.unsetRoute0(tunLink)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
t.options = tunOptions
|
|
return t.setRoute(tunLink)
|
|
}
|
|
|
|
func (t *NativeTun) routes(tunLink netlink.Link) ([]netlink.Route, error) {
|
|
routeRanges, err := t.options.BuildAutoRouteRanges(false)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
// Do not create gateway on linux by default
|
|
gateway4, gateway6 := t.options.Inet4GatewayAddr(), t.options.Inet6GatewayAddr()
|
|
return common.Map(routeRanges, func(it netip.Prefix) netlink.Route {
|
|
var gateway net.IP
|
|
if it.Addr().Is4() && !gateway4.IsUnspecified() {
|
|
gateway = gateway4.AsSlice()
|
|
} else if it.Addr().Is6() && !gateway6.IsUnspecified() {
|
|
gateway = gateway6.AsSlice()
|
|
}
|
|
return netlink.Route{
|
|
Dst: prefixToIPNet(it),
|
|
Gw: gateway,
|
|
LinkIndex: tunLink.Attrs().Index,
|
|
Table: t.options.IPRoute2TableIndex,
|
|
}
|
|
}), nil
|
|
}
|
|
|
|
func (t *NativeTun) nextIndex6() int {
|
|
ruleList, err := netlink.RuleList(netlink.FAMILY_V6)
|
|
if err != nil {
|
|
return -1
|
|
}
|
|
var minIndex int
|
|
for _, rule := range ruleList {
|
|
if rule.Priority > 0 && (minIndex == 0 || rule.Priority < minIndex) {
|
|
minIndex = rule.Priority
|
|
}
|
|
}
|
|
minIndex--
|
|
t.ruleIndex6 = append(t.ruleIndex6, minIndex)
|
|
return minIndex
|
|
}
|
|
|
|
func (t *NativeTun) rules() []*netlink.Rule {
|
|
if !t.options.AutoRoute {
|
|
if len(t.options.Inet6Address) > 0 {
|
|
it := netlink.NewRule()
|
|
it.Priority = t.nextIndex6()
|
|
it.Table = t.options.IPRoute2TableIndex
|
|
it.Family = unix.AF_INET6
|
|
it.OifName = t.options.Name
|
|
return []*netlink.Rule{it}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
var p4, p6 bool
|
|
var pRule int
|
|
if len(t.options.Inet4Address) > 0 {
|
|
p4 = true
|
|
pRule += 1
|
|
}
|
|
if len(t.options.Inet6Address) > 0 {
|
|
p6 = true
|
|
pRule += 1
|
|
}
|
|
if pRule == 0 {
|
|
return []*netlink.Rule{}
|
|
}
|
|
|
|
var rules []*netlink.Rule
|
|
var it *netlink.Rule
|
|
|
|
excludeRanges := t.options.ExcludedRanges()
|
|
|
|
ruleStart := t.options.IPRoute2RuleIndex
|
|
priority := ruleStart
|
|
priority6 := priority
|
|
|
|
if t.options.AutoRedirectMarkMode {
|
|
if p4 {
|
|
it = netlink.NewRule()
|
|
it.Priority = priority
|
|
it.Mark = t.options.AutoRedirectOutputMark
|
|
it.MarkSet = true
|
|
it.Goto = priority + 2
|
|
it.Family = unix.AF_INET
|
|
rules = append(rules, it)
|
|
priority++
|
|
|
|
it = netlink.NewRule()
|
|
it.Priority = priority
|
|
it.Mark = t.options.AutoRedirectInputMark
|
|
it.MarkSet = true
|
|
it.Table = t.options.IPRoute2TableIndex
|
|
it.Family = unix.AF_INET
|
|
rules = append(rules, it)
|
|
priority++
|
|
|
|
it = netlink.NewRule()
|
|
it.Priority = priority
|
|
it.Family = unix.AF_INET
|
|
rules = append(rules, it)
|
|
}
|
|
if p6 {
|
|
it = netlink.NewRule()
|
|
it.Priority = priority6
|
|
it.Mark = t.options.AutoRedirectOutputMark
|
|
it.MarkSet = true
|
|
it.Goto = priority6 + 2
|
|
it.Family = unix.AF_INET6
|
|
rules = append(rules, it)
|
|
priority6++
|
|
|
|
it = netlink.NewRule()
|
|
it.Priority = priority6
|
|
it.Mark = t.options.AutoRedirectInputMark
|
|
it.MarkSet = true
|
|
it.Table = t.options.IPRoute2TableIndex
|
|
it.Family = unix.AF_INET6
|
|
rules = append(rules, it)
|
|
priority6++
|
|
|
|
it = netlink.NewRule()
|
|
it.Priority = priority6
|
|
it.Family = unix.AF_INET6
|
|
rules = append(rules, it)
|
|
}
|
|
return rules
|
|
}
|
|
|
|
nopPriority := ruleStart + 10
|
|
for _, excludeRange := range excludeRanges {
|
|
if p4 {
|
|
it = netlink.NewRule()
|
|
it.Priority = priority
|
|
it.UIDRange = netlink.NewRuleUIDRange(excludeRange.Start, excludeRange.End)
|
|
it.Goto = nopPriority
|
|
it.Family = unix.AF_INET
|
|
rules = append(rules, it)
|
|
}
|
|
if p6 {
|
|
it = netlink.NewRule()
|
|
it.Priority = priority6
|
|
it.UIDRange = netlink.NewRuleUIDRange(excludeRange.Start, excludeRange.End)
|
|
it.Goto = nopPriority
|
|
it.Family = unix.AF_INET6
|
|
rules = append(rules, it)
|
|
}
|
|
}
|
|
if len(excludeRanges) > 0 {
|
|
if p4 {
|
|
priority++
|
|
}
|
|
if p6 {
|
|
priority6++
|
|
}
|
|
}
|
|
if len(t.options.IncludeInterface) > 0 {
|
|
matchPriority := priority + 2*len(t.options.IncludeInterface) + 1
|
|
for _, includeInterface := range t.options.IncludeInterface {
|
|
if p4 {
|
|
it = netlink.NewRule()
|
|
it.Priority = priority
|
|
it.IifName = includeInterface
|
|
it.Goto = matchPriority
|
|
it.Family = unix.AF_INET
|
|
rules = append(rules, it)
|
|
priority++
|
|
}
|
|
if p6 {
|
|
it = netlink.NewRule()
|
|
it.Priority = priority6
|
|
it.IifName = includeInterface
|
|
it.Goto = matchPriority
|
|
it.Family = unix.AF_INET6
|
|
rules = append(rules, it)
|
|
priority6++
|
|
}
|
|
}
|
|
if p4 {
|
|
it = netlink.NewRule()
|
|
it.Priority = priority
|
|
it.Family = unix.AF_INET
|
|
it.Goto = nopPriority
|
|
rules = append(rules, it)
|
|
priority++
|
|
|
|
it = netlink.NewRule()
|
|
it.Priority = matchPriority
|
|
it.Family = unix.AF_INET
|
|
rules = append(rules, it)
|
|
priority++
|
|
}
|
|
if p6 {
|
|
it = netlink.NewRule()
|
|
it.Priority = priority6
|
|
it.Family = unix.AF_INET6
|
|
it.Goto = nopPriority
|
|
rules = append(rules, it)
|
|
priority6++
|
|
|
|
it = netlink.NewRule()
|
|
it.Priority = matchPriority
|
|
it.Family = unix.AF_INET6
|
|
rules = append(rules, it)
|
|
priority6++
|
|
}
|
|
} else if len(t.options.ExcludeInterface) > 0 {
|
|
for _, excludeInterface := range t.options.ExcludeInterface {
|
|
if p4 {
|
|
it = netlink.NewRule()
|
|
it.Priority = priority
|
|
it.IifName = excludeInterface
|
|
it.Goto = nopPriority
|
|
it.Family = unix.AF_INET
|
|
rules = append(rules, it)
|
|
priority++
|
|
}
|
|
if p6 {
|
|
it = netlink.NewRule()
|
|
it.Priority = priority6
|
|
it.IifName = excludeInterface
|
|
it.Goto = nopPriority
|
|
it.Family = unix.AF_INET6
|
|
rules = append(rules, it)
|
|
priority6++
|
|
}
|
|
}
|
|
}
|
|
|
|
if runtime.GOOS == "android" && t.options.InterfaceMonitor.AndroidVPNEnabled() {
|
|
const protectedFromVPN = 0x20000
|
|
if p4 {
|
|
it = netlink.NewRule()
|
|
if t.options.InterfaceMonitor.OverrideAndroidVPN() {
|
|
it.Mark = protectedFromVPN
|
|
it.MarkSet = true
|
|
}
|
|
it.Mask = protectedFromVPN
|
|
it.Priority = priority
|
|
it.Family = unix.AF_INET
|
|
it.Goto = nopPriority
|
|
rules = append(rules, it)
|
|
priority++
|
|
}
|
|
if p6 {
|
|
it = netlink.NewRule()
|
|
if t.options.InterfaceMonitor.OverrideAndroidVPN() {
|
|
it.Mark = protectedFromVPN
|
|
it.MarkSet = true
|
|
}
|
|
it.Mask = protectedFromVPN
|
|
it.Family = unix.AF_INET6
|
|
it.Priority = priority6
|
|
it.Goto = nopPriority
|
|
rules = append(rules, it)
|
|
priority6++
|
|
}
|
|
}
|
|
|
|
if t.options.StrictRoute {
|
|
if !p4 {
|
|
it = netlink.NewRule()
|
|
it.Priority = priority
|
|
it.Family = unix.AF_INET
|
|
it.Type = unix.FR_ACT_UNREACHABLE
|
|
rules = append(rules, it)
|
|
priority++
|
|
}
|
|
if !p6 {
|
|
it = netlink.NewRule()
|
|
it.Priority = priority6
|
|
it.Family = unix.AF_INET6
|
|
it.Type = unix.FR_ACT_UNREACHABLE
|
|
rules = append(rules, it)
|
|
priority6++
|
|
}
|
|
}
|
|
|
|
if runtime.GOOS != "android" {
|
|
if p4 {
|
|
for _, address := range t.options.Inet4Address {
|
|
it = netlink.NewRule()
|
|
it.Priority = priority
|
|
it.Dst = address.Masked()
|
|
it.Table = t.options.IPRoute2TableIndex
|
|
it.Family = unix.AF_INET
|
|
rules = append(rules, it)
|
|
}
|
|
priority++
|
|
|
|
it = netlink.NewRule()
|
|
it.Priority = priority
|
|
it.Table = t.options.IPRoute2TableIndex
|
|
it.SuppressPrefixlen = 0
|
|
it.Family = unix.AF_INET
|
|
rules = append(rules, it)
|
|
priority++
|
|
}
|
|
if p6 {
|
|
it = netlink.NewRule()
|
|
it.Priority = priority6
|
|
it.Table = t.options.IPRoute2TableIndex
|
|
it.SuppressPrefixlen = 0
|
|
it.Family = unix.AF_INET6
|
|
rules = append(rules, it)
|
|
priority6++
|
|
}
|
|
if p4 {
|
|
it = netlink.NewRule()
|
|
it.Priority = priority
|
|
it.Invert = true
|
|
it.Dport = netlink.NewRulePortRange(53, 53)
|
|
it.Table = unix.RT_TABLE_MAIN
|
|
it.SuppressPrefixlen = 0
|
|
it.Family = unix.AF_INET
|
|
rules = append(rules, it)
|
|
}
|
|
if p4 && !t.options.StrictRoute {
|
|
it = netlink.NewRule()
|
|
it.Priority = priority
|
|
it.IPProto = syscall.IPPROTO_ICMP
|
|
it.Goto = nopPriority
|
|
it.Family = unix.AF_INET
|
|
rules = append(rules, it)
|
|
}
|
|
if p6 {
|
|
it = netlink.NewRule()
|
|
it.Priority = priority6
|
|
it.Invert = true
|
|
it.Dport = netlink.NewRulePortRange(53, 53)
|
|
it.Table = unix.RT_TABLE_MAIN
|
|
it.SuppressPrefixlen = 0
|
|
it.Family = unix.AF_INET6
|
|
rules = append(rules, it)
|
|
}
|
|
|
|
if p6 && !t.options.StrictRoute {
|
|
it = netlink.NewRule()
|
|
it.Priority = priority6
|
|
it.IPProto = syscall.IPPROTO_ICMPV6
|
|
it.Goto = nopPriority
|
|
it.Family = unix.AF_INET6
|
|
rules = append(rules, it)
|
|
priority6++
|
|
}
|
|
}
|
|
if p4 {
|
|
it = netlink.NewRule()
|
|
it.Priority = priority
|
|
it.IifName = t.options.Name
|
|
it.Goto = nopPriority
|
|
it.Family = unix.AF_INET
|
|
rules = append(rules, it)
|
|
priority++
|
|
|
|
it = netlink.NewRule()
|
|
it.Priority = priority
|
|
it.Invert = true
|
|
it.IifName = "lo"
|
|
it.Table = t.options.IPRoute2TableIndex
|
|
it.Family = unix.AF_INET
|
|
rules = append(rules, it)
|
|
|
|
it = netlink.NewRule()
|
|
it.Priority = priority
|
|
it.IifName = "lo"
|
|
it.Src = netip.PrefixFrom(netip.IPv4Unspecified(), 32)
|
|
it.Table = t.options.IPRoute2TableIndex
|
|
it.Family = unix.AF_INET
|
|
rules = append(rules, it)
|
|
|
|
for _, address := range t.options.Inet4Address {
|
|
it = netlink.NewRule()
|
|
it.Priority = priority
|
|
it.IifName = "lo"
|
|
it.Src = address.Masked()
|
|
it.Table = t.options.IPRoute2TableIndex
|
|
it.Family = unix.AF_INET
|
|
rules = append(rules, it)
|
|
}
|
|
// priority++
|
|
}
|
|
if p6 {
|
|
it = netlink.NewRule()
|
|
it.Priority = priority6
|
|
it.IifName = t.options.Name
|
|
it.Goto = nopPriority
|
|
it.Family = unix.AF_INET6
|
|
rules = append(rules, it)
|
|
|
|
it = netlink.NewRule()
|
|
it.Priority = priority6
|
|
it.IifName = "lo"
|
|
it.Src = netip.PrefixFrom(netip.IPv6Unspecified(), 1)
|
|
it.Goto = nopPriority
|
|
it.Family = unix.AF_INET6
|
|
rules = append(rules, it)
|
|
|
|
it = netlink.NewRule()
|
|
it.Priority = priority6
|
|
it.IifName = "lo"
|
|
it.Src = netip.PrefixFrom(netip.AddrFrom16([16]byte{0: 128}), 1)
|
|
it.Goto = nopPriority
|
|
it.Family = unix.AF_INET6
|
|
rules = append(rules, it)
|
|
priority6++
|
|
|
|
for _, address := range t.options.Inet6Address {
|
|
it = netlink.NewRule()
|
|
it.Priority = priority6
|
|
it.IifName = "lo"
|
|
it.Src = address.Masked()
|
|
it.Table = t.options.IPRoute2TableIndex
|
|
it.Family = unix.AF_INET6
|
|
rules = append(rules, it)
|
|
}
|
|
priority6++
|
|
|
|
it = netlink.NewRule()
|
|
it.Priority = priority6
|
|
it.Table = t.options.IPRoute2TableIndex
|
|
it.Family = unix.AF_INET6
|
|
rules = append(rules, it)
|
|
// priority6++
|
|
}
|
|
if p4 {
|
|
it = netlink.NewRule()
|
|
it.Priority = nopPriority
|
|
it.Family = unix.AF_INET
|
|
rules = append(rules, it)
|
|
}
|
|
if p6 {
|
|
it = netlink.NewRule()
|
|
it.Priority = nopPriority
|
|
it.Family = unix.AF_INET6
|
|
rules = append(rules, it)
|
|
}
|
|
return rules
|
|
}
|
|
|
|
func (t *NativeTun) setRoute(tunLink netlink.Link) error {
|
|
routes, err := t.routes(tunLink)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for i, route := range routes {
|
|
err := netlink.RouteAdd(&route)
|
|
if err != nil {
|
|
return E.Cause(err, "add route ", i)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (t *NativeTun) setRules() error {
|
|
for i, rule := range t.rules() {
|
|
err := netlink.RuleAdd(rule)
|
|
if err != nil {
|
|
return E.Cause(err, "add rule ", i, "/", len(t.rules()))
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (t *NativeTun) unsetRoute() error {
|
|
if t.options.FileDescriptor > 0 {
|
|
return nil
|
|
}
|
|
tunLink, err := netlink.LinkByName(t.options.Name)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return t.unsetRoute0(tunLink)
|
|
}
|
|
|
|
func (t *NativeTun) unsetRoute0(tunLink netlink.Link) error {
|
|
if routes, err := t.routes(tunLink); err == nil {
|
|
for _, route := range routes {
|
|
_ = netlink.RouteDel(&route)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (t *NativeTun) unsetRules() error {
|
|
if t.options.FileDescriptor > 0 {
|
|
return nil
|
|
}
|
|
if len(t.ruleIndex6) > 0 {
|
|
for _, index := range t.ruleIndex6 {
|
|
ruleToDel := netlink.NewRule()
|
|
ruleToDel.Family = unix.AF_INET6
|
|
ruleToDel.Priority = index
|
|
err := netlink.RuleDel(ruleToDel)
|
|
if err != nil {
|
|
return E.Cause(err, "unset rule6 ", index)
|
|
}
|
|
}
|
|
t.ruleIndex6 = nil
|
|
}
|
|
if t.options.AutoRoute {
|
|
ruleList, err := netlink.RuleList(netlink.FAMILY_ALL)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for _, rule := range ruleList {
|
|
ruleStart := t.options.IPRoute2RuleIndex
|
|
ruleEnd := ruleStart + 10
|
|
if rule.Priority >= ruleStart && rule.Priority <= ruleEnd {
|
|
ruleToDel := netlink.NewRule()
|
|
ruleToDel.Family = rule.Family
|
|
ruleToDel.Priority = rule.Priority
|
|
err = netlink.RuleDel(ruleToDel)
|
|
if err != nil {
|
|
return E.Cause(err, "unset rule ", rule.Priority, " for ", rule.Family)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (t *NativeTun) resetRules() error {
|
|
t.unsetRules()
|
|
return t.setRules()
|
|
}
|
|
|
|
func (t *NativeTun) routeUpdate(_ *control.Interface, flags int) {
|
|
if flags&FlagAndroidVPNUpdate == 0 {
|
|
return
|
|
}
|
|
err := t.resetRules()
|
|
if err != nil {
|
|
if t.options.Logger != nil {
|
|
t.options.Logger.Error(E.Cause(err, "reset route"))
|
|
}
|
|
}
|
|
}
|
|
|
|
func (t *NativeTun) setSearchDomainForSystemdResolved() {
|
|
if t.options.EXP_DisableDNSHijack {
|
|
return
|
|
}
|
|
ctlPath, err := exec.LookPath("resolvectl")
|
|
if err != nil {
|
|
return
|
|
}
|
|
dnsServer := t.options.DNSServers
|
|
if len(dnsServer) == 0 {
|
|
if len(t.options.Inet4Address) > 0 && HasNextAddress(t.options.Inet4Address[0], 1) {
|
|
dnsServer = append(dnsServer, t.options.Inet4Address[0].Addr().Next())
|
|
}
|
|
if len(t.options.Inet6Address) > 0 && HasNextAddress(t.options.Inet6Address[0], 1) {
|
|
dnsServer = append(dnsServer, t.options.Inet6Address[0].Addr().Next())
|
|
}
|
|
}
|
|
if len(dnsServer) == 0 {
|
|
return
|
|
}
|
|
go func() {
|
|
_ = shell.Exec(ctlPath, "domain", t.options.Name, "~.").Run()
|
|
_ = shell.Exec(ctlPath, "default-route", t.options.Name, "true").Run()
|
|
_ = shell.Exec(ctlPath, append([]string{"dns", t.options.Name}, common.Map(dnsServer, netip.Addr.String)...)...).Run()
|
|
}()
|
|
}
|