Add pre-matching support for auto redirect
This commit is contained in:
3
go.mod
3
go.mod
@@ -3,8 +3,10 @@ module github.com/sagernet/sing-tun
|
|||||||
go 1.24.7
|
go 1.24.7
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/florianl/go-nfqueue/v2 v2.0.2
|
||||||
github.com/go-ole/go-ole v1.3.0
|
github.com/go-ole/go-ole v1.3.0
|
||||||
github.com/google/btree v1.1.3
|
github.com/google/btree v1.1.3
|
||||||
|
github.com/mdlayher/netlink v1.7.2
|
||||||
github.com/sagernet/fswatch v0.1.1
|
github.com/sagernet/fswatch v0.1.1
|
||||||
github.com/sagernet/gvisor v0.0.0-20250811.0-sing-box-mod.1
|
github.com/sagernet/gvisor v0.0.0-20250811.0-sing-box-mod.1
|
||||||
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a
|
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a
|
||||||
@@ -22,7 +24,6 @@ require (
|
|||||||
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
||||||
github.com/google/go-cmp v0.6.0 // indirect
|
github.com/google/go-cmp v0.6.0 // indirect
|
||||||
github.com/josharian/native v1.1.0 // 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/mdlayher/socket v0.4.1 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
github.com/vishvananda/netns v0.0.4 // indirect
|
github.com/vishvananda/netns v0.0.4 // indirect
|
||||||
|
|||||||
2
go.sum
2
go.sum
@@ -1,5 +1,7 @@
|
|||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/florianl/go-nfqueue/v2 v2.0.2 h1:FL5lQTeetgpCvac1TRwSfgaXUn0YSO7WzGvWNIp3JPE=
|
||||||
|
github.com/florianl/go-nfqueue/v2 v2.0.2/go.mod h1:VA09+iPOT43OMoCKNfXHyzujQUty2xmzyCRkBOlmabc=
|
||||||
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
|
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
|
||||||
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
|
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
|
||||||
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
|
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
|
||||||
|
|||||||
244
nfqueue_linux.go
Normal file
244
nfqueue_linux.go
Normal file
@@ -0,0 +1,244 @@
|
|||||||
|
//go:build linux
|
||||||
|
|
||||||
|
package tun
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-tun/internal/gtcpip/header"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
"github.com/sagernet/sing/common/logger"
|
||||||
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
|
||||||
|
"github.com/florianl/go-nfqueue/v2"
|
||||||
|
"github.com/mdlayher/netlink"
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
const nfqueueMaxPacketLen = 512
|
||||||
|
|
||||||
|
type nfqueueHandler struct {
|
||||||
|
ctx context.Context
|
||||||
|
cancel context.CancelFunc
|
||||||
|
handler Handler
|
||||||
|
logger logger.Logger
|
||||||
|
nfq *nfqueue.Nfqueue
|
||||||
|
queue uint16
|
||||||
|
outputMark uint32
|
||||||
|
resetMark uint32
|
||||||
|
wg sync.WaitGroup
|
||||||
|
closed atomic.Bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type nfqueueOptions struct {
|
||||||
|
Context context.Context
|
||||||
|
Handler Handler
|
||||||
|
Logger logger.Logger
|
||||||
|
Queue uint16
|
||||||
|
OutputMark uint32
|
||||||
|
ResetMark uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
func newNFQueueHandler(options nfqueueOptions) (*nfqueueHandler, error) {
|
||||||
|
ctx, cancel := context.WithCancel(options.Context)
|
||||||
|
return &nfqueueHandler{
|
||||||
|
ctx: ctx,
|
||||||
|
cancel: cancel,
|
||||||
|
handler: options.Handler,
|
||||||
|
logger: options.Logger,
|
||||||
|
queue: options.Queue,
|
||||||
|
outputMark: options.OutputMark,
|
||||||
|
resetMark: options.ResetMark,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *nfqueueHandler) setVerdict(packetID uint32, verdict int, mark uint32) {
|
||||||
|
var err error
|
||||||
|
if mark != 0 {
|
||||||
|
err = h.nfq.SetVerdictWithOption(packetID, verdict, nfqueue.WithMark(mark))
|
||||||
|
} else {
|
||||||
|
err = h.nfq.SetVerdict(packetID, verdict)
|
||||||
|
}
|
||||||
|
if err != nil && !h.closed.Load() && h.ctx.Err() == nil {
|
||||||
|
h.logger.Trace(E.Cause(err, "set verdict"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *nfqueueHandler) Start() error {
|
||||||
|
config := nfqueue.Config{
|
||||||
|
NfQueue: h.queue,
|
||||||
|
MaxPacketLen: nfqueueMaxPacketLen,
|
||||||
|
MaxQueueLen: 4096,
|
||||||
|
Copymode: nfqueue.NfQnlCopyPacket,
|
||||||
|
AfFamily: unix.AF_UNSPEC,
|
||||||
|
Flags: nfqueue.NfQaCfgFlagFailOpen,
|
||||||
|
}
|
||||||
|
|
||||||
|
nfq, err := nfqueue.Open(&config)
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, "open nfqueue")
|
||||||
|
}
|
||||||
|
h.nfq = nfq
|
||||||
|
|
||||||
|
if err = nfq.SetOption(netlink.NoENOBUFS, true); err != nil {
|
||||||
|
h.nfq.Close()
|
||||||
|
return E.Cause(err, "set nfqueue option")
|
||||||
|
}
|
||||||
|
|
||||||
|
h.wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
defer h.wg.Done()
|
||||||
|
err := nfq.RegisterWithErrorFunc(h.ctx, h.handlePacket, func(e error) int {
|
||||||
|
if h.ctx.Err() != nil {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
h.logger.Error("nfqueue error: ", e)
|
||||||
|
return 0
|
||||||
|
})
|
||||||
|
if err != nil && h.ctx.Err() == nil {
|
||||||
|
h.logger.Error("nfqueue register error: ", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseIPv6TransportHeader(payload []byte) (transportProto uint8, transportOffset int, ok bool) {
|
||||||
|
if len(payload) < header.IPv6MinimumSize {
|
||||||
|
return 0, 0, false
|
||||||
|
}
|
||||||
|
|
||||||
|
ipv6 := header.IPv6(payload)
|
||||||
|
nextHeader := ipv6.NextHeader()
|
||||||
|
offset := header.IPv6MinimumSize
|
||||||
|
|
||||||
|
for {
|
||||||
|
switch nextHeader {
|
||||||
|
case unix.IPPROTO_HOPOPTS,
|
||||||
|
unix.IPPROTO_ROUTING,
|
||||||
|
unix.IPPROTO_DSTOPTS:
|
||||||
|
if len(payload) < offset+2 {
|
||||||
|
return 0, 0, false
|
||||||
|
}
|
||||||
|
nextHeader = payload[offset]
|
||||||
|
extLen := int(payload[offset+1]+1) * 8
|
||||||
|
if len(payload) < offset+extLen {
|
||||||
|
return 0, 0, false
|
||||||
|
}
|
||||||
|
offset += extLen
|
||||||
|
|
||||||
|
case unix.IPPROTO_FRAGMENT:
|
||||||
|
if len(payload) < offset+8 {
|
||||||
|
return 0, 0, false
|
||||||
|
}
|
||||||
|
nextHeader = payload[offset]
|
||||||
|
offset += 8
|
||||||
|
|
||||||
|
case unix.IPPROTO_AH:
|
||||||
|
if len(payload) < offset+2 {
|
||||||
|
return 0, 0, false
|
||||||
|
}
|
||||||
|
nextHeader = payload[offset]
|
||||||
|
extLen := int(payload[offset+1]+2) * 4
|
||||||
|
if len(payload) < offset+extLen {
|
||||||
|
return 0, 0, false
|
||||||
|
}
|
||||||
|
offset += extLen
|
||||||
|
|
||||||
|
case unix.IPPROTO_NONE:
|
||||||
|
return 0, 0, false
|
||||||
|
|
||||||
|
default:
|
||||||
|
return nextHeader, offset, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *nfqueueHandler) handlePacket(attr nfqueue.Attribute) int {
|
||||||
|
if h.closed.Load() {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
if attr.PacketID == nil || attr.Payload == nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
packetID := *attr.PacketID
|
||||||
|
payload := *attr.Payload
|
||||||
|
|
||||||
|
if len(payload) < header.IPv4MinimumSize {
|
||||||
|
h.setVerdict(packetID, nfqueue.NfAccept, 0)
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
var srcAddr, dstAddr M.Socksaddr
|
||||||
|
var tcpOffset int
|
||||||
|
|
||||||
|
version := payload[0] >> 4
|
||||||
|
if version == 4 {
|
||||||
|
ipv4 := header.IPv4(payload)
|
||||||
|
if !ipv4.IsValid(len(payload)) || ipv4.Protocol() != uint8(unix.IPPROTO_TCP) {
|
||||||
|
h.setVerdict(packetID, nfqueue.NfAccept, 0)
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
srcAddr = M.SocksaddrFrom(ipv4.SourceAddr(), 0)
|
||||||
|
dstAddr = M.SocksaddrFrom(ipv4.DestinationAddr(), 0)
|
||||||
|
tcpOffset = int(ipv4.HeaderLength())
|
||||||
|
} else if version == 6 {
|
||||||
|
transportProto, transportOffset, ok := parseIPv6TransportHeader(payload)
|
||||||
|
if !ok || transportProto != unix.IPPROTO_TCP {
|
||||||
|
h.setVerdict(packetID, nfqueue.NfAccept, 0)
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
ipv6 := header.IPv6(payload)
|
||||||
|
srcAddr = M.SocksaddrFrom(ipv6.SourceAddr(), 0)
|
||||||
|
dstAddr = M.SocksaddrFrom(ipv6.DestinationAddr(), 0)
|
||||||
|
tcpOffset = transportOffset
|
||||||
|
} else {
|
||||||
|
h.setVerdict(packetID, nfqueue.NfAccept, 0)
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(payload) < tcpOffset+header.TCPMinimumSize {
|
||||||
|
h.setVerdict(packetID, nfqueue.NfAccept, 0)
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
tcp := header.TCP(payload[tcpOffset:])
|
||||||
|
srcAddr = M.SocksaddrFrom(srcAddr.Addr, tcp.SourcePort())
|
||||||
|
dstAddr = M.SocksaddrFrom(dstAddr.Addr, tcp.DestinationPort())
|
||||||
|
|
||||||
|
flags := tcp.Flags()
|
||||||
|
if !flags.Contains(header.TCPFlagSyn) || flags.Contains(header.TCPFlagAck) {
|
||||||
|
h.setVerdict(packetID, nfqueue.NfAccept, 0)
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
_, pErr := h.handler.PrepareConnection(N.NetworkTCP, srcAddr, dstAddr, nil, 0)
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case errors.Is(pErr, ErrBypass):
|
||||||
|
h.setVerdict(packetID, nfqueue.NfAccept, h.outputMark)
|
||||||
|
case errors.Is(pErr, ErrReset):
|
||||||
|
h.setVerdict(packetID, nfqueue.NfAccept, h.resetMark)
|
||||||
|
case errors.Is(pErr, ErrDrop):
|
||||||
|
h.setVerdict(packetID, nfqueue.NfDrop, 0)
|
||||||
|
default:
|
||||||
|
h.setVerdict(packetID, nfqueue.NfAccept, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *nfqueueHandler) Close() error {
|
||||||
|
h.closed.Store(true)
|
||||||
|
h.cancel()
|
||||||
|
if h.nfq != nil {
|
||||||
|
h.nfq.Close()
|
||||||
|
}
|
||||||
|
h.wg.Wait()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -5,7 +5,6 @@ import (
|
|||||||
|
|
||||||
"github.com/sagernet/sing/common/control"
|
"github.com/sagernet/sing/common/control"
|
||||||
"github.com/sagernet/sing/common/logger"
|
"github.com/sagernet/sing/common/logger"
|
||||||
N "github.com/sagernet/sing/common/network"
|
|
||||||
|
|
||||||
"go4.org/netipx"
|
"go4.org/netipx"
|
||||||
)
|
)
|
||||||
@@ -13,6 +12,8 @@ import (
|
|||||||
const (
|
const (
|
||||||
DefaultAutoRedirectInputMark = 0x2023
|
DefaultAutoRedirectInputMark = 0x2023
|
||||||
DefaultAutoRedirectOutputMark = 0x2024
|
DefaultAutoRedirectOutputMark = 0x2024
|
||||||
|
DefaultAutoRedirectResetMark = 0x2025
|
||||||
|
DefaultAutoRedirectNFQueue = 100
|
||||||
)
|
)
|
||||||
|
|
||||||
type AutoRedirect interface {
|
type AutoRedirect interface {
|
||||||
@@ -24,7 +25,7 @@ type AutoRedirect interface {
|
|||||||
type AutoRedirectOptions struct {
|
type AutoRedirectOptions struct {
|
||||||
TunOptions *Options
|
TunOptions *Options
|
||||||
Context context.Context
|
Context context.Context
|
||||||
Handler N.TCPConnectionHandlerEx
|
Handler Handler
|
||||||
Logger logger.Logger
|
Logger logger.Logger
|
||||||
NetworkMonitor NetworkUpdateMonitor
|
NetworkMonitor NetworkUpdateMonitor
|
||||||
InterfaceFinder control.InterfaceFinder
|
InterfaceFinder control.InterfaceFinder
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ import (
|
|||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
"github.com/sagernet/sing/common/logger"
|
"github.com/sagernet/sing/common/logger"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
N "github.com/sagernet/sing/common/network"
|
|
||||||
"github.com/sagernet/sing/common/x/list"
|
"github.com/sagernet/sing/common/x/list"
|
||||||
|
|
||||||
"go4.org/netipx"
|
"go4.org/netipx"
|
||||||
@@ -22,7 +21,7 @@ import (
|
|||||||
type autoRedirect struct {
|
type autoRedirect struct {
|
||||||
tunOptions *Options
|
tunOptions *Options
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
handler N.TCPConnectionHandlerEx
|
handler Handler
|
||||||
logger logger.Logger
|
logger logger.Logger
|
||||||
tableName string
|
tableName string
|
||||||
networkMonitor NetworkUpdateMonitor
|
networkMonitor NetworkUpdateMonitor
|
||||||
@@ -41,6 +40,8 @@ type autoRedirect struct {
|
|||||||
suPath string
|
suPath string
|
||||||
routeAddressSet *[]*netipx.IPSet
|
routeAddressSet *[]*netipx.IPSet
|
||||||
routeExcludeAddressSet *[]*netipx.IPSet
|
routeExcludeAddressSet *[]*netipx.IPSet
|
||||||
|
nfqueueHandler *nfqueueHandler
|
||||||
|
nfqueueEnabled bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewAutoRedirect(options AutoRedirectOptions) (AutoRedirect, error) {
|
func NewAutoRedirect(options AutoRedirectOptions) (AutoRedirect, error) {
|
||||||
@@ -125,13 +126,30 @@ func (r *autoRedirect) Start() error {
|
|||||||
listenAddr = netip.IPv4Unspecified()
|
listenAddr = netip.IPv4Unspecified()
|
||||||
}
|
}
|
||||||
server := newRedirectServer(r.ctx, r.handler, r.logger, listenAddr)
|
server := newRedirectServer(r.ctx, r.handler, r.logger, listenAddr)
|
||||||
err := server.Start()
|
err = server.Start()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause(err, "start redirect server")
|
return E.Cause(err, "start redirect server")
|
||||||
}
|
}
|
||||||
r.redirectServer = server
|
r.redirectServer = server
|
||||||
}
|
}
|
||||||
if r.useNFTables {
|
if r.useNFTables {
|
||||||
|
var handler *nfqueueHandler
|
||||||
|
handler, err = newNFQueueHandler(nfqueueOptions{
|
||||||
|
Context: r.ctx,
|
||||||
|
Handler: r.handler,
|
||||||
|
Logger: r.logger,
|
||||||
|
Queue: r.effectiveNFQueue(),
|
||||||
|
OutputMark: r.effectiveOutputMark(),
|
||||||
|
ResetMark: r.effectiveResetMark(),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
r.logger.Warn("nfqueue not available, pre-match disabled: ", err)
|
||||||
|
} else if err = handler.Start(); err != nil {
|
||||||
|
r.logger.Warn("nfqueue start failed, pre-match disabled: ", err)
|
||||||
|
} else {
|
||||||
|
r.nfqueueHandler = handler
|
||||||
|
r.nfqueueEnabled = true
|
||||||
|
}
|
||||||
r.cleanupNFTables()
|
r.cleanupNFTables()
|
||||||
err = r.setupNFTables()
|
err = r.setupNFTables()
|
||||||
} else {
|
} else {
|
||||||
@@ -142,6 +160,9 @@ func (r *autoRedirect) Start() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *autoRedirect) Close() error {
|
func (r *autoRedirect) Close() error {
|
||||||
|
if r.nfqueueHandler != nil {
|
||||||
|
r.nfqueueHandler.Close()
|
||||||
|
}
|
||||||
if r.useNFTables {
|
if r.useNFTables {
|
||||||
r.cleanupNFTables()
|
r.cleanupNFTables()
|
||||||
} else {
|
} else {
|
||||||
@@ -181,3 +202,28 @@ func (r *autoRedirect) redirectPort() uint16 {
|
|||||||
}
|
}
|
||||||
return M.AddrPortFromNet(r.redirectServer.listener.Addr()).Port()
|
return M.AddrPortFromNet(r.redirectServer.listener.Addr()).Port()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *autoRedirect) effectiveOutputMark() uint32 {
|
||||||
|
if r.tunOptions.AutoRedirectOutputMark != 0 {
|
||||||
|
return r.tunOptions.AutoRedirectOutputMark
|
||||||
|
}
|
||||||
|
return DefaultAutoRedirectOutputMark
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *autoRedirect) effectiveResetMark() uint32 {
|
||||||
|
if r.tunOptions.AutoRedirectResetMark != 0 {
|
||||||
|
return r.tunOptions.AutoRedirectResetMark
|
||||||
|
}
|
||||||
|
return DefaultAutoRedirectResetMark
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *autoRedirect) effectiveNFQueue() uint16 {
|
||||||
|
if r.tunOptions.AutoRedirectNFQueue != 0 {
|
||||||
|
return r.tunOptions.AutoRedirectNFQueue
|
||||||
|
}
|
||||||
|
return DefaultAutoRedirectNFQueue
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *autoRedirect) shouldSkipOutputChain() bool {
|
||||||
|
return len(r.tunOptions.IncludeInterface) > 0 && !common.Contains(r.tunOptions.IncludeInterface, "lo") || common.Contains(r.tunOptions.ExcludeInterface, "lo")
|
||||||
|
}
|
||||||
|
|||||||
@@ -51,13 +51,23 @@ func (r *autoRedirect) setupNFTables() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
skipOutput := len(r.tunOptions.IncludeInterface) > 0 && !common.Contains(r.tunOptions.IncludeInterface, "lo") || common.Contains(r.tunOptions.ExcludeInterface, "lo")
|
if r.nfqueueEnabled {
|
||||||
if !skipOutput {
|
err = r.nftablesCreatePreMatchChains(nft, table)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !r.shouldSkipOutputChain() {
|
||||||
|
outputNATPriority := nftables.ChainPriorityMangle
|
||||||
|
if r.nfqueueEnabled {
|
||||||
|
outputNATPriority = nftables.ChainPriorityRef(*nftables.ChainPriorityMangle + 1)
|
||||||
|
}
|
||||||
chainOutput := nft.AddChain(&nftables.Chain{
|
chainOutput := nft.AddChain(&nftables.Chain{
|
||||||
Name: "output",
|
Name: "output",
|
||||||
Table: table,
|
Table: table,
|
||||||
Hooknum: nftables.ChainHookOutput,
|
Hooknum: nftables.ChainHookOutput,
|
||||||
Priority: nftables.ChainPriorityMangle,
|
Priority: outputNATPriority,
|
||||||
Type: nftables.ChainTypeNAT,
|
Type: nftables.ChainTypeNAT,
|
||||||
})
|
})
|
||||||
if r.tunOptions.AutoRedirectMarkMode {
|
if r.tunOptions.AutoRedirectMarkMode {
|
||||||
@@ -267,7 +277,7 @@ func (r *autoRedirect) setupNFTables() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO; test is this works
|
// TODO: test if this works
|
||||||
func (r *autoRedirect) nftablesUpdateLocalAddressSet() error {
|
func (r *autoRedirect) nftablesUpdateLocalAddressSet() error {
|
||||||
newLocalAddresses := common.FlatMap(r.interfaceFinder.Interfaces(), func(it control.Interface) []netip.Prefix {
|
newLocalAddresses := common.FlatMap(r.interfaceFinder.Interfaces(), func(it control.Interface) []netip.Prefix {
|
||||||
return common.Filter(it.Addresses, func(prefix netip.Prefix) bool {
|
return common.Filter(it.Addresses, func(prefix netip.Prefix) bool {
|
||||||
@@ -327,3 +337,122 @@ func (r *autoRedirect) cleanupNFTables() {
|
|||||||
_ = nft.Flush()
|
_ = nft.Flush()
|
||||||
_ = nft.CloseLasting()
|
_ = nft.CloseLasting()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *autoRedirect) nftablesCreatePreMatchChains(nft *nftables.Conn, table *nftables.Table) error {
|
||||||
|
chainPreroutingPreMatch := nft.AddChain(&nftables.Chain{
|
||||||
|
Name: "prerouting_prematch",
|
||||||
|
Table: table,
|
||||||
|
Hooknum: nftables.ChainHookPrerouting,
|
||||||
|
Priority: nftables.ChainPriorityRef(*nftables.ChainPriorityNATDest - 1),
|
||||||
|
Type: nftables.ChainTypeFilter,
|
||||||
|
})
|
||||||
|
r.nftablesAddPreMatchRules(nft, table, chainPreroutingPreMatch, true)
|
||||||
|
|
||||||
|
if !r.shouldSkipOutputChain() {
|
||||||
|
chainOutputPreMatch := nft.AddChain(&nftables.Chain{
|
||||||
|
Name: "output_prematch",
|
||||||
|
Table: table,
|
||||||
|
Hooknum: nftables.ChainHookOutput,
|
||||||
|
Priority: nftables.ChainPriorityRef(*nftables.ChainPriorityMangle - 1),
|
||||||
|
Type: nftables.ChainTypeFilter,
|
||||||
|
})
|
||||||
|
r.nftablesAddPreMatchRules(nft, table, chainOutputPreMatch, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *autoRedirect) nftablesAddPreMatchRules(nft *nftables.Conn, table *nftables.Table, chain *nftables.Chain, isPrerouting bool) {
|
||||||
|
ifnameKey := expr.MetaKeyOIFNAME
|
||||||
|
if isPrerouting {
|
||||||
|
ifnameKey = expr.MetaKeyIIFNAME
|
||||||
|
}
|
||||||
|
nft.AddRule(&nftables.Rule{
|
||||||
|
Table: table,
|
||||||
|
Chain: chain,
|
||||||
|
Exprs: []expr.Any{
|
||||||
|
&expr.Meta{Key: ifnameKey, Register: 1},
|
||||||
|
&expr.Cmp{Op: expr.CmpOpEq, Register: 1, Data: nftablesIfname(r.tunOptions.Name)},
|
||||||
|
&expr.Verdict{Kind: expr.VerdictReturn},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
nft.AddRule(&nftables.Rule{
|
||||||
|
Table: table,
|
||||||
|
Chain: chain,
|
||||||
|
Exprs: []expr.Any{
|
||||||
|
&expr.Meta{Key: expr.MetaKeyL4PROTO, Register: 1},
|
||||||
|
&expr.Cmp{Op: expr.CmpOpNeq, Register: 1, Data: []byte{unix.IPPROTO_TCP}},
|
||||||
|
&expr.Verdict{Kind: expr.VerdictReturn},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
nft.AddRule(&nftables.Rule{
|
||||||
|
Table: table,
|
||||||
|
Chain: chain,
|
||||||
|
Exprs: []expr.Any{
|
||||||
|
&expr.Meta{Key: expr.MetaKeyMARK, Register: 1},
|
||||||
|
&expr.Cmp{Op: expr.CmpOpEq, Register: 1, Data: binaryutil.NativeEndian.PutUint32(r.effectiveOutputMark())},
|
||||||
|
&expr.Verdict{Kind: expr.VerdictReturn},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
nft.AddRule(&nftables.Rule{
|
||||||
|
Table: table,
|
||||||
|
Chain: chain,
|
||||||
|
Exprs: []expr.Any{
|
||||||
|
&expr.Ct{Key: expr.CtKeyMARK, Register: 1},
|
||||||
|
&expr.Cmp{Op: expr.CmpOpEq, Register: 1, Data: binaryutil.NativeEndian.PutUint32(r.effectiveOutputMark())},
|
||||||
|
&expr.Verdict{Kind: expr.VerdictReturn},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
nft.AddRule(&nftables.Rule{
|
||||||
|
Table: table,
|
||||||
|
Chain: chain,
|
||||||
|
Exprs: []expr.Any{
|
||||||
|
&expr.Payload{
|
||||||
|
OperationType: expr.PayloadLoad,
|
||||||
|
DestRegister: 1,
|
||||||
|
Base: expr.PayloadBaseTransportHeader,
|
||||||
|
Offset: 13,
|
||||||
|
Len: 1,
|
||||||
|
},
|
||||||
|
&expr.Bitwise{
|
||||||
|
SourceRegister: 1,
|
||||||
|
DestRegister: 1,
|
||||||
|
Len: 1,
|
||||||
|
Mask: []byte{0x12},
|
||||||
|
Xor: []byte{0x00},
|
||||||
|
},
|
||||||
|
&expr.Cmp{Op: expr.CmpOpEq, Register: 1, Data: []byte{0x02}},
|
||||||
|
&expr.Counter{},
|
||||||
|
&expr.Queue{
|
||||||
|
Num: r.effectiveNFQueue(),
|
||||||
|
Flag: expr.QueueFlagBypass,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
nft.AddRule(&nftables.Rule{
|
||||||
|
Table: table,
|
||||||
|
Chain: chain,
|
||||||
|
Exprs: []expr.Any{
|
||||||
|
&expr.Meta{Key: expr.MetaKeyMARK, Register: 1},
|
||||||
|
&expr.Cmp{Op: expr.CmpOpEq, Register: 1, Data: binaryutil.NativeEndian.PutUint32(r.effectiveResetMark())},
|
||||||
|
&expr.Counter{},
|
||||||
|
&expr.Reject{Type: unix.NFT_REJECT_TCP_RST},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
nft.AddRule(&nftables.Rule{
|
||||||
|
Table: table,
|
||||||
|
Chain: chain,
|
||||||
|
Exprs: []expr.Any{
|
||||||
|
&expr.Meta{Key: expr.MetaKeyMARK, Register: 1},
|
||||||
|
&expr.Cmp{Op: expr.CmpOpEq, Register: 1, Data: binaryutil.NativeEndian.PutUint32(r.effectiveOutputMark())},
|
||||||
|
&expr.Ct{Key: expr.CtKeyMARK, Register: 1, SourceRegister: true},
|
||||||
|
&expr.Counter{},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@@ -213,6 +213,48 @@ func (r *autoRedirect) nftablesCreateExcludeRules(nft *nftables.Conn, table *nft
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if r.nfqueueEnabled && chain.Hooknum == nftables.ChainHookPrerouting && chain.Type == nftables.ChainTypeNAT {
|
||||||
|
nft.AddRule(&nftables.Rule{
|
||||||
|
Table: table,
|
||||||
|
Chain: chain,
|
||||||
|
Exprs: []expr.Any{
|
||||||
|
&expr.Ct{
|
||||||
|
Key: expr.CtKeyMARK,
|
||||||
|
Register: 1,
|
||||||
|
},
|
||||||
|
&expr.Cmp{
|
||||||
|
Op: expr.CmpOpEq,
|
||||||
|
Register: 1,
|
||||||
|
Data: binaryutil.NativeEndian.PutUint32(r.effectiveOutputMark()),
|
||||||
|
},
|
||||||
|
&expr.Counter{},
|
||||||
|
&expr.Verdict{
|
||||||
|
Kind: expr.VerdictReturn,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if r.nfqueueEnabled && chain.Hooknum == nftables.ChainHookOutput && chain.Type == nftables.ChainTypeNAT {
|
||||||
|
nft.AddRule(&nftables.Rule{
|
||||||
|
Table: table,
|
||||||
|
Chain: chain,
|
||||||
|
Exprs: []expr.Any{
|
||||||
|
&expr.Ct{
|
||||||
|
Key: expr.CtKeyMARK,
|
||||||
|
Register: 1,
|
||||||
|
},
|
||||||
|
&expr.Cmp{
|
||||||
|
Op: expr.CmpOpEq,
|
||||||
|
Register: 1,
|
||||||
|
Data: binaryutil.NativeEndian.PutUint32(r.effectiveOutputMark()),
|
||||||
|
},
|
||||||
|
&expr.Counter{},
|
||||||
|
&expr.Verdict{
|
||||||
|
Kind: expr.VerdictReturn,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
if chain.Hooknum == nftables.ChainHookPrerouting {
|
if chain.Hooknum == nftables.ChainHookPrerouting {
|
||||||
nft.AddRule(&nftables.Rule{
|
nft.AddRule(&nftables.Rule{
|
||||||
Table: table,
|
Table: table,
|
||||||
|
|||||||
1
stack.go
1
stack.go
@@ -15,6 +15,7 @@ import (
|
|||||||
var (
|
var (
|
||||||
ErrDrop = E.New("drop by rule")
|
ErrDrop = E.New("drop by rule")
|
||||||
ErrReset = E.New("reset by rule")
|
ErrReset = E.New("reset by rule")
|
||||||
|
ErrBypass = E.New("bypass by rule")
|
||||||
)
|
)
|
||||||
|
|
||||||
type Stack interface {
|
type Stack interface {
|
||||||
|
|||||||
2
tun.go
2
tun.go
@@ -83,6 +83,8 @@ type Options struct {
|
|||||||
AutoRedirectMarkMode bool
|
AutoRedirectMarkMode bool
|
||||||
AutoRedirectInputMark uint32
|
AutoRedirectInputMark uint32
|
||||||
AutoRedirectOutputMark uint32
|
AutoRedirectOutputMark uint32
|
||||||
|
AutoRedirectResetMark uint32
|
||||||
|
AutoRedirectNFQueue uint16
|
||||||
ExcludeMPTCP bool
|
ExcludeMPTCP bool
|
||||||
Inet4LoopbackAddress []netip.Addr
|
Inet4LoopbackAddress []netip.Addr
|
||||||
Inet6LoopbackAddress []netip.Addr
|
Inet6LoopbackAddress []netip.Addr
|
||||||
|
|||||||
Reference in New Issue
Block a user