mirror of
https://github.com/SagerNet/sing-tun.git
synced 2025-03-31 10:27:39 +03:00
230 lines
5.9 KiB
Go
230 lines
5.9 KiB
Go
//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"
|
|
"github.com/sagernet/sing/common/control"
|
|
|
|
"golang.org/x/exp/slices"
|
|
"golang.org/x/sys/unix"
|
|
)
|
|
|
|
func (r *autoRedirect) setupNFTables() error {
|
|
nft, err := nftables.New()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer nft.CloseLasting()
|
|
|
|
table := nft.AddTable(&nftables.Table{
|
|
Name: r.tableName,
|
|
Family: nftables.TableFamilyINet,
|
|
})
|
|
|
|
err = r.nftablesCreateAddressSets(nft, table, false)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = r.interfaceFinder.Update()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
r.localAddresses = common.FlatMap(r.interfaceFinder.Interfaces(), func(it control.Interface) []netip.Prefix {
|
|
return common.Filter(it.Addresses, func(prefix netip.Prefix) bool {
|
|
return it.Name == "lo" || prefix.Addr().IsGlobalUnicast()
|
|
})
|
|
})
|
|
err = r.nftablesCreateLocalAddressSets(nft, table, r.localAddresses, nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
skipOutput := len(r.tunOptions.IncludeInterface) > 0 && !common.Contains(r.tunOptions.IncludeInterface, "lo") || common.Contains(r.tunOptions.ExcludeInterface, "lo")
|
|
if !skipOutput {
|
|
chainOutput := nft.AddChain(&nftables.Chain{
|
|
Name: "output",
|
|
Table: table,
|
|
Hooknum: nftables.ChainHookOutput,
|
|
Priority: nftables.ChainPriorityMangle,
|
|
Type: nftables.ChainTypeNAT,
|
|
})
|
|
if r.tunOptions.AutoRedirectMarkMode {
|
|
err = r.nftablesCreateExcludeRules(nft, table, chainOutput)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
r.nftablesCreateUnreachable(nft, table, chainOutput)
|
|
r.nftablesCreateRedirect(nft, table, chainOutput)
|
|
|
|
chainOutputUDP := nft.AddChain(&nftables.Chain{
|
|
Name: "output_udp",
|
|
Table: table,
|
|
Hooknum: nftables.ChainHookOutput,
|
|
Priority: nftables.ChainPriorityMangle,
|
|
Type: nftables.ChainTypeRoute,
|
|
})
|
|
err = r.nftablesCreateExcludeRules(nft, table, chainOutputUDP)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
r.nftablesCreateUnreachable(nft, table, chainOutputUDP)
|
|
r.nftablesCreateMark(nft, table, chainOutputUDP)
|
|
} else {
|
|
r.nftablesCreateRedirect(nft, table, chainOutput, &expr.Meta{
|
|
Key: expr.MetaKeyOIFNAME,
|
|
Register: 1,
|
|
}, &expr.Cmp{
|
|
Op: expr.CmpOpEq,
|
|
Register: 1,
|
|
Data: nftablesIfname(r.tunOptions.Name),
|
|
})
|
|
}
|
|
}
|
|
|
|
chainPreRouting := nft.AddChain(&nftables.Chain{
|
|
Name: "prerouting",
|
|
Table: table,
|
|
Hooknum: nftables.ChainHookPrerouting,
|
|
Priority: nftables.ChainPriorityRef(*nftables.ChainPriorityNATDest + 1),
|
|
Type: nftables.ChainTypeNAT,
|
|
})
|
|
err = r.nftablesCreateExcludeRules(nft, table, chainPreRouting)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
r.nftablesCreateUnreachable(nft, table, chainPreRouting)
|
|
r.nftablesCreateRedirect(nft, table, chainPreRouting)
|
|
r.nftablesCreateMark(nft, table, chainPreRouting)
|
|
|
|
if r.tunOptions.AutoRedirectMarkMode {
|
|
chainPreRoutingUDP := nft.AddChain(&nftables.Chain{
|
|
Name: "prerouting_udp",
|
|
Table: table,
|
|
Hooknum: nftables.ChainHookPrerouting,
|
|
Priority: nftables.ChainPriorityRef(*nftables.ChainPriorityNATDest + 2),
|
|
Type: nftables.ChainTypeFilter,
|
|
})
|
|
if r.enableIPv4 {
|
|
nftablesCreateExcludeDestinationIPSet(nft, table, chainPreRoutingUDP, 5, "inet4_local_address_set", nftables.TableFamilyIPv4, false)
|
|
}
|
|
if r.enableIPv6 {
|
|
nftablesCreateExcludeDestinationIPSet(nft, table, chainPreRoutingUDP, 6, "inet6_local_address_set", nftables.TableFamilyIPv6, false)
|
|
}
|
|
nft.AddRule(&nftables.Rule{
|
|
Table: table,
|
|
Chain: chainPreRoutingUDP,
|
|
Exprs: []expr.Any{
|
|
&expr.Meta{
|
|
Key: expr.MetaKeyL4PROTO,
|
|
Register: 1,
|
|
},
|
|
&expr.Cmp{
|
|
Op: expr.CmpOpEq,
|
|
Register: 1,
|
|
Data: []byte{unix.IPPROTO_UDP},
|
|
},
|
|
&expr.Ct{
|
|
Key: expr.CtKeyMARK,
|
|
Register: 1,
|
|
},
|
|
&expr.Cmp{
|
|
Op: expr.CmpOpEq,
|
|
Register: 1,
|
|
Data: binaryutil.NativeEndian.PutUint32(r.tunOptions.AutoRedirectInputMark),
|
|
},
|
|
&expr.Meta{
|
|
Key: expr.MetaKeyMARK,
|
|
Register: 1,
|
|
SourceRegister: true,
|
|
},
|
|
&expr.Counter{},
|
|
},
|
|
})
|
|
}
|
|
|
|
err = r.configureOpenWRTFirewall4(nft, false)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = nft.Flush()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
r.networkListener = r.networkMonitor.RegisterCallback(func() {
|
|
err = r.nftablesUpdateLocalAddressSet()
|
|
if err != nil {
|
|
r.logger.Error("update local address set: ", err)
|
|
}
|
|
})
|
|
return nil
|
|
}
|
|
|
|
// TODO; test is this works
|
|
func (r *autoRedirect) nftablesUpdateLocalAddressSet() error {
|
|
newLocalAddresses := common.FlatMap(r.interfaceFinder.Interfaces(), func(it control.Interface) []netip.Prefix {
|
|
return common.Filter(it.Addresses, func(prefix netip.Prefix) bool {
|
|
return it.Name == "lo" || prefix.Addr().IsGlobalUnicast()
|
|
})
|
|
})
|
|
if slices.Equal(newLocalAddresses, r.localAddresses) {
|
|
return nil
|
|
}
|
|
nft, err := nftables.New()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer nft.CloseLasting()
|
|
table, err := nft.ListTableOfFamily(r.tableName, nftables.TableFamilyINet)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = r.nftablesCreateLocalAddressSets(nft, table, newLocalAddresses, r.localAddresses)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
r.localAddresses = newLocalAddresses
|
|
return nft.Flush()
|
|
}
|
|
|
|
func (r *autoRedirect) nftablesUpdateRouteAddressSet() error {
|
|
nft, err := nftables.New()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer nft.CloseLasting()
|
|
table, err := nft.ListTableOfFamily(r.tableName, nftables.TableFamilyINet)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = r.nftablesCreateAddressSets(nft, table, true)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nft.Flush()
|
|
}
|
|
|
|
func (r *autoRedirect) cleanupNFTables() {
|
|
if r.networkListener != nil {
|
|
r.networkMonitor.UnregisterCallback(r.networkListener)
|
|
}
|
|
nft, err := nftables.New()
|
|
if err != nil {
|
|
return
|
|
}
|
|
nft.DelTable(&nftables.Table{
|
|
Name: r.tableName,
|
|
Family: nftables.TableFamilyINet,
|
|
})
|
|
common.Must(r.configureOpenWRTFirewall4(nft, true))
|
|
_ = nft.Flush()
|
|
_ = nft.CloseLasting()
|
|
}
|