Compare commits
6 Commits
v0.8.2
...
v0.4.0-bet
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
74159499d0 | ||
|
|
831469ad4e | ||
|
|
66e34abeb1 | ||
|
|
a604946fc7 | ||
|
|
5bbbe1f6e4 | ||
|
|
7262c87111 |
14
go.mod
14
go.mod
@@ -7,14 +7,20 @@ require (
|
||||
github.com/go-ole/go-ole v1.3.0
|
||||
github.com/sagernet/gvisor v0.0.0-20240428053021-e691de28565f
|
||||
github.com/sagernet/netlink v0.0.0-20240523065131-45e60152f9ba
|
||||
github.com/sagernet/sing v0.3.8
|
||||
github.com/sagernet/nftables v0.3.0-beta.2
|
||||
github.com/sagernet/sing v0.5.0-alpha.8
|
||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba
|
||||
golang.org/x/net v0.24.0
|
||||
golang.org/x/sys v0.19.0
|
||||
golang.org/x/net v0.25.0
|
||||
golang.org/x/sys v0.20.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/google/btree v1.1.2 // indirect
|
||||
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 // indirect
|
||||
github.com/google/go-cmp v0.5.9 // indirect
|
||||
github.com/josharian/native v1.1.0 // indirect
|
||||
github.com/mdlayher/netlink v1.7.2 // indirect
|
||||
github.com/mdlayher/socket v0.4.1 // indirect
|
||||
github.com/vishvananda/netns v0.0.4 // indirect
|
||||
golang.org/x/sync v0.1.0 // indirect
|
||||
golang.org/x/time v0.5.0 // indirect
|
||||
)
|
||||
|
||||
29
go.sum
29
go.sum
@@ -5,24 +5,35 @@ github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
|
||||
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
|
||||
github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU=
|
||||
github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA=
|
||||
github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
|
||||
github.com/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/g=
|
||||
github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw=
|
||||
github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U=
|
||||
github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/sagernet/gvisor v0.0.0-20240428053021-e691de28565f h1:NkhuupzH5ch7b/Y/6ZHJWrnNLoiNnSJaow6DPb8VW2I=
|
||||
github.com/sagernet/gvisor v0.0.0-20240428053021-e691de28565f/go.mod h1:KXmw+ouSJNOsuRpg4wgwwCQuunrGz4yoAqQjsLjc6N0=
|
||||
github.com/sagernet/netlink v0.0.0-20240523065131-45e60152f9ba h1:EY5AS7CCtfmARNv2zXUOrsEMPFDGYxaw65JzA2p51Vk=
|
||||
github.com/sagernet/netlink v0.0.0-20240523065131-45e60152f9ba/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM=
|
||||
github.com/sagernet/sing v0.3.8 h1:gm4JKalPhydMYX2zFOTnnd4TXtM/16WFRqSjMepYQQk=
|
||||
github.com/sagernet/sing v0.3.8/go.mod h1:+60H3Cm91RnL9dpVGWDPHt0zTQImO9Vfqt9a4rSambI=
|
||||
github.com/sagernet/nftables v0.3.0-beta.2 h1:yKqMl4Dpb6nKxAmlE6fXjJRlLO2c1f2wyNFBg4hBr8w=
|
||||
github.com/sagernet/nftables v0.3.0-beta.2/go.mod h1:OQXAjvjNGGFxaTgVCSTRIhYB5/llyVDeapVoENYBDS8=
|
||||
github.com/sagernet/sing v0.5.0-alpha.8 h1:2KtzBvKP6hwknsi/G6H4vRgR4it31HQ6quLb0Woze7c=
|
||||
github.com/sagernet/sing v0.5.0-alpha.8/go.mod h1:Xh4KO9nGdvm4K/LVg9Xn9jSxJdqe9KcXbAzNC1S2qfw=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 h1:gga7acRE695APm9hlsSMoOoE65U4/TcqNj90mc69Rlg=
|
||||
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
|
||||
github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8=
|
||||
github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
|
||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M=
|
||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
|
||||
golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
|
||||
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
|
||||
golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
|
||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
|
||||
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
|
||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
||||
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
|
||||
@@ -42,6 +42,7 @@ type defaultInterfaceMonitor struct {
|
||||
defaultInterfaceName string
|
||||
defaultInterfaceIndex int
|
||||
androidVPNEnabled bool
|
||||
noRoute bool
|
||||
networkMonitor NetworkUpdateMonitor
|
||||
checkUpdateTimer *time.Timer
|
||||
element *list.Element[NetworkUpdateCallback]
|
||||
@@ -86,11 +87,16 @@ func (m *defaultInterfaceMonitor) postCheckUpdate() {
|
||||
}
|
||||
err = m.checkUpdate()
|
||||
if errors.Is(err, ErrNoRoute) {
|
||||
m.defaultInterfaceName = ""
|
||||
m.defaultInterfaceIndex = -1
|
||||
m.emit(EventNoRoute)
|
||||
if !m.noRoute {
|
||||
m.noRoute = true
|
||||
m.defaultInterfaceName = ""
|
||||
m.defaultInterfaceIndex = -1
|
||||
m.emit(EventNoRoute)
|
||||
}
|
||||
} else if err != nil {
|
||||
m.logger.Error("check interface: ", err)
|
||||
} else {
|
||||
m.noRoute = false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
22
redirect.go
Normal file
22
redirect.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package tun
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/sagernet/sing/common/logger"
|
||||
)
|
||||
|
||||
type AutoRedirect interface {
|
||||
Start() error
|
||||
Close() error
|
||||
}
|
||||
|
||||
type AutoRedirectOptions struct {
|
||||
TunOptions *Options
|
||||
Context context.Context
|
||||
Handler Handler
|
||||
Logger logger.Logger
|
||||
TableName string
|
||||
DisableNFTables bool
|
||||
CustomRedirectPort func() int
|
||||
}
|
||||
234
redirect_iptables.go
Normal file
234
redirect_iptables.go
Normal file
@@ -0,0 +1,234 @@
|
||||
//go:build linux
|
||||
|
||||
package tun
|
||||
|
||||
import (
|
||||
"net/netip"
|
||||
"os/exec"
|
||||
"strings"
|
||||
|
||||
"github.com/sagernet/sing/common"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
F "github.com/sagernet/sing/common/format"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func (r *autoRedirect) iptablesPathForFamily(family int) string {
|
||||
if family == unix.AF_INET {
|
||||
return r.iptablesPath
|
||||
} else {
|
||||
return r.ip6tablesPath
|
||||
}
|
||||
}
|
||||
|
||||
func (r *autoRedirect) setupIPTables(family int) error {
|
||||
tableNameOutput := r.tableName + "-output"
|
||||
tableNameForward := r.tableName + "-forward"
|
||||
tableNamePreRouteing := r.tableName + "-prerouting"
|
||||
iptablesPath := r.iptablesPathForFamily(family)
|
||||
redirectPort := r.redirectPort()
|
||||
// OUTPUT
|
||||
err := r.runShell(iptablesPath, "-t nat -N", tableNameOutput)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = r.runShell(iptablesPath, "-t nat -A", tableNameOutput,
|
||||
"-p tcp -o", r.tunOptions.Name,
|
||||
"-j REDIRECT --to-ports", redirectPort)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = r.runShell(iptablesPath, "-t nat -I OUTPUT -j", tableNameOutput)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if r.androidSu {
|
||||
return nil
|
||||
}
|
||||
// FORWARD
|
||||
err = r.runShell(iptablesPath, "-N", tableNameForward)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = r.runShell(iptablesPath, "-A", tableNameForward,
|
||||
"-i", r.tunOptions.Name, "-j", "ACCEPT")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = r.runShell(iptablesPath, "-A", tableNameForward,
|
||||
"-o", r.tunOptions.Name, "-j", "ACCEPT")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = r.runShell(iptablesPath, "-I FORWARD -j", tableNameForward)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// PREROUTING
|
||||
err = r.runShell(iptablesPath, "-t nat -N", tableNamePreRouteing)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var (
|
||||
routeAddress []netip.Prefix
|
||||
routeExcludeAddress []netip.Prefix
|
||||
)
|
||||
if family == unix.AF_INET {
|
||||
routeAddress = r.tunOptions.Inet4RouteAddress
|
||||
routeExcludeAddress = r.tunOptions.Inet4RouteExcludeAddress
|
||||
} else {
|
||||
routeAddress = r.tunOptions.Inet6RouteAddress
|
||||
routeExcludeAddress = r.tunOptions.Inet6RouteExcludeAddress
|
||||
}
|
||||
if len(routeAddress) > 0 && (len(r.tunOptions.IncludeInterface) > 0 || len(r.tunOptions.IncludeUID) > 0) {
|
||||
return E.New("`*_route_address` is conflict with `include_interface` or `include_uid`")
|
||||
}
|
||||
err = r.runShell(iptablesPath, "-t nat -A", tableNamePreRouteing,
|
||||
"-i", r.tunOptions.Name, "-j RETURN")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, address := range routeExcludeAddress {
|
||||
err = r.runShell(iptablesPath, "-t nat -A", tableNamePreRouteing,
|
||||
"-d", address.String(), "-j RETURN")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
for _, name := range r.tunOptions.ExcludeInterface {
|
||||
err = r.runShell(iptablesPath, "-t nat -A", tableNamePreRouteing,
|
||||
"-i", name, "-j RETURN")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
for _, uid := range r.tunOptions.ExcludeUID {
|
||||
err = r.runShell(iptablesPath, "-t nat -A", tableNamePreRouteing,
|
||||
"-m owner --uid-owner", uid, "-j RETURN")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if !r.tunOptions.EXP_DisableDNSHijack {
|
||||
dnsServer := common.Find(r.tunOptions.DNSServers, func(it netip.Addr) bool {
|
||||
return it.Is4() == (family == unix.AF_INET)
|
||||
})
|
||||
if !dnsServer.IsValid() {
|
||||
if family == unix.AF_INET {
|
||||
dnsServer = r.tunOptions.Inet4Address[0].Addr().Next()
|
||||
} else {
|
||||
dnsServer = r.tunOptions.Inet6Address[0].Addr().Next()
|
||||
}
|
||||
}
|
||||
if len(routeAddress) > 0 {
|
||||
for _, address := range routeAddress {
|
||||
err = r.runShell(iptablesPath, "-t nat -A", tableNamePreRouteing,
|
||||
"-d", address.String(), "-p udp --dport 53 -j DNAT --to", dnsServer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
} else if len(r.tunOptions.IncludeInterface) > 0 || len(r.tunOptions.IncludeUID) > 0 {
|
||||
for _, name := range r.tunOptions.IncludeInterface {
|
||||
err = r.runShell(iptablesPath, "-t nat -A", tableNamePreRouteing,
|
||||
"-i", name, "-p udp --dport 53 -j DNAT --to", dnsServer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
for _, uidRange := range r.tunOptions.IncludeUID {
|
||||
for uid := uidRange.Start; uid <= uidRange.End; uid++ {
|
||||
err = r.runShell(iptablesPath, "-t nat -A", tableNamePreRouteing,
|
||||
"-m owner --uid-owner", uid, "-p udp --dport 53 -j DNAT --to", dnsServer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
err = r.runShell(iptablesPath, "-t nat -A", tableNamePreRouteing,
|
||||
"-p udp --dport 53 -j DNAT --to", dnsServer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
err = r.runShell(iptablesPath, "-t nat -A", tableNamePreRouteing, "-m addrtype --dst-type LOCAL -j RETURN")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(routeAddress) > 0 {
|
||||
for _, address := range routeAddress {
|
||||
err = r.runShell(iptablesPath, "-t nat -A", tableNamePreRouteing,
|
||||
"-d", address.String(), "-p tcp -j REDIRECT --to-ports", redirectPort)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
} else if len(r.tunOptions.IncludeInterface) > 0 || len(r.tunOptions.IncludeUID) > 0 {
|
||||
for _, name := range r.tunOptions.IncludeInterface {
|
||||
err = r.runShell(iptablesPath, "-t nat -A", tableNamePreRouteing,
|
||||
"-i", name, "-p tcp -j REDIRECT --to-ports", redirectPort)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
for _, uidRange := range r.tunOptions.IncludeUID {
|
||||
for uid := uidRange.Start; uid <= uidRange.End; uid++ {
|
||||
err = r.runShell(iptablesPath, "-t nat -A", tableNamePreRouteing,
|
||||
"-m owner --uid-owner", uid, "-p tcp -j REDIRECT --to-ports", redirectPort)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
err = r.runShell(iptablesPath, "-t nat -A", tableNamePreRouteing,
|
||||
"-p tcp -j REDIRECT --to-ports", redirectPort)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
err = r.runShell(iptablesPath, "-t nat -I PREROUTING -j", tableNamePreRouteing)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *autoRedirect) cleanupIPTables(family int) {
|
||||
tableNameOutput := r.tableName + "-output"
|
||||
tableNameForward := r.tableName + "-forward"
|
||||
tableNamePreRouteing := r.tableName + "-prerouting"
|
||||
iptablesPath := r.iptablesPathForFamily(family)
|
||||
_ = r.runShell(iptablesPath, "-t nat -D OUTPUT -j", tableNameOutput)
|
||||
_ = r.runShell(iptablesPath, "-t nat -F", tableNameOutput)
|
||||
_ = r.runShell(iptablesPath, "-t nat -X", tableNameOutput)
|
||||
if !r.androidSu {
|
||||
_ = r.runShell(iptablesPath, "-D FORWARD -j", tableNameForward)
|
||||
_ = r.runShell(iptablesPath, "-F", tableNameForward)
|
||||
_ = r.runShell(iptablesPath, "-X", tableNameForward)
|
||||
_ = r.runShell(iptablesPath, "-t nat -D PREROUTING -j", tableNamePreRouteing)
|
||||
_ = r.runShell(iptablesPath, "-t nat -F", tableNamePreRouteing)
|
||||
_ = r.runShell(iptablesPath, "-t nat -X", tableNamePreRouteing)
|
||||
}
|
||||
}
|
||||
|
||||
func (r *autoRedirect) runShell(commands ...any) error {
|
||||
commandStr := strings.Join(F.MapToString(commands), " ")
|
||||
var command *exec.Cmd
|
||||
if r.androidSu {
|
||||
command = exec.Command(r.suPath, "-c", commandStr)
|
||||
} else {
|
||||
commandArray := strings.Split(commandStr, " ")
|
||||
command = exec.Command(commandArray[0], commandArray[1:]...)
|
||||
}
|
||||
combinedOutput, err := command.CombinedOutput()
|
||||
if err != nil {
|
||||
return E.Extend(err, F.ToString(commandStr, ": ", string(combinedOutput)))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
187
redirect_linux.go
Normal file
187
redirect_linux.go
Normal file
@@ -0,0 +1,187 @@
|
||||
package tun
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/netip"
|
||||
"os"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
|
||||
"github.com/sagernet/nftables"
|
||||
"github.com/sagernet/sing/common"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/sagernet/sing/common/logger"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
type autoRedirect struct {
|
||||
tunOptions *Options
|
||||
ctx context.Context
|
||||
handler Handler
|
||||
logger logger.Logger
|
||||
tableName string
|
||||
customRedirectPortFunc func() int
|
||||
customRedirectPort int
|
||||
redirectServer *redirectServer
|
||||
enableIPv4 bool
|
||||
enableIPv6 bool
|
||||
iptablesPath string
|
||||
ip6tablesPath string
|
||||
useNFTables bool
|
||||
androidSu bool
|
||||
suPath string
|
||||
}
|
||||
|
||||
func NewAutoRedirect(options AutoRedirectOptions) (AutoRedirect, error) {
|
||||
r := &autoRedirect{
|
||||
tunOptions: options.TunOptions,
|
||||
ctx: options.Context,
|
||||
handler: options.Handler,
|
||||
logger: options.Logger,
|
||||
tableName: options.TableName,
|
||||
useNFTables: runtime.GOOS != "android" && !options.DisableNFTables,
|
||||
customRedirectPortFunc: options.CustomRedirectPort,
|
||||
}
|
||||
var err error
|
||||
if runtime.GOOS == "android" {
|
||||
r.enableIPv4 = true
|
||||
r.iptablesPath = "/system/bin/iptables"
|
||||
userId := os.Getuid()
|
||||
if userId != 0 {
|
||||
r.androidSu = true
|
||||
for _, suPath := range []string{
|
||||
"su",
|
||||
"/system/bin/su",
|
||||
} {
|
||||
r.suPath, err = exec.LookPath(suPath)
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return nil, E.Extend(E.Cause(err, "root permission is required for auto redirect"), os.Getenv("PATH"))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if r.useNFTables {
|
||||
err = r.initializeNFTables()
|
||||
if err != nil && err != os.ErrInvalid {
|
||||
r.logger.Debug("device has no nftables support: ", err)
|
||||
}
|
||||
}
|
||||
if len(r.tunOptions.Inet4Address) > 0 {
|
||||
r.enableIPv4 = true
|
||||
if !r.useNFTables {
|
||||
r.iptablesPath, err = exec.LookPath("iptables")
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "iptables is required")
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(r.tunOptions.Inet6Address) > 0 {
|
||||
r.enableIPv6 = true
|
||||
if !r.useNFTables {
|
||||
r.ip6tablesPath, err = exec.LookPath("ip6tables")
|
||||
if err != nil {
|
||||
if !r.enableIPv4 {
|
||||
return nil, E.Cause(err, "ip6tables is required")
|
||||
} else {
|
||||
r.enableIPv6 = false
|
||||
r.logger.Error("device has no ip6tables nat support: ", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func (r *autoRedirect) Start() error {
|
||||
if r.customRedirectPortFunc != nil {
|
||||
r.customRedirectPort = r.customRedirectPortFunc()
|
||||
}
|
||||
if r.customRedirectPort == 0 {
|
||||
var listenAddr netip.Addr
|
||||
if runtime.GOOS == "android" {
|
||||
listenAddr = netip.AddrFrom4([4]byte{127, 0, 0, 1})
|
||||
} else if r.enableIPv6 {
|
||||
listenAddr = netip.IPv6Unspecified()
|
||||
} else {
|
||||
listenAddr = netip.IPv4Unspecified()
|
||||
}
|
||||
server := newRedirectServer(r.ctx, r.handler, r.logger, listenAddr)
|
||||
err := server.Start()
|
||||
if err != nil {
|
||||
return E.Cause(err, "start redirect server")
|
||||
}
|
||||
r.redirectServer = server
|
||||
}
|
||||
return r.setupTables()
|
||||
}
|
||||
|
||||
func (r *autoRedirect) Close() error {
|
||||
r.cleanupTables()
|
||||
return common.Close(
|
||||
common.PtrOrNil(r.redirectServer),
|
||||
)
|
||||
}
|
||||
|
||||
func (r *autoRedirect) initializeNFTables() error {
|
||||
nft, err := nftables.New()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer nft.CloseLasting()
|
||||
_, err = nft.ListTablesOfFamily(unix.AF_INET)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
r.useNFTables = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *autoRedirect) redirectPort() uint16 {
|
||||
if r.customRedirectPort > 0 {
|
||||
return uint16(r.customRedirectPort)
|
||||
}
|
||||
return M.AddrPortFromNet(r.redirectServer.listener.Addr()).Port()
|
||||
}
|
||||
|
||||
func (r *autoRedirect) setupTables() error {
|
||||
var setupTables func(int) error
|
||||
if r.useNFTables {
|
||||
setupTables = r.setupNFTables
|
||||
} else {
|
||||
setupTables = r.setupIPTables
|
||||
}
|
||||
if r.enableIPv4 {
|
||||
err := setupTables(unix.AF_INET)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if r.enableIPv6 {
|
||||
err := setupTables(unix.AF_INET6)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *autoRedirect) cleanupTables() {
|
||||
var cleanupTables func(int)
|
||||
if r.useNFTables {
|
||||
cleanupTables = r.cleanupNFTables
|
||||
} else {
|
||||
cleanupTables = r.cleanupIPTables
|
||||
}
|
||||
if r.enableIPv4 {
|
||||
cleanupTables(unix.AF_INET)
|
||||
}
|
||||
if r.enableIPv6 {
|
||||
cleanupTables(unix.AF_INET6)
|
||||
}
|
||||
}
|
||||
237
redirect_nftables.go
Normal file
237
redirect_nftables.go
Normal file
@@ -0,0 +1,237 @@
|
||||
//go:build linux
|
||||
|
||||
package tun
|
||||
|
||||
import (
|
||||
"net/netip"
|
||||
|
||||
"github.com/sagernet/nftables"
|
||||
"github.com/sagernet/nftables/binaryutil"
|
||||
"github.com/sagernet/nftables/expr"
|
||||
"github.com/sagernet/sing/common"
|
||||
F "github.com/sagernet/sing/common/format"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
const (
|
||||
nftablesChainOutput = "output"
|
||||
nftablesChainForward = "forward"
|
||||
nftablesChainPreRouting = "prerouting"
|
||||
)
|
||||
|
||||
func nftablesFamily(family int) nftables.TableFamily {
|
||||
switch family {
|
||||
case unix.AF_INET:
|
||||
return nftables.TableFamilyIPv4
|
||||
case unix.AF_INET6:
|
||||
return nftables.TableFamilyIPv6
|
||||
default:
|
||||
panic(F.ToString("unknown family ", family))
|
||||
}
|
||||
}
|
||||
|
||||
func (r *autoRedirect) setupNFTables(family int) error {
|
||||
nft, err := nftables.New()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer nft.CloseLasting()
|
||||
|
||||
redirectPort := r.redirectPort()
|
||||
|
||||
table := nft.AddTable(&nftables.Table{
|
||||
Name: r.tableName,
|
||||
Family: nftablesFamily(family),
|
||||
})
|
||||
|
||||
chainOutput := nft.AddChain(&nftables.Chain{
|
||||
Name: nftablesChainOutput,
|
||||
Table: table,
|
||||
Hooknum: nftables.ChainHookOutput,
|
||||
Priority: nftables.ChainPriorityMangle,
|
||||
Type: nftables.ChainTypeNAT,
|
||||
})
|
||||
nft.AddRule(&nftables.Rule{
|
||||
Table: table,
|
||||
Chain: chainOutput,
|
||||
Exprs: nftablesRuleIfName(expr.MetaKeyOIFNAME, r.tunOptions.Name, nftablesRuleRedirectToPorts(redirectPort)...),
|
||||
})
|
||||
|
||||
chainForward := nft.AddChain(&nftables.Chain{
|
||||
Name: nftablesChainForward,
|
||||
Table: table,
|
||||
Hooknum: nftables.ChainHookForward,
|
||||
Priority: nftables.ChainPriorityMangle,
|
||||
})
|
||||
nft.AddRule(&nftables.Rule{
|
||||
Table: table,
|
||||
Chain: chainForward,
|
||||
Exprs: nftablesRuleIfName(expr.MetaKeyIIFNAME, r.tunOptions.Name, &expr.Verdict{
|
||||
Kind: expr.VerdictAccept,
|
||||
}),
|
||||
})
|
||||
nft.AddRule(&nftables.Rule{
|
||||
Table: table,
|
||||
Chain: chainForward,
|
||||
Exprs: nftablesRuleIfName(expr.MetaKeyOIFNAME, r.tunOptions.Name, &expr.Verdict{
|
||||
Kind: expr.VerdictAccept,
|
||||
}),
|
||||
})
|
||||
|
||||
chainPreRouting := nft.AddChain(&nftables.Chain{
|
||||
Name: nftablesChainPreRouting,
|
||||
Table: table,
|
||||
Hooknum: nftables.ChainHookPrerouting,
|
||||
Priority: nftables.ChainPriorityMangle,
|
||||
Type: nftables.ChainTypeNAT,
|
||||
})
|
||||
nft.AddRule(&nftables.Rule{
|
||||
Table: table,
|
||||
Chain: chainPreRouting,
|
||||
Exprs: nftablesRuleIfName(expr.MetaKeyIIFNAME, r.tunOptions.Name, &expr.Verdict{
|
||||
Kind: expr.VerdictReturn,
|
||||
}),
|
||||
})
|
||||
var (
|
||||
routeAddress []netip.Prefix
|
||||
routeExcludeAddress []netip.Prefix
|
||||
)
|
||||
if table.Family == nftables.TableFamilyIPv4 {
|
||||
routeAddress = r.tunOptions.Inet4RouteAddress
|
||||
routeExcludeAddress = r.tunOptions.Inet4RouteExcludeAddress
|
||||
} else {
|
||||
routeAddress = r.tunOptions.Inet6RouteAddress
|
||||
routeExcludeAddress = r.tunOptions.Inet6RouteExcludeAddress
|
||||
}
|
||||
for _, address := range routeExcludeAddress {
|
||||
nft.AddRule(&nftables.Rule{
|
||||
Table: table,
|
||||
Chain: chainPreRouting,
|
||||
Exprs: nftablesRuleDestinationAddress(address, &expr.Verdict{
|
||||
Kind: expr.VerdictReturn,
|
||||
}),
|
||||
})
|
||||
}
|
||||
for _, name := range r.tunOptions.ExcludeInterface {
|
||||
nft.AddRule(&nftables.Rule{
|
||||
Table: table,
|
||||
Chain: chainPreRouting,
|
||||
Exprs: nftablesRuleIfName(expr.MetaKeyIIFNAME, name, &expr.Verdict{
|
||||
Kind: expr.VerdictReturn,
|
||||
}),
|
||||
})
|
||||
}
|
||||
for _, uidRange := range r.tunOptions.ExcludeUID {
|
||||
nft.AddRule(&nftables.Rule{
|
||||
Table: table,
|
||||
Chain: chainPreRouting,
|
||||
Exprs: nftablesRuleMetaUInt32Range(expr.MetaKeySKUID, uidRange, &expr.Verdict{
|
||||
Kind: expr.VerdictReturn,
|
||||
}),
|
||||
})
|
||||
}
|
||||
|
||||
var routeExprs []expr.Any
|
||||
if len(routeAddress) > 0 {
|
||||
for _, address := range routeAddress {
|
||||
routeExprs = append(routeExprs, nftablesRuleDestinationAddress(address)...)
|
||||
}
|
||||
}
|
||||
|
||||
if !r.tunOptions.EXP_DisableDNSHijack {
|
||||
dnsServer := common.Find(r.tunOptions.DNSServers, func(it netip.Addr) bool {
|
||||
return it.Is4() == (family == unix.AF_INET)
|
||||
})
|
||||
if !dnsServer.IsValid() {
|
||||
if family == unix.AF_INET {
|
||||
dnsServer = r.tunOptions.Inet4Address[0].Addr().Next()
|
||||
} else {
|
||||
dnsServer = r.tunOptions.Inet6Address[0].Addr().Next()
|
||||
}
|
||||
}
|
||||
if len(r.tunOptions.IncludeInterface) > 0 || len(r.tunOptions.IncludeUID) > 0 {
|
||||
for _, name := range r.tunOptions.IncludeInterface {
|
||||
nft.AddRule(&nftables.Rule{
|
||||
Table: table,
|
||||
Chain: chainPreRouting,
|
||||
Exprs: nftablesRuleIfName(expr.MetaKeyIIFNAME, name, append(routeExprs, nftablesRuleHijackDNS(table.Family, dnsServer)...)...),
|
||||
})
|
||||
}
|
||||
for _, uidRange := range r.tunOptions.IncludeUID {
|
||||
nft.AddRule(&nftables.Rule{
|
||||
Table: table,
|
||||
Chain: chainPreRouting,
|
||||
Exprs: nftablesRuleMetaUInt32Range(expr.MetaKeySKUID, uidRange, append(routeExprs, nftablesRuleHijackDNS(table.Family, dnsServer)...)...),
|
||||
})
|
||||
}
|
||||
} else {
|
||||
nft.AddRule(&nftables.Rule{
|
||||
Table: table,
|
||||
Chain: chainPreRouting,
|
||||
Exprs: append(routeExprs, nftablesRuleHijackDNS(table.Family, dnsServer)...),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
nft.AddRule(&nftables.Rule{
|
||||
Table: table,
|
||||
Chain: chainPreRouting,
|
||||
Exprs: []expr.Any{
|
||||
&expr.Fib{
|
||||
Register: 1,
|
||||
FlagDADDR: true,
|
||||
ResultADDRTYPE: true,
|
||||
},
|
||||
&expr.Cmp{
|
||||
Op: expr.CmpOpEq,
|
||||
Register: 1,
|
||||
Data: binaryutil.NativeEndian.PutUint32(unix.RTN_LOCAL),
|
||||
},
|
||||
&expr.Verdict{
|
||||
Kind: expr.VerdictReturn,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
if len(r.tunOptions.IncludeInterface) > 0 || len(r.tunOptions.IncludeUID) > 0 {
|
||||
for _, name := range r.tunOptions.IncludeInterface {
|
||||
nft.AddRule(&nftables.Rule{
|
||||
Table: table,
|
||||
Chain: chainPreRouting,
|
||||
Exprs: nftablesRuleIfName(expr.MetaKeyIIFNAME, name, append(routeExprs, nftablesRuleRedirectToPorts(redirectPort)...)...),
|
||||
})
|
||||
}
|
||||
for _, uidRange := range r.tunOptions.IncludeUID {
|
||||
nft.AddRule(&nftables.Rule{
|
||||
Table: table,
|
||||
Chain: chainPreRouting,
|
||||
Exprs: nftablesRuleMetaUInt32Range(expr.MetaKeySKUID, uidRange, append(routeExprs, nftablesRuleRedirectToPorts(redirectPort)...)...),
|
||||
})
|
||||
}
|
||||
} else {
|
||||
nft.AddRule(&nftables.Rule{
|
||||
Table: table,
|
||||
Chain: chainPreRouting,
|
||||
Exprs: append(routeExprs, nftablesRuleRedirectToPorts(redirectPort)...),
|
||||
})
|
||||
}
|
||||
return nft.Flush()
|
||||
}
|
||||
|
||||
func (r *autoRedirect) cleanupNFTables(family int) {
|
||||
conn, err := nftables.New()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
conn.FlushTable(&nftables.Table{
|
||||
Name: r.tableName,
|
||||
Family: nftablesFamily(family),
|
||||
})
|
||||
conn.DelTable(&nftables.Table{
|
||||
Name: r.tableName,
|
||||
Family: nftablesFamily(family),
|
||||
})
|
||||
_ = conn.Flush()
|
||||
_ = conn.CloseLasting()
|
||||
}
|
||||
153
redirect_nftables_expr.go
Normal file
153
redirect_nftables_expr.go
Normal file
@@ -0,0 +1,153 @@
|
||||
//go:build linux
|
||||
|
||||
package tun
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/netip"
|
||||
|
||||
"github.com/sagernet/nftables"
|
||||
"github.com/sagernet/nftables/binaryutil"
|
||||
"github.com/sagernet/nftables/expr"
|
||||
"github.com/sagernet/sing/common/ranges"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func nftablesIfname(n string) []byte {
|
||||
b := make([]byte, 16)
|
||||
copy(b, n+"\x00")
|
||||
return b
|
||||
}
|
||||
|
||||
func nftablesRuleIfName(key expr.MetaKey, value string, exprs ...expr.Any) []expr.Any {
|
||||
newExprs := []expr.Any{
|
||||
&expr.Meta{Key: key, Register: 1},
|
||||
&expr.Cmp{
|
||||
Op: expr.CmpOpEq,
|
||||
Register: 1,
|
||||
Data: nftablesIfname(value),
|
||||
},
|
||||
}
|
||||
newExprs = append(newExprs, exprs...)
|
||||
return newExprs
|
||||
}
|
||||
|
||||
func nftablesRuleMetaUInt32Range(key expr.MetaKey, uidRange ranges.Range[uint32], exprs ...expr.Any) []expr.Any {
|
||||
newExprs := []expr.Any{
|
||||
&expr.Meta{Key: key, Register: 1},
|
||||
&expr.Range{
|
||||
Op: expr.CmpOpEq,
|
||||
Register: 1,
|
||||
FromData: binaryutil.BigEndian.PutUint32(uidRange.Start),
|
||||
ToData: binaryutil.BigEndian.PutUint32(uidRange.End),
|
||||
},
|
||||
}
|
||||
newExprs = append(newExprs, exprs...)
|
||||
return newExprs
|
||||
}
|
||||
|
||||
func nftablesRuleDestinationAddress(address netip.Prefix, exprs ...expr.Any) []expr.Any {
|
||||
var newExprs []expr.Any
|
||||
if address.Addr().Is4() {
|
||||
newExprs = append(newExprs, &expr.Payload{
|
||||
OperationType: expr.PayloadLoad,
|
||||
DestRegister: 1,
|
||||
SourceRegister: 0,
|
||||
Base: expr.PayloadBaseNetworkHeader,
|
||||
Offset: 16,
|
||||
Len: 4,
|
||||
}, &expr.Bitwise{
|
||||
SourceRegister: 1,
|
||||
DestRegister: 1,
|
||||
Len: 4,
|
||||
Xor: make([]byte, 4),
|
||||
Mask: net.CIDRMask(address.Bits(), 32),
|
||||
})
|
||||
} else {
|
||||
newExprs = append(newExprs, &expr.Payload{
|
||||
OperationType: expr.PayloadLoad,
|
||||
DestRegister: 1,
|
||||
SourceRegister: 0,
|
||||
Base: expr.PayloadBaseNetworkHeader,
|
||||
Offset: 24,
|
||||
Len: 16,
|
||||
}, &expr.Bitwise{
|
||||
SourceRegister: 1,
|
||||
DestRegister: 1,
|
||||
Len: 16,
|
||||
Xor: make([]byte, 16),
|
||||
Mask: net.CIDRMask(address.Bits(), 128),
|
||||
})
|
||||
}
|
||||
newExprs = append(newExprs, &expr.Cmp{
|
||||
Op: expr.CmpOpEq,
|
||||
Register: 1,
|
||||
Data: address.Masked().Addr().AsSlice(),
|
||||
})
|
||||
newExprs = append(newExprs, exprs...)
|
||||
return newExprs
|
||||
}
|
||||
|
||||
func nftablesRuleHijackDNS(family nftables.TableFamily, dnsServerAddress netip.Addr) []expr.Any {
|
||||
return []expr.Any{
|
||||
&expr.Meta{
|
||||
Key: expr.MetaKeyL4PROTO,
|
||||
Register: 1,
|
||||
},
|
||||
&expr.Cmp{
|
||||
Op: expr.CmpOpEq,
|
||||
Register: 1,
|
||||
Data: []byte{unix.IPPROTO_UDP},
|
||||
},
|
||||
&expr.Payload{
|
||||
OperationType: expr.PayloadLoad,
|
||||
DestRegister: 1,
|
||||
SourceRegister: 0,
|
||||
Base: expr.PayloadBaseTransportHeader,
|
||||
Offset: 2,
|
||||
Len: 2,
|
||||
}, &expr.Cmp{
|
||||
Op: expr.CmpOpEq,
|
||||
Register: 1,
|
||||
Data: binaryutil.BigEndian.PutUint16(53),
|
||||
}, &expr.Immediate{
|
||||
Register: 1,
|
||||
Data: dnsServerAddress.AsSlice(),
|
||||
}, &expr.NAT{
|
||||
Type: expr.NATTypeDestNAT,
|
||||
Family: uint32(family),
|
||||
RegAddrMin: 1,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
NF_NAT_RANGE_MAP_IPS = 1 << iota
|
||||
NF_NAT_RANGE_PROTO_SPECIFIED
|
||||
NF_NAT_RANGE_PROTO_RANDOM
|
||||
NF_NAT_RANGE_PERSISTENT
|
||||
NF_NAT_RANGE_PROTO_RANDOM_FULLY
|
||||
NF_NAT_RANGE_PROTO_OFFSET
|
||||
)
|
||||
|
||||
func nftablesRuleRedirectToPorts(redirectPort uint16) []expr.Any {
|
||||
return []expr.Any{
|
||||
&expr.Meta{
|
||||
Key: expr.MetaKeyL4PROTO,
|
||||
Register: 1,
|
||||
},
|
||||
&expr.Cmp{
|
||||
Op: expr.CmpOpEq,
|
||||
Register: 1,
|
||||
Data: []byte{unix.IPPROTO_TCP},
|
||||
},
|
||||
&expr.Immediate{
|
||||
Register: 1,
|
||||
Data: binaryutil.BigEndian.PutUint16(redirectPort),
|
||||
}, &expr.Redir{
|
||||
RegisterProtoMin: 1,
|
||||
Flags: NF_NAT_RANGE_PROTO_SPECIFIED,
|
||||
},
|
||||
}
|
||||
}
|
||||
88
redirect_server.go
Normal file
88
redirect_server.go
Normal file
@@ -0,0 +1,88 @@
|
||||
//go:build linux
|
||||
|
||||
package tun
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net"
|
||||
"net/netip"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing/common/atomic"
|
||||
"github.com/sagernet/sing/common/control"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/sagernet/sing/common/logger"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
)
|
||||
|
||||
const ProtocolRedirect = "redirect"
|
||||
|
||||
type redirectServer struct {
|
||||
ctx context.Context
|
||||
handler Handler
|
||||
logger logger.Logger
|
||||
listenAddr netip.Addr
|
||||
listener *net.TCPListener
|
||||
inShutdown atomic.Bool
|
||||
}
|
||||
|
||||
func newRedirectServer(ctx context.Context, handler Handler, logger logger.Logger, listenAddr netip.Addr) *redirectServer {
|
||||
return &redirectServer{
|
||||
ctx: ctx,
|
||||
handler: handler,
|
||||
logger: logger,
|
||||
listenAddr: listenAddr,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *redirectServer) Start() error {
|
||||
var listenConfig net.ListenConfig
|
||||
// listenConfig.KeepAlive = C.TCPKeepAliveInitial
|
||||
listenConfig.KeepAlive = 10 * time.Minute
|
||||
listener, err := listenConfig.Listen(s.ctx, M.NetworkFromNetAddr("tcp", s.listenAddr), M.SocksaddrFrom(s.listenAddr, 0).String())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.listener = listener.(*net.TCPListener)
|
||||
go s.loopIn()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *redirectServer) Close() error {
|
||||
s.inShutdown.Store(true)
|
||||
return s.listener.Close()
|
||||
}
|
||||
|
||||
func (s *redirectServer) loopIn() {
|
||||
for {
|
||||
conn, err := s.listener.AcceptTCP()
|
||||
if err != nil {
|
||||
var netError net.Error
|
||||
//goland:noinspection GoDeprecation
|
||||
//nolint:staticcheck
|
||||
if errors.As(err, &netError) && netError.Temporary() {
|
||||
s.logger.Error(err)
|
||||
continue
|
||||
}
|
||||
if s.inShutdown.Load() && E.IsClosed(err) {
|
||||
return
|
||||
}
|
||||
s.listener.Close()
|
||||
s.logger.Error("serve error: ", err)
|
||||
continue
|
||||
}
|
||||
var metadata M.Metadata
|
||||
metadata.Protocol = ProtocolRedirect
|
||||
metadata.Source = M.SocksaddrFromNet(conn.RemoteAddr())
|
||||
destination, err := control.GetOriginalDestination(conn)
|
||||
if err != nil {
|
||||
_ = conn.SetLinger(0)
|
||||
_ = conn.Close()
|
||||
s.logger.Error("process connection from ", metadata.Source, ": invalid connection: ", err)
|
||||
continue
|
||||
}
|
||||
metadata.Destination = M.SocksaddrFromNetIP(destination)
|
||||
go s.handler.NewConnection(s.ctx, conn, metadata)
|
||||
}
|
||||
}
|
||||
11
redirect_stub.go
Normal file
11
redirect_stub.go
Normal file
@@ -0,0 +1,11 @@
|
||||
//go:build !linux
|
||||
|
||||
package tun
|
||||
|
||||
import (
|
||||
"os"
|
||||
)
|
||||
|
||||
func NewAutoRedirect(options AutoRedirectOptions) (AutoRedirect, error) {
|
||||
return nil, os.ErrInvalid
|
||||
}
|
||||
@@ -138,7 +138,6 @@ func (w *UDPBackWriter) WritePacket(packetBuffer *buf.Buffer, destination M.Sock
|
||||
TTL: route.DefaultTTL(),
|
||||
TOS: 0,
|
||||
}, packet)
|
||||
|
||||
if err != nil {
|
||||
route.Stats().UDP.PacketSendErrors.Increment()
|
||||
return wrapStackError(err)
|
||||
|
||||
5
tun.go
5
tun.go
@@ -48,6 +48,8 @@ type Options struct {
|
||||
MTU uint32
|
||||
GSO bool
|
||||
AutoRoute bool
|
||||
DNSServers []netip.Addr
|
||||
IPRoute2RuleIndex int
|
||||
StrictRoute bool
|
||||
Inet4RouteAddress []netip.Prefix
|
||||
Inet6RouteAddress []netip.Prefix
|
||||
@@ -67,6 +69,9 @@ type Options struct {
|
||||
|
||||
// No work for TCP, do not use.
|
||||
_TXChecksumOffload bool
|
||||
|
||||
// For library usages.
|
||||
EXP_DisableDNSHijack bool
|
||||
}
|
||||
|
||||
func CalculateInterfaceName(name string) (tunName string) {
|
||||
|
||||
153
tun_linux.go
153
tun_linux.go
@@ -359,11 +359,6 @@ func (t *NativeTun) routes(tunLink netlink.Link) ([]netlink.Route, error) {
|
||||
}), nil
|
||||
}
|
||||
|
||||
const (
|
||||
ruleStart = 9000
|
||||
ruleEnd = ruleStart + 10
|
||||
)
|
||||
|
||||
func (t *NativeTun) nextIndex6() int {
|
||||
ruleList, err := netlink.RuleList(netlink.FAMILY_V6)
|
||||
if err != nil {
|
||||
@@ -411,9 +406,14 @@ func (t *NativeTun) rules() []*netlink.Rule {
|
||||
var it *netlink.Rule
|
||||
|
||||
excludeRanges := t.options.ExcludedRanges()
|
||||
|
||||
ruleStart := t.options.IPRoute2RuleIndex
|
||||
if ruleStart == 0 {
|
||||
ruleStart = 9000
|
||||
}
|
||||
priority := ruleStart
|
||||
priority6 := priority
|
||||
nopPriority := ruleEnd
|
||||
nopPriority := ruleStart + 10
|
||||
|
||||
for _, excludeRange := range excludeRanges {
|
||||
if p4 {
|
||||
@@ -516,7 +516,7 @@ func (t *NativeTun) rules() []*netlink.Rule {
|
||||
|
||||
if runtime.GOOS == "android" && t.options.InterfaceMonitor.AndroidVPNEnabled() {
|
||||
const protectedFromVPN = 0x20000
|
||||
if p4 || t.options.StrictRoute {
|
||||
if p4 {
|
||||
it = netlink.NewRule()
|
||||
if t.options.InterfaceMonitor.OverrideAndroidVPN() {
|
||||
it.Mark = protectedFromVPN
|
||||
@@ -528,7 +528,7 @@ func (t *NativeTun) rules() []*netlink.Rule {
|
||||
rules = append(rules, it)
|
||||
priority++
|
||||
}
|
||||
if p6 || t.options.StrictRoute {
|
||||
if p6 {
|
||||
it = netlink.NewRule()
|
||||
if t.options.InterfaceMonitor.OverrideAndroidVPN() {
|
||||
it.Mark = protectedFromVPN
|
||||
@@ -627,74 +627,78 @@ func (t *NativeTun) rules() []*netlink.Rule {
|
||||
priority6++
|
||||
}
|
||||
}
|
||||
|
||||
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 = 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.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 = netip.PrefixFrom(netip.IPv4Unspecified(), 32)
|
||||
it.Src = address.Masked()
|
||||
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 {
|
||||
for _, address := range t.options.Inet6Address {
|
||||
it = netlink.NewRule()
|
||||
it.Priority = priority6
|
||||
it.IifName = "lo"
|
||||
it.Src = address.Masked()
|
||||
it.Table = t.options.TableIndex
|
||||
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 = netip.PrefixFrom(netip.IPv6Unspecified(), 1)
|
||||
it.Goto = nopPriority
|
||||
it.Src = address.Masked()
|
||||
it.Table = t.options.TableIndex
|
||||
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++
|
||||
}
|
||||
priority6++
|
||||
|
||||
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++
|
||||
|
||||
it = netlink.NewRule()
|
||||
it.Priority = priority6
|
||||
@@ -784,6 +788,11 @@ func (t *NativeTun) unsetRules() error {
|
||||
return err
|
||||
}
|
||||
for _, rule := range ruleList {
|
||||
ruleStart := t.options.IPRoute2RuleIndex
|
||||
if ruleStart == 0 {
|
||||
ruleStart = 9000
|
||||
}
|
||||
ruleEnd := ruleStart + 10
|
||||
if rule.Priority >= ruleStart && rule.Priority <= ruleEnd {
|
||||
ruleToDel := netlink.NewRule()
|
||||
ruleToDel.Family = rule.Family
|
||||
@@ -816,20 +825,28 @@ func (t *NativeTun) routeUpdate(event int) {
|
||||
}
|
||||
|
||||
func (t *NativeTun) setSearchDomainForSystemdResolved() {
|
||||
if t.options.EXP_DisableDNSHijack {
|
||||
return
|
||||
}
|
||||
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())
|
||||
dnsServer := t.options.DNSServers
|
||||
if len(dnsServer) == 0 {
|
||||
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())
|
||||
}
|
||||
}
|
||||
if len(t.options.Inet6Address) > 0 {
|
||||
dnsServer = append(dnsServer, t.options.Inet6Address[0].Addr().Next())
|
||||
}
|
||||
go shell.Exec(ctlPath, "domain", t.options.Name, "~.").Run()
|
||||
if t.options.AutoRoute {
|
||||
go shell.Exec(ctlPath, "default-route", t.options.Name, "true").Run()
|
||||
go shell.Exec(ctlPath, append([]string{"dns", t.options.Name}, common.Map(dnsServer, netip.Addr.String)...)...).Run()
|
||||
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()
|
||||
}()
|
||||
}
|
||||
|
||||
16
tun_rules.go
16
tun_rules.go
@@ -109,9 +109,11 @@ func (o *Options) BuildAutoRouteRanges(underNetworkExtension bool) ([]netip.Pref
|
||||
var inet4Ranges []netip.Prefix
|
||||
if len(o.Inet4RouteAddress) > 0 {
|
||||
inet4Ranges = o.Inet4RouteAddress
|
||||
for _, address := range o.Inet4Address {
|
||||
if address.Bits() < 32 {
|
||||
inet4Ranges = append(inet4Ranges, netipx.RangeOfPrefix(address).Prefixes()...)
|
||||
if runtime.GOOS == "darwin" {
|
||||
for _, address := range o.Inet4Address {
|
||||
if address.Bits() < 32 {
|
||||
inet4Ranges = append(inet4Ranges, address.Masked())
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if autoRouteUseSubRanges && !underNetworkExtension {
|
||||
@@ -149,9 +151,11 @@ func (o *Options) BuildAutoRouteRanges(underNetworkExtension bool) ([]netip.Pref
|
||||
var inet6Ranges []netip.Prefix
|
||||
if len(o.Inet6RouteAddress) > 0 {
|
||||
inet6Ranges = o.Inet6RouteAddress
|
||||
for _, address := range o.Inet6Address {
|
||||
if address.Bits() < 32 {
|
||||
inet6Ranges = append(inet6Ranges, netipx.RangeOfPrefix(address).Prefixes()...)
|
||||
if runtime.GOOS == "darwin" {
|
||||
for _, address := range o.Inet6Address {
|
||||
if address.Bits() < 32 {
|
||||
inet6Ranges = append(inet6Ranges, address.Masked())
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if autoRouteUseSubRanges && !underNetworkExtension {
|
||||
|
||||
@@ -72,9 +72,15 @@ func (t *NativeTun) configure() error {
|
||||
if err != nil {
|
||||
return E.Cause(err, "set ipv4 address")
|
||||
}
|
||||
err = luid.SetDNS(winipcfg.AddressFamily(windows.AF_INET), []netip.Addr{t.options.Inet4Address[0].Addr().Next()}, nil)
|
||||
if err != nil {
|
||||
return E.Cause(err, "set ipv4 dns")
|
||||
if !t.options.EXP_DisableDNSHijack {
|
||||
dnsServers := common.Filter(t.options.DNSServers, netip.Addr.Is4)
|
||||
if len(dnsServers) == 0 {
|
||||
dnsServers = []netip.Addr{t.options.Inet4Address[0].Addr().Next()}
|
||||
}
|
||||
err = luid.SetDNS(winipcfg.AddressFamily(windows.AF_INET), dnsServers, nil)
|
||||
if err != nil {
|
||||
return E.Cause(err, "set ipv4 dns")
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(t.options.Inet6Address) > 0 {
|
||||
@@ -82,9 +88,15 @@ func (t *NativeTun) configure() error {
|
||||
if err != nil {
|
||||
return E.Cause(err, "set ipv6 address")
|
||||
}
|
||||
err = luid.SetDNS(winipcfg.AddressFamily(windows.AF_INET6), []netip.Addr{t.options.Inet6Address[0].Addr().Next()}, nil)
|
||||
if err != nil {
|
||||
return E.Cause(err, "set ipv6 dns")
|
||||
if !t.options.EXP_DisableDNSHijack {
|
||||
dnsServers := common.Filter(t.options.DNSServers, netip.Addr.Is6)
|
||||
if len(dnsServers) == 0 {
|
||||
dnsServers = []netip.Addr{t.options.Inet6Address[0].Addr().Next()}
|
||||
}
|
||||
err = luid.SetDNS(winipcfg.AddressFamily(windows.AF_INET6), dnsServers, nil)
|
||||
if err != nil {
|
||||
return E.Cause(err, "set ipv6 dns")
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(t.options.Inet4Address) > 0 || len(t.options.Inet6Address) > 0 {
|
||||
@@ -284,42 +296,40 @@ func (t *NativeTun) configure() error {
|
||||
}
|
||||
}
|
||||
|
||||
blockDNSCondition := make([]winsys.FWPM_FILTER_CONDITION0, 2)
|
||||
blockDNSCondition[0].FieldKey = winsys.FWPM_CONDITION_IP_PROTOCOL
|
||||
blockDNSCondition[0].MatchType = winsys.FWP_MATCH_EQUAL
|
||||
blockDNSCondition[0].ConditionValue.Type = winsys.FWP_UINT8
|
||||
blockDNSCondition[0].ConditionValue.Value = uintptr(uint8(winsys.IPPROTO_UDP))
|
||||
blockDNSCondition[1].FieldKey = winsys.FWPM_CONDITION_IP_REMOTE_PORT
|
||||
blockDNSCondition[1].MatchType = winsys.FWP_MATCH_EQUAL
|
||||
blockDNSCondition[1].ConditionValue.Type = winsys.FWP_UINT16
|
||||
blockDNSCondition[1].ConditionValue.Value = uintptr(uint16(53))
|
||||
if !t.options.EXP_DisableDNSHijack {
|
||||
blockDNSCondition := make([]winsys.FWPM_FILTER_CONDITION0, 1)
|
||||
blockDNSCondition[0].FieldKey = winsys.FWPM_CONDITION_IP_REMOTE_PORT
|
||||
blockDNSCondition[0].MatchType = winsys.FWP_MATCH_EQUAL
|
||||
blockDNSCondition[0].ConditionValue.Type = winsys.FWP_UINT16
|
||||
blockDNSCondition[0].ConditionValue.Value = uintptr(uint16(53))
|
||||
|
||||
blockDNSFilter4 := winsys.FWPM_FILTER0{}
|
||||
blockDNSFilter4.FilterCondition = &blockDNSCondition[0]
|
||||
blockDNSFilter4.NumFilterConditions = 2
|
||||
blockDNSFilter4.DisplayData = winsys.CreateDisplayData(TunnelType, "block ipv4 dns")
|
||||
blockDNSFilter4.SubLayerKey = subLayerKey
|
||||
blockDNSFilter4.LayerKey = winsys.FWPM_LAYER_ALE_AUTH_CONNECT_V4
|
||||
blockDNSFilter4.Action.Type = winsys.FWP_ACTION_BLOCK
|
||||
blockDNSFilter4.Weight.Type = winsys.FWP_UINT8
|
||||
blockDNSFilter4.Weight.Value = uintptr(10)
|
||||
err = winsys.FwpmFilterAdd0(engine, &blockDNSFilter4, 0, &filterId)
|
||||
if err != nil {
|
||||
return os.NewSyscallError("FwpmFilterAdd0", err)
|
||||
}
|
||||
blockDNSFilter4 := winsys.FWPM_FILTER0{}
|
||||
blockDNSFilter4.FilterCondition = &blockDNSCondition[0]
|
||||
blockDNSFilter4.NumFilterConditions = 2
|
||||
blockDNSFilter4.DisplayData = winsys.CreateDisplayData(TunnelType, "block ipv4 dns")
|
||||
blockDNSFilter4.SubLayerKey = subLayerKey
|
||||
blockDNSFilter4.LayerKey = winsys.FWPM_LAYER_ALE_AUTH_CONNECT_V4
|
||||
blockDNSFilter4.Action.Type = winsys.FWP_ACTION_BLOCK
|
||||
blockDNSFilter4.Weight.Type = winsys.FWP_UINT8
|
||||
blockDNSFilter4.Weight.Value = uintptr(10)
|
||||
err = winsys.FwpmFilterAdd0(engine, &blockDNSFilter4, 0, &filterId)
|
||||
if err != nil {
|
||||
return os.NewSyscallError("FwpmFilterAdd0", err)
|
||||
}
|
||||
|
||||
blockDNSFilter6 := winsys.FWPM_FILTER0{}
|
||||
blockDNSFilter6.FilterCondition = &blockDNSCondition[0]
|
||||
blockDNSFilter6.NumFilterConditions = 2
|
||||
blockDNSFilter6.DisplayData = winsys.CreateDisplayData(TunnelType, "block ipv6 dns")
|
||||
blockDNSFilter6.SubLayerKey = subLayerKey
|
||||
blockDNSFilter6.LayerKey = winsys.FWPM_LAYER_ALE_AUTH_CONNECT_V6
|
||||
blockDNSFilter6.Action.Type = winsys.FWP_ACTION_BLOCK
|
||||
blockDNSFilter6.Weight.Type = winsys.FWP_UINT8
|
||||
blockDNSFilter6.Weight.Value = uintptr(10)
|
||||
err = winsys.FwpmFilterAdd0(engine, &blockDNSFilter6, 0, &filterId)
|
||||
if err != nil {
|
||||
return os.NewSyscallError("FwpmFilterAdd0", err)
|
||||
blockDNSFilter6 := winsys.FWPM_FILTER0{}
|
||||
blockDNSFilter6.FilterCondition = &blockDNSCondition[0]
|
||||
blockDNSFilter6.NumFilterConditions = 2
|
||||
blockDNSFilter6.DisplayData = winsys.CreateDisplayData(TunnelType, "block ipv6 dns")
|
||||
blockDNSFilter6.SubLayerKey = subLayerKey
|
||||
blockDNSFilter6.LayerKey = winsys.FWPM_LAYER_ALE_AUTH_CONNECT_V6
|
||||
blockDNSFilter6.Action.Type = winsys.FWP_ACTION_BLOCK
|
||||
blockDNSFilter6.Weight.Type = winsys.FWP_UINT8
|
||||
blockDNSFilter6.Weight.Value = uintptr(10)
|
||||
err = winsys.FwpmFilterAdd0(engine, &blockDNSFilter6, 0, &filterId)
|
||||
if err != nil {
|
||||
return os.NewSyscallError("FwpmFilterAdd0", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user