Compare commits

...

6 Commits

Author SHA1 Message Date
世界
bea26198e7 Fix "Fix gLazyConn race" 2025-06-16 14:01:32 +08:00
世界
3df19f464e Fix gLazyConn race 2025-06-13 18:18:53 +08:00
世界
494b0ef858 redirect: Fix unreachable 2025-06-13 18:18:53 +08:00
世界
f13cd94aa0 redirect: Fix counter position 2025-04-28 11:06:02 +08:00
世界
51ac6b34f1 redirect: Fix handling of local pings 2025-04-12 12:07:56 +08:00
世界
31e29f93cc redirect: Remove iptables rules except basic output redirect for Android 2025-04-12 12:07:23 +08:00
5 changed files with 94 additions and 267 deletions

View File

@@ -3,12 +3,9 @@
package tun
import (
"net/netip"
"os/exec"
"runtime"
"strings"
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
F "github.com/sagernet/sing/common/format"
)
@@ -30,10 +27,7 @@ func (r *autoRedirect) setupIPTables() error {
}
func (r *autoRedirect) setupIPTablesForFamily(iptablesPath string) error {
tableNameInput := r.tableName + "-input"
tableNameForward := r.tableName + "-forward"
tableNameOutput := r.tableName + "-output"
tableNamePreRouteing := r.tableName + "-prerouting"
redirectPort := r.redirectPort()
// OUTPUT
err := r.runShell(iptablesPath, "-t nat -N", tableNameOutput)
@@ -50,184 +44,6 @@ func (r *autoRedirect) setupIPTablesForFamily(iptablesPath string) error {
if err != nil {
return err
}
if runtime.GOOS == "android" {
return nil
}
// INPUT
err = r.runShell(iptablesPath, "-N", tableNameInput)
if err != nil {
return err
}
err = r.runShell(iptablesPath, "-A", tableNameInput,
"-i", r.tunOptions.Name, "-j", "ACCEPT")
if err != nil {
return err
}
err = r.runShell(iptablesPath, "-A", tableNameInput,
"-o", r.tunOptions.Name, "-j", "ACCEPT")
if err != nil {
return err
}
err = r.runShell(iptablesPath, "-I INPUT -j", tableNameInput)
if err != nil {
return err
}
// 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 iptablesPath == r.iptablesPath {
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() == (iptablesPath == r.iptablesPath)
})
if !dnsServer.IsValid() {
if iptablesPath == r.iptablesPath {
if HasNextAddress(r.tunOptions.Inet4Address[0], 1) {
dnsServer = r.tunOptions.Inet4Address[0].Addr().Next()
}
} else {
if HasNextAddress(r.tunOptions.Inet6Address[0], 1) {
dnsServer = r.tunOptions.Inet6Address[0].Addr().Next()
}
}
}
if dnsServer.IsValid() {
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
}
@@ -241,29 +57,11 @@ func (r *autoRedirect) cleanupIPTables() {
}
func (r *autoRedirect) cleanupIPTablesForFamily(iptablesPath string) {
tableNameInput := r.tableName + "-input"
tableNameOutput := r.tableName + "-output"
tableNameForward := r.tableName + "-forward"
tableNamePreRouteing := r.tableName + "-prerouting"
_ = r.runShell(iptablesPath, "-t nat -D OUTPUT -j", tableNameOutput)
_ = r.runShell(iptablesPath, "-t nat -F", tableNameOutput)
_ = r.runShell(iptablesPath, "-t nat -X", tableNameOutput)
if runtime.GOOS == "android" {
return
}
_ = r.runShell(iptablesPath, "-D INPUT -j", tableNameInput)
_ = r.runShell(iptablesPath, "-F", tableNameInput)
_ = r.runShell(iptablesPath, "-X", tableNameInput)
_ = 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 {

View File

@@ -83,9 +83,8 @@ func (r *autoRedirect) Start() error {
} else {
if r.useNFTables {
err = r.initializeNFTables()
if err != nil && err != os.ErrInvalid {
r.useNFTables = false
r.logger.Debug("missing nftables support: ", err)
if err != nil {
return E.Cause(err, "missing nftables support")
}
}
if len(r.tunOptions.Inet4Address) > 0 {

View File

@@ -64,7 +64,7 @@ func (r *autoRedirect) setupNFTables() error {
r.nftablesCreateRedirect(nft, table, chainOutput)
chainOutputUDP := nft.AddChain(&nftables.Chain{
Name: "output_udp",
Name: "output_udp_icmp",
Table: table,
Hooknum: nftables.ChainHookOutput,
Priority: nftables.ChainPriorityMangle,

View File

@@ -439,6 +439,20 @@ func (r *autoRedirect) nftablesCreateExcludeRules(nft *nftables.Conn, table *nft
if r.tunOptions.AutoRedirectMarkMode &&
((chain.Hooknum == nftables.ChainHookOutput && chain.Type == nftables.ChainTypeRoute) ||
(chain.Hooknum == nftables.ChainHookPrerouting && chain.Type == nftables.ChainTypeFilter)) {
ipProto := &nftables.Set{
Table: table,
Anonymous: true,
Constant: true,
KeyType: nftables.TypeInetProto,
}
err := nft.AddSet(ipProto, []nftables.SetElement{
{Key: []byte{unix.IPPROTO_UDP}},
{Key: []byte{unix.IPPROTO_ICMP}},
{Key: []byte{unix.IPPROTO_ICMPV6}},
})
if err != nil {
return err
}
nft.AddRule(&nftables.Rule{
Table: table,
Chain: chain,
@@ -447,10 +461,11 @@ func (r *autoRedirect) nftablesCreateExcludeRules(nft *nftables.Conn, table *nft
Key: expr.MetaKeyL4PROTO,
Register: 1,
},
&expr.Cmp{
Op: expr.CmpOpNeq,
Register: 1,
Data: []byte{unix.IPPROTO_UDP},
&expr.Lookup{
SourceRegister: 1,
SetID: ipProto.ID,
SetName: ipProto.Name,
Invert: true,
},
&expr.Verdict{
Kind: expr.VerdictReturn,
@@ -682,6 +697,7 @@ func (r *autoRedirect) nftablesCreateDNSHijackRulesForFamily(
Register: 1,
Data: binaryutil.BigEndian.PutUint16(53),
},
&expr.Counter{},
&expr.Immediate{
Register: 1,
Data: dnsServer.AsSlice(),
@@ -691,7 +707,6 @@ func (r *autoRedirect) nftablesCreateDNSHijackRulesForFamily(
Family: uint32(family),
RegAddrMin: 1,
},
&expr.Counter{},
)
nft.AddRule(&nftables.Rule{
Table: table,
@@ -727,9 +742,7 @@ func (r *autoRedirect) nftablesCreateUnreachable(
Data: []byte{uint8(nfProto)},
},
&expr.Counter{},
&expr.Verdict{
Kind: expr.VerdictDrop,
},
&expr.Reject{},
},
})
}

View File

@@ -6,6 +6,7 @@ import (
"context"
"net"
"os"
"sync"
"time"
"github.com/sagernet/gvisor/pkg/tcpip"
@@ -17,19 +18,25 @@ import (
)
type gLazyConn struct {
tcpConn *gonet.TCPConn
parentCtx context.Context
stack *stack.Stack
request *tcp.ForwarderRequest
localAddr net.Addr
remoteAddr net.Addr
handshakeDone bool
handshakeErr error
tcpConn *gonet.TCPConn
parentCtx context.Context
stack *stack.Stack
request *tcp.ForwarderRequest
localAddr net.Addr
remoteAddr net.Addr
handshakeAccess sync.Mutex
handshakeDone bool
handshakeErr error
}
func (c *gLazyConn) HandshakeContext(ctx context.Context) error {
if c.handshakeDone {
return nil
return c.handshakeErr
}
c.handshakeAccess.Lock()
defer c.handshakeAccess.Unlock()
if c.handshakeDone {
return c.handshakeErr
}
defer func() {
c.handshakeDone = true
@@ -64,6 +71,11 @@ func (c *gLazyConn) HandshakeContext(ctx context.Context) error {
}
func (c *gLazyConn) HandshakeFailure(err error) error {
if c.handshakeDone {
return os.ErrInvalid
}
c.handshakeAccess.Lock()
defer c.handshakeAccess.Unlock()
if c.handshakeDone {
return os.ErrInvalid
}
@@ -78,25 +90,17 @@ func (c *gLazyConn) HandshakeSuccess() error {
}
func (c *gLazyConn) Read(b []byte) (n int, err error) {
if !c.handshakeDone {
err = c.HandshakeContext(context.Background())
if err != nil {
return
}
} else if c.handshakeErr != nil {
return 0, c.handshakeErr
err = c.HandshakeContext(context.Background())
if err != nil {
return
}
return c.tcpConn.Read(b)
}
func (c *gLazyConn) Write(b []byte) (n int, err error) {
if !c.handshakeDone {
err = c.HandshakeContext(context.Background())
if err != nil {
return
}
} else if c.handshakeErr != nil {
return 0, c.handshakeErr
err = c.HandshakeContext(context.Background())
if err != nil {
return
}
return c.tcpConn.Write(b)
}
@@ -110,46 +114,41 @@ func (c *gLazyConn) RemoteAddr() net.Addr {
}
func (c *gLazyConn) SetDeadline(t time.Time) error {
if !c.handshakeDone {
err := c.HandshakeContext(context.Background())
if err != nil {
return err
}
} else if c.handshakeErr != nil {
return c.handshakeErr
err := c.HandshakeContext(context.Background())
if err != nil {
return err
}
return c.tcpConn.SetDeadline(t)
}
func (c *gLazyConn) SetReadDeadline(t time.Time) error {
if !c.handshakeDone {
err := c.HandshakeContext(context.Background())
if err != nil {
return err
}
} else if c.handshakeErr != nil {
return c.handshakeErr
err := c.HandshakeContext(context.Background())
if err != nil {
return err
}
return c.tcpConn.SetReadDeadline(t)
}
func (c *gLazyConn) SetWriteDeadline(t time.Time) error {
if !c.handshakeDone {
err := c.HandshakeContext(context.Background())
if err != nil {
return err
}
} else if c.handshakeErr != nil {
return c.handshakeErr
err := c.HandshakeContext(context.Background())
if err != nil {
return err
}
return c.tcpConn.SetWriteDeadline(t)
}
func (c *gLazyConn) Close() error {
if !c.handshakeDone {
c.request.Complete(true)
c.handshakeErr = net.ErrClosed
return nil
c.handshakeAccess.Lock()
if !c.handshakeDone {
c.request.Complete(true)
c.handshakeErr = net.ErrClosed
c.handshakeDone = true
return nil
} else if c.handshakeErr != nil {
return nil
}
c.handshakeAccess.Unlock()
} else if c.handshakeErr != nil {
return nil
}
@@ -158,9 +157,16 @@ func (c *gLazyConn) Close() error {
func (c *gLazyConn) CloseRead() error {
if !c.handshakeDone {
c.request.Complete(true)
c.handshakeErr = net.ErrClosed
return nil
c.handshakeAccess.Lock()
if !c.handshakeDone {
c.request.Complete(true)
c.handshakeErr = net.ErrClosed
c.handshakeDone = true
return nil
} else if c.handshakeErr != nil {
return nil
}
c.handshakeAccess.Unlock()
} else if c.handshakeErr != nil {
return nil
}
@@ -169,9 +175,16 @@ func (c *gLazyConn) CloseRead() error {
func (c *gLazyConn) CloseWrite() error {
if !c.handshakeDone {
c.request.Complete(true)
c.handshakeErr = net.ErrClosed
return nil
c.handshakeAccess.Lock()
if !c.handshakeDone {
c.request.Complete(true)
c.handshakeErr = net.ErrClosed
c.handshakeDone = true
return nil
} else if c.handshakeErr != nil {
return nil
}
c.handshakeAccess.Unlock()
} else if c.handshakeErr != nil {
return nil
}
@@ -179,10 +192,14 @@ func (c *gLazyConn) CloseWrite() error {
}
func (c *gLazyConn) ReaderReplaceable() bool {
c.handshakeAccess.Lock()
defer c.handshakeAccess.Unlock()
return c.handshakeDone && c.handshakeErr == nil
}
func (c *gLazyConn) WriterReplaceable() bool {
c.handshakeAccess.Lock()
defer c.handshakeAccess.Unlock()
return c.handshakeDone && c.handshakeErr == nil
}