package tun import ( "math/rand" "net" "net/netip" "os" "os/exec" "runtime" "syscall" "unsafe" "github.com/sagernet/netlink" "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/bufio" 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" ) type NativeTun struct { tunFd int tunFile *os.File interfaceCallback *list.Element[DefaultInterfaceUpdateCallback] options Options ruleIndex6 []int } func New(options Options) (Tun, error) { if options.FileDescriptor == 0 { tunFd, err := open(options.Name) 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, } runtime.SetFinalizer(nativeTun.tunFile, nil) err = nativeTun.configure(tunLink) if err != nil { return nil, E.Errors(err, unix.Close(tunFd)) } return nativeTun, nil } else { nativeTun := &NativeTun{ tunFd: options.FileDescriptor, tunFile: os.NewFile(uintptr(options.FileDescriptor), "tun"), options: options, } runtime.SetFinalizer(nativeTun.tunFile, nil) return nativeTun, nil } } func (t *NativeTun) Read(p []byte) (n int, err error) { return t.tunFile.Read(p) } func (t *NativeTun) Write(p []byte) (n int, err error) { return t.tunFile.Write(p) } func (t *NativeTun) CreateVectorisedWriter() N.VectorisedWriter { return bufio.NewVectorisedWriter(t.tunFile) } var controlPath string func init() { const defaultTunPath = "/dev/net/tun" const androidTunPath = "/dev/tun" if rw.FileExists(androidTunPath) { controlPath = androidTunPath } else { controlPath = defaultTunPath } } func open(name string) (int, error) { fd, err := unix.Open(controlPath, unix.O_RDWR, 0) if err != nil { return -1, err } var ifr struct { name [16]byte flags uint16 _ [22]byte } copy(ifr.name[:], name) ifr.flags = unix.IFF_TUN | unix.IFF_NO_PI _, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(fd), unix.TUNSETIFF, uintptr(unsafe.Pointer(&ifr))) if errno != 0 { unix.Close(fd) return -1, errno } if err = unix.SetNonblock(fd, true); err != nil { unix.Close(fd) return -1, err } return fd, nil } func (t *NativeTun) configure(tunLink netlink.Link) error { err := netlink.LinkSetMTU(tunLink, int(t.options.MTU)) if err == unix.EPERM { // unprivileged 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 } } } err = netlink.LinkSetUp(tunLink) if err != nil { return err } if t.options.TableIndex == 0 { for { t.options.TableIndex = int(rand.Uint32()) routeList, fErr := netlink.RouteListFiltered(netlink.FAMILY_ALL, &netlink.Route{Table: t.options.TableIndex}, 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) routes(tunLink netlink.Link) []netlink.Route { var routes []netlink.Route if len(t.options.Inet4Address) > 0 { if t.options.AutoRoute { if len(t.options.Inet4RouteAddress) > 0 { for _, addr := range t.options.Inet4RouteAddress { routes = append(routes, netlink.Route{ Dst: &net.IPNet{ IP: addr.Addr().AsSlice(), Mask: net.CIDRMask(addr.Bits(), 32), }, LinkIndex: tunLink.Attrs().Index, Table: t.options.TableIndex, }) } } else { routes = append(routes, netlink.Route{ Dst: &net.IPNet{ IP: net.IPv4zero, Mask: net.CIDRMask(0, 32), }, LinkIndex: tunLink.Attrs().Index, Table: t.options.TableIndex, }) } } } if len(t.options.Inet6Address) > 0 { if len(t.options.Inet6RouteAddress) > 0 { for _, addr := range t.options.Inet6RouteAddress { routes = append(routes, netlink.Route{ Dst: &net.IPNet{ IP: addr.Addr().AsSlice(), Mask: net.CIDRMask(addr.Bits(), 128), }, LinkIndex: tunLink.Attrs().Index, Table: t.options.TableIndex, }) } } else { routes = append(routes, netlink.Route{ Dst: &net.IPNet{ IP: net.IPv6zero, Mask: net.CIDRMask(0, 128), }, LinkIndex: tunLink.Attrs().Index, Table: t.options.TableIndex, }) } } return routes } const ( ruleStart = 9000 ruleEnd = ruleStart + 10 ) 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.TableIndex 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() priority := ruleStart priority6 := priority nopPriority := ruleEnd 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++ it = netlink.NewRule() it.Priority = priority it.OifName = 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++ it = netlink.NewRule() it.Priority = priority6 it.OifName = 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++ it = netlink.NewRule() it.Priority = priority it.OifName = 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++ it = netlink.NewRule() it.Priority = priority6 it.OifName = 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 || t.options.StrictRoute { it = netlink.NewRule() if t.options.InterfaceMonitor.OverrideAndroidVPN() { it.Mark = protectedFromVPN } it.Mask = protectedFromVPN it.Priority = priority it.Family = unix.AF_INET it.Goto = nopPriority rules = append(rules, it) priority++ } if p6 || t.options.StrictRoute { it = netlink.NewRule() if t.options.InterfaceMonitor.OverrideAndroidVPN() { it.Mark = protectedFromVPN } 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.TableIndex it.Family = unix.AF_INET rules = append(rules, it) } priority++ } /*if p6 { it = netlink.NewRule() it.Priority = priority it.Dst = t.options.Inet6Address.Masked() it.Table = tunTableIndex it.Family = unix.AF_INET6 rules = append(rules, it) }*/ if p4 { it = netlink.NewRule() it.Priority = priority it.IPProto = syscall.IPPROTO_ICMP it.Goto = nopPriority it.Family = unix.AF_INET rules = append(rules, it) priority++ } if p6 { 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.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 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 p4 { if t.options.StrictRoute { it = netlink.NewRule() it.Priority = priority it.Table = t.options.TableIndex it.Family = unix.AF_INET rules = append(rules, it) } else { it = netlink.NewRule() it.Priority = priority it.Invert = true it.IifName = "lo" it.Table = t.options.TableIndex 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.TableIndex 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.TableIndex it.Family = unix.AF_INET rules = append(rules, it) } } priority++ } if p6 { if !t.options.StrictRoute { 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++ } it = netlink.NewRule() it.Priority = priority6 it.Table = t.options.TableIndex 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 { for i, route := range t.routes(tunLink) { 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 { for _, route := range t.routes(tunLink) { _ = 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 { 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(event int) { if event&EventAndroidVPNUpdate == 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() { ctlPath, err := exec.LookPath("resolvectl") if err != nil { return } var dnsServer []netip.Addr if len(t.options.Inet4Address) > 0 { dnsServer = append(dnsServer, t.options.Inet4Address[0].Addr().Next()) } if len(t.options.Inet6Address) > 0 { dnsServer = append(dnsServer, t.options.Inet6Address[0].Addr().Next()) } shell.Exec(ctlPath, "domain", t.options.Name, "~.").Start() if t.options.AutoRoute { shell.Exec(ctlPath, "default-route", t.options.Name, "true").Start() shell.Exec(ctlPath, append([]string{"dns", t.options.Name}, common.Map(dnsServer, netip.Addr.String)...)...).Start() } }