mirror of
https://github.com/SagerNet/sing-tun.git
synced 2025-03-31 10:27:39 +03:00
687 lines
18 KiB
Go
687 lines
18 KiB
Go
//go:build linux
|
|
|
|
package tun
|
|
|
|
import (
|
|
"net/netip"
|
|
_ "unsafe"
|
|
|
|
"github.com/sagernet/nftables"
|
|
"github.com/sagernet/nftables/binaryutil"
|
|
"github.com/sagernet/nftables/expr"
|
|
"github.com/sagernet/nftables/userdata"
|
|
"github.com/sagernet/sing/common"
|
|
"github.com/sagernet/sing/common/ranges"
|
|
|
|
"golang.org/x/exp/slices"
|
|
"golang.org/x/sys/unix"
|
|
)
|
|
|
|
//go:linkname allocSetID github.com/sagernet/nftables.allocSetID
|
|
var allocSetID uint32
|
|
|
|
func init() {
|
|
allocSetID = 6
|
|
}
|
|
|
|
func (r *autoRedirect) nftablesCreateAddressSets(
|
|
nft *nftables.Conn, table *nftables.Table,
|
|
update bool,
|
|
) error {
|
|
routeAddressSet := *r.routeAddressSet
|
|
routeExcludeAddressSet := *r.routeExcludeAddressSet
|
|
if len(routeAddressSet) == 0 && len(routeExcludeAddressSet) == 0 {
|
|
return nil
|
|
}
|
|
|
|
if len(routeAddressSet) > 0 {
|
|
if r.enableIPv4 {
|
|
_, err := nftablesCreateIPSet(nft, table, 1, "inet4_route_address_set", nftables.TableFamilyIPv4, routeAddressSet, nil, true, update)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if r.enableIPv6 {
|
|
_, err := nftablesCreateIPSet(nft, table, 2, "inet6_route_address_set", nftables.TableFamilyIPv6, routeAddressSet, nil, true, update)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
|
|
if len(routeExcludeAddressSet) > 0 {
|
|
if r.enableIPv4 {
|
|
_, err := nftablesCreateIPSet(nft, table, 3, "inet4_route_exclude_address_set", nftables.TableFamilyIPv4, routeExcludeAddressSet, nil, false, update)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if r.enableIPv6 {
|
|
_, err := nftablesCreateIPSet(nft, table, 4, "inet6_route_exclude_address_set", nftables.TableFamilyIPv6, routeExcludeAddressSet, nil, false, update)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (r *autoRedirect) nftablesCreateLocalAddressSets(
|
|
nft *nftables.Conn, table *nftables.Table,
|
|
localAddresses []netip.Prefix, lastAddresses []netip.Prefix,
|
|
) error {
|
|
if r.enableIPv4 {
|
|
localAddresses4 := common.Filter(localAddresses, func(it netip.Prefix) bool {
|
|
return it.Addr().Is4()
|
|
})
|
|
updateAddresses4 := common.Filter(localAddresses, func(it netip.Prefix) bool {
|
|
return it.Addr().Is4()
|
|
})
|
|
var update bool
|
|
if len(lastAddresses) != 0 {
|
|
if !slices.Equal(localAddresses4, updateAddresses4) {
|
|
update = true
|
|
}
|
|
}
|
|
if len(lastAddresses) == 0 || update {
|
|
_, err := nftablesCreateIPSet(nft, table, 5, "inet4_local_address_set", nftables.TableFamilyIPv4, nil, localAddresses4, false, update)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
if r.enableIPv6 {
|
|
localAddresses6 := common.Filter(localAddresses, func(it netip.Prefix) bool {
|
|
return it.Addr().Is6()
|
|
})
|
|
updateAddresses6 := common.Filter(localAddresses, func(it netip.Prefix) bool {
|
|
return it.Addr().Is6()
|
|
})
|
|
var update bool
|
|
if len(lastAddresses) != 0 {
|
|
if !slices.Equal(localAddresses6, updateAddresses6) {
|
|
update = true
|
|
}
|
|
}
|
|
localAddresses6 = common.Filter(localAddresses6, func(it netip.Prefix) bool {
|
|
address := it.Addr()
|
|
return address.IsLoopback() || address.IsGlobalUnicast() && !address.IsPrivate()
|
|
})
|
|
if len(lastAddresses) == 0 || update {
|
|
_, err := nftablesCreateIPSet(nft, table, 6, "inet6_local_address_set", nftables.TableFamilyIPv6, nil, localAddresses6, false, update)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (r *autoRedirect) nftablesCreateExcludeRules(nft *nftables.Conn, table *nftables.Table, chain *nftables.Chain) error {
|
|
if r.tunOptions.AutoRedirectMarkMode && chain.Hooknum == nftables.ChainHookOutput {
|
|
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.tunOptions.AutoRedirectOutputMark),
|
|
},
|
|
&expr.Counter{},
|
|
&expr.Verdict{
|
|
Kind: expr.VerdictReturn,
|
|
},
|
|
},
|
|
})
|
|
}
|
|
if chain.Hooknum == nftables.ChainHookPrerouting {
|
|
if len(r.tunOptions.IncludeInterface) > 0 {
|
|
if len(r.tunOptions.IncludeInterface) > 1 {
|
|
includeInterface := &nftables.Set{
|
|
Table: table,
|
|
Anonymous: true,
|
|
Constant: true,
|
|
KeyType: nftables.TypeIFName,
|
|
}
|
|
err := nft.AddSet(includeInterface, common.Map(r.tunOptions.IncludeInterface, func(it string) nftables.SetElement {
|
|
return nftables.SetElement{
|
|
Key: nftablesIfname(it),
|
|
}
|
|
}))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
nft.AddRule(&nftables.Rule{
|
|
Table: table,
|
|
Chain: chain,
|
|
Exprs: []expr.Any{
|
|
&expr.Meta{Key: expr.MetaKeyIIFNAME, Register: 1},
|
|
&expr.Lookup{
|
|
SourceRegister: 1,
|
|
SetID: includeInterface.ID,
|
|
SetName: includeInterface.Name,
|
|
Invert: true,
|
|
},
|
|
&expr.Counter{},
|
|
&expr.Verdict{
|
|
Kind: expr.VerdictReturn,
|
|
},
|
|
},
|
|
})
|
|
} else {
|
|
nft.AddRule(&nftables.Rule{
|
|
Table: table,
|
|
Chain: chain,
|
|
Exprs: []expr.Any{
|
|
&expr.Meta{Key: expr.MetaKeyIIFNAME, Register: 1},
|
|
&expr.Cmp{
|
|
Op: expr.CmpOpNeq,
|
|
Register: 1,
|
|
Data: nftablesIfname(r.tunOptions.IncludeInterface[0]),
|
|
},
|
|
&expr.Counter{},
|
|
&expr.Verdict{
|
|
Kind: expr.VerdictReturn,
|
|
},
|
|
},
|
|
})
|
|
}
|
|
}
|
|
|
|
if len(r.tunOptions.ExcludeInterface) > 0 {
|
|
if len(r.tunOptions.ExcludeInterface) > 1 {
|
|
excludeInterface := &nftables.Set{
|
|
Table: table,
|
|
Anonymous: true,
|
|
Constant: true,
|
|
KeyType: nftables.TypeIFName,
|
|
}
|
|
err := nft.AddSet(excludeInterface, common.Map(r.tunOptions.ExcludeInterface, func(it string) nftables.SetElement {
|
|
return nftables.SetElement{
|
|
Key: nftablesIfname(it),
|
|
}
|
|
}))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
nft.AddRule(&nftables.Rule{
|
|
Table: table,
|
|
Chain: chain,
|
|
Exprs: []expr.Any{
|
|
&expr.Meta{Key: expr.MetaKeyIIFNAME, Register: 1},
|
|
&expr.Lookup{
|
|
SourceRegister: 1,
|
|
SetID: excludeInterface.ID,
|
|
SetName: excludeInterface.Name,
|
|
},
|
|
&expr.Counter{},
|
|
&expr.Verdict{
|
|
Kind: expr.VerdictReturn,
|
|
},
|
|
},
|
|
})
|
|
} else {
|
|
nft.AddRule(&nftables.Rule{
|
|
Table: table,
|
|
Chain: chain,
|
|
Exprs: []expr.Any{
|
|
&expr.Meta{Key: expr.MetaKeyIIFNAME, Register: 1},
|
|
&expr.Cmp{
|
|
Op: expr.CmpOpEq,
|
|
Register: 1,
|
|
Data: nftablesIfname(r.tunOptions.ExcludeInterface[0]),
|
|
},
|
|
&expr.Counter{},
|
|
&expr.Verdict{
|
|
Kind: expr.VerdictReturn,
|
|
},
|
|
},
|
|
})
|
|
}
|
|
}
|
|
} else {
|
|
if len(r.tunOptions.IncludeUID) > 0 {
|
|
if len(r.tunOptions.IncludeUID) > 1 || r.tunOptions.IncludeUID[0].Start != r.tunOptions.IncludeUID[0].End {
|
|
includeUID := &nftables.Set{
|
|
Table: table,
|
|
Anonymous: true,
|
|
Constant: true,
|
|
Interval: true,
|
|
KeyType: nftables.TypeUID,
|
|
}
|
|
err := nft.AddSet(includeUID, common.FlatMap(r.tunOptions.IncludeUID, func(it ranges.Range[uint32]) []nftables.SetElement {
|
|
return []nftables.SetElement{
|
|
{
|
|
Key: binaryutil.NativeEndian.PutUint32(it.Start),
|
|
},
|
|
{
|
|
Key: binaryutil.NativeEndian.PutUint32(it.End + 1),
|
|
IntervalEnd: true,
|
|
},
|
|
}
|
|
}))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
nft.AddRule(&nftables.Rule{
|
|
Table: table,
|
|
Chain: chain,
|
|
Exprs: []expr.Any{
|
|
&expr.Meta{Key: expr.MetaKeySKUID, Register: 1},
|
|
&expr.Lookup{
|
|
SourceRegister: 1,
|
|
SetID: includeUID.ID,
|
|
SetName: includeUID.Name,
|
|
Invert: true,
|
|
},
|
|
&expr.Counter{},
|
|
&expr.Verdict{
|
|
Kind: expr.VerdictReturn,
|
|
},
|
|
},
|
|
UserData: userdata.AppendString(nil, userdata.TypeComment, "not a bug :("),
|
|
})
|
|
} else {
|
|
nft.AddRule(&nftables.Rule{
|
|
Table: table,
|
|
Chain: chain,
|
|
Exprs: []expr.Any{
|
|
&expr.Meta{Key: expr.MetaKeySKUID, Register: 1},
|
|
&expr.Cmp{
|
|
Op: expr.CmpOpNeq,
|
|
Register: 1,
|
|
Data: binaryutil.BigEndian.PutUint32(r.tunOptions.IncludeUID[0].Start),
|
|
},
|
|
&expr.Counter{},
|
|
&expr.Verdict{
|
|
Kind: expr.VerdictReturn,
|
|
},
|
|
},
|
|
})
|
|
}
|
|
}
|
|
|
|
if len(r.tunOptions.ExcludeUID) > 0 {
|
|
if len(r.tunOptions.ExcludeUID) > 1 || r.tunOptions.ExcludeUID[0].Start != r.tunOptions.ExcludeUID[0].End {
|
|
excludeUID := &nftables.Set{
|
|
Table: table,
|
|
Anonymous: true,
|
|
Constant: true,
|
|
Interval: true,
|
|
KeyType: nftables.TypeUID,
|
|
}
|
|
err := nft.AddSet(excludeUID, common.FlatMap(r.tunOptions.ExcludeUID, func(it ranges.Range[uint32]) []nftables.SetElement {
|
|
return []nftables.SetElement{
|
|
{
|
|
Key: binaryutil.NativeEndian.PutUint32(it.Start),
|
|
},
|
|
{
|
|
Key: binaryutil.NativeEndian.PutUint32(it.End + 1),
|
|
IntervalEnd: true,
|
|
},
|
|
}
|
|
}))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
nft.AddRule(&nftables.Rule{
|
|
Table: table,
|
|
Chain: chain,
|
|
Exprs: []expr.Any{
|
|
&expr.Meta{Key: expr.MetaKeySKUID, Register: 1},
|
|
&expr.Lookup{
|
|
SourceRegister: 1,
|
|
SetID: excludeUID.ID,
|
|
SetName: excludeUID.Name,
|
|
},
|
|
&expr.Counter{},
|
|
&expr.Verdict{
|
|
Kind: expr.VerdictReturn,
|
|
},
|
|
},
|
|
UserData: userdata.AppendString(nil, userdata.TypeComment, "not a bug :("),
|
|
})
|
|
} else {
|
|
nft.AddRule(&nftables.Rule{
|
|
Table: table,
|
|
Chain: chain,
|
|
Exprs: []expr.Any{
|
|
&expr.Meta{Key: expr.MetaKeySKUID, Register: 1},
|
|
&expr.Cmp{
|
|
Op: expr.CmpOpEq,
|
|
Register: 1,
|
|
Data: binaryutil.NativeEndian.PutUint32(r.tunOptions.ExcludeUID[0].Start),
|
|
},
|
|
&expr.Counter{},
|
|
&expr.Verdict{
|
|
Kind: expr.VerdictReturn,
|
|
},
|
|
},
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
if len(r.tunOptions.Inet4RouteAddress) > 0 {
|
|
inet4RouteAddress, err := nftablesCreateIPSet(nft, table, 0, "", nftables.TableFamilyIPv4, nil, r.tunOptions.Inet4RouteAddress, false, false)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
nftablesCreateExcludeDestinationIPSet(nft, table, chain, inet4RouteAddress.ID, inet4RouteAddress.Name, nftables.TableFamilyIPv4, true)
|
|
}
|
|
|
|
if len(r.tunOptions.Inet6RouteAddress) > 0 {
|
|
inet6RouteAddress, err := nftablesCreateIPSet(nft, table, 0, "", nftables.TableFamilyIPv6, nil, r.tunOptions.Inet6RouteAddress, false, false)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
nftablesCreateExcludeDestinationIPSet(nft, table, chain, inet6RouteAddress.ID, inet6RouteAddress.Name, nftables.TableFamilyIPv6, true)
|
|
}
|
|
|
|
if len(r.tunOptions.Inet4RouteExcludeAddress) > 0 {
|
|
inet4RouteExcludeAddress, err := nftablesCreateIPSet(nft, table, 0, "", nftables.TableFamilyIPv4, nil, r.tunOptions.Inet4RouteExcludeAddress, false, false)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
nftablesCreateExcludeDestinationIPSet(nft, table, chain, inet4RouteExcludeAddress.ID, inet4RouteExcludeAddress.Name, nftables.TableFamilyIPv4, false)
|
|
}
|
|
|
|
if len(r.tunOptions.Inet6RouteExcludeAddress) > 0 {
|
|
inet6RouteExcludeAddress, err := nftablesCreateIPSet(nft, table, 0, "", nftables.TableFamilyIPv6, nil, r.tunOptions.Inet6RouteExcludeAddress, false, false)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
nftablesCreateExcludeDestinationIPSet(nft, table, chain, inet6RouteExcludeAddress.ID, inet6RouteExcludeAddress.Name, nftables.TableFamilyIPv6, false)
|
|
}
|
|
|
|
if !r.tunOptions.EXP_DisableDNSHijack && ((chain.Hooknum == nftables.ChainHookPrerouting && chain.Type == nftables.ChainTypeNAT) ||
|
|
(r.tunOptions.AutoRedirectMarkMode && chain.Hooknum == nftables.ChainHookOutput && chain.Type == nftables.ChainTypeNAT)) {
|
|
if r.enableIPv4 {
|
|
err := r.nftablesCreateDNSHijackRulesForFamily(nft, table, chain, nftables.TableFamilyIPv4)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if r.enableIPv6 {
|
|
err := r.nftablesCreateDNSHijackRulesForFamily(nft, table, chain, nftables.TableFamilyIPv6)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
|
|
if r.tunOptions.AutoRedirectMarkMode &&
|
|
((chain.Hooknum == nftables.ChainHookOutput && chain.Type == nftables.ChainTypeRoute) ||
|
|
(chain.Hooknum == nftables.ChainHookPrerouting && chain.Type == nftables.ChainTypeFilter)) {
|
|
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_UDP},
|
|
},
|
|
&expr.Verdict{
|
|
Kind: expr.VerdictReturn,
|
|
},
|
|
},
|
|
})
|
|
}
|
|
|
|
if r.enableIPv4 {
|
|
nftablesCreateExcludeDestinationIPSet(nft, table, chain, 5, "inet4_local_address_set", nftables.TableFamilyIPv4, false)
|
|
}
|
|
if r.enableIPv6 {
|
|
nftablesCreateExcludeDestinationIPSet(nft, table, chain, 6, "inet6_local_address_set", nftables.TableFamilyIPv6, false)
|
|
}
|
|
|
|
routeAddressSet := *r.routeAddressSet
|
|
routeExcludeAddressSet := *r.routeExcludeAddressSet
|
|
|
|
if r.enableIPv4 && len(routeAddressSet) > 0 {
|
|
nftablesCreateExcludeDestinationIPSet(nft, table, chain, 1, "inet4_route_address_set", nftables.TableFamilyIPv4, true)
|
|
}
|
|
|
|
if r.enableIPv6 && len(routeAddressSet) > 0 {
|
|
nftablesCreateExcludeDestinationIPSet(nft, table, chain, 2, "inet6_route_address_set", nftables.TableFamilyIPv6, true)
|
|
}
|
|
|
|
if r.enableIPv4 && len(routeExcludeAddressSet) > 0 {
|
|
nftablesCreateExcludeDestinationIPSet(nft, table, chain, 3, "inet4_route_exclude_address_set", nftables.TableFamilyIPv4, false)
|
|
}
|
|
|
|
if r.enableIPv6 && len(routeExcludeAddressSet) > 0 {
|
|
nftablesCreateExcludeDestinationIPSet(nft, table, chain, 4, "inet6_route_exclude_address_set", nftables.TableFamilyIPv6, false)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (r *autoRedirect) nftablesCreateMark(nft *nftables.Conn, table *nftables.Table, chain *nftables.Chain) {
|
|
nft.AddRule(&nftables.Rule{
|
|
Table: table,
|
|
Chain: chain,
|
|
Exprs: []expr.Any{
|
|
&expr.Immediate{
|
|
Register: 1,
|
|
Data: binaryutil.NativeEndian.PutUint32(r.tunOptions.AutoRedirectInputMark),
|
|
},
|
|
&expr.Meta{
|
|
Key: expr.MetaKeyMARK,
|
|
Register: 1,
|
|
SourceRegister: true,
|
|
},
|
|
&expr.Meta{
|
|
Key: expr.MetaKeyMARK,
|
|
Register: 1,
|
|
}, // output meta mark set myMark ct mark set meta mark
|
|
&expr.Ct{
|
|
Key: expr.CtKeyMARK,
|
|
Register: 1,
|
|
SourceRegister: true,
|
|
},
|
|
&expr.Counter{},
|
|
},
|
|
})
|
|
}
|
|
|
|
func (r *autoRedirect) nftablesCreateRedirect(
|
|
nft *nftables.Conn, table *nftables.Table, chain *nftables.Chain,
|
|
exprs ...expr.Any,
|
|
) {
|
|
if r.enableIPv4 && !r.enableIPv6 {
|
|
exprs = append(exprs,
|
|
&expr.Meta{
|
|
Key: expr.MetaKeyNFPROTO,
|
|
Register: 1,
|
|
},
|
|
&expr.Cmp{
|
|
Op: expr.CmpOpEq,
|
|
Register: 1,
|
|
Data: []byte{uint8(nftables.TableFamilyIPv4)},
|
|
})
|
|
} else if !r.enableIPv4 && r.enableIPv6 {
|
|
exprs = append(exprs,
|
|
&expr.Meta{
|
|
Key: expr.MetaKeyNFPROTO,
|
|
Register: 1,
|
|
},
|
|
&expr.Cmp{
|
|
Op: expr.CmpOpEq,
|
|
Register: 1,
|
|
Data: []byte{uint8(nftables.TableFamilyIPv6)},
|
|
})
|
|
}
|
|
nft.AddRule(&nftables.Rule{
|
|
Table: table,
|
|
Chain: chain,
|
|
Exprs: append(exprs,
|
|
&expr.Meta{
|
|
Key: expr.MetaKeyL4PROTO,
|
|
Register: 1,
|
|
},
|
|
&expr.Cmp{
|
|
Op: expr.CmpOpEq,
|
|
Register: 1,
|
|
Data: []byte{unix.IPPROTO_TCP},
|
|
},
|
|
&expr.Counter{},
|
|
&expr.Immediate{
|
|
Register: 1,
|
|
Data: binaryutil.BigEndian.PutUint16(r.redirectPort()),
|
|
},
|
|
&expr.Redir{
|
|
RegisterProtoMin: 1,
|
|
Flags: unix.NF_NAT_RANGE_PROTO_SPECIFIED,
|
|
},
|
|
&expr.Verdict{
|
|
Kind: expr.VerdictReturn,
|
|
},
|
|
),
|
|
})
|
|
}
|
|
|
|
func (r *autoRedirect) nftablesCreateDNSHijackRulesForFamily(
|
|
nft *nftables.Conn, table *nftables.Table, chain *nftables.Chain,
|
|
family nftables.TableFamily,
|
|
) error {
|
|
ipProto := &nftables.Set{
|
|
Table: table,
|
|
Anonymous: true,
|
|
Constant: true,
|
|
KeyType: nftables.TypeInetProto,
|
|
}
|
|
err := nft.AddSet(ipProto, []nftables.SetElement{
|
|
{Key: []byte{unix.IPPROTO_TCP}},
|
|
{Key: []byte{unix.IPPROTO_UDP}},
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
dnsServer := common.Find(r.tunOptions.DNSServers, func(it netip.Addr) bool {
|
|
return it.Is4() == (family == nftables.TableFamilyIPv4)
|
|
})
|
|
if !dnsServer.IsValid() {
|
|
if family == nftables.TableFamilyIPv4 {
|
|
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() {
|
|
return nil
|
|
}
|
|
exprs := []expr.Any{
|
|
&expr.Meta{
|
|
Key: expr.MetaKeyNFPROTO,
|
|
Register: 1,
|
|
},
|
|
&expr.Cmp{
|
|
Op: expr.CmpOpEq,
|
|
Register: 1,
|
|
Data: []byte{uint8(family)},
|
|
},
|
|
}
|
|
if chain.Hooknum == nftables.ChainHookOutput {
|
|
// It looks like we can't hijack DNS requests sent to loopback.
|
|
// https://serverfault.com/questions/363899/iptables-dnat-from-loopback
|
|
// and tproxy is not available in output
|
|
exprs = append(exprs,
|
|
&expr.Meta{
|
|
Key: expr.MetaKeyOIFNAME,
|
|
Register: 1,
|
|
},
|
|
&expr.Cmp{
|
|
Op: expr.CmpOpNeq,
|
|
Register: 1,
|
|
Data: nftablesIfname("lo"),
|
|
},
|
|
)
|
|
}
|
|
exprs = append(exprs,
|
|
&expr.Meta{
|
|
Key: expr.MetaKeyL4PROTO,
|
|
Register: 1,
|
|
},
|
|
&expr.Lookup{
|
|
SourceRegister: 1,
|
|
SetID: ipProto.ID,
|
|
SetName: ipProto.Name,
|
|
},
|
|
&expr.Payload{
|
|
OperationType: expr.PayloadLoad,
|
|
DestRegister: 1,
|
|
Base: expr.PayloadBaseTransportHeader,
|
|
Offset: 2,
|
|
Len: 2,
|
|
},
|
|
&expr.Cmp{
|
|
Op: expr.CmpOpEq,
|
|
Register: 1,
|
|
Data: binaryutil.BigEndian.PutUint16(53),
|
|
},
|
|
&expr.Immediate{
|
|
Register: 1,
|
|
Data: dnsServer.AsSlice(),
|
|
},
|
|
&expr.NAT{
|
|
Type: expr.NATTypeDestNAT,
|
|
Family: uint32(family),
|
|
RegAddrMin: 1,
|
|
},
|
|
&expr.Counter{},
|
|
)
|
|
nft.AddRule(&nftables.Rule{
|
|
Table: table,
|
|
Chain: chain,
|
|
Exprs: exprs,
|
|
})
|
|
return nil
|
|
}
|
|
|
|
func (r *autoRedirect) nftablesCreateUnreachable(
|
|
nft *nftables.Conn, table *nftables.Table, chain *nftables.Chain,
|
|
) {
|
|
if (r.enableIPv4 && r.enableIPv6) || !r.tunOptions.StrictRoute {
|
|
return
|
|
}
|
|
var nfProto nftables.TableFamily
|
|
if r.enableIPv4 {
|
|
nfProto = nftables.TableFamilyIPv6
|
|
} else {
|
|
nfProto = nftables.TableFamilyIPv4
|
|
}
|
|
nft.AddRule(&nftables.Rule{
|
|
Table: table,
|
|
Chain: chain,
|
|
Exprs: []expr.Any{
|
|
&expr.Meta{
|
|
Key: expr.MetaKeyNFPROTO,
|
|
Register: 1,
|
|
},
|
|
&expr.Cmp{
|
|
Op: expr.CmpOpEq,
|
|
Register: 1,
|
|
Data: []byte{uint8(nfProto)},
|
|
},
|
|
&expr.Counter{},
|
|
&expr.Verdict{
|
|
Kind: expr.VerdictDrop,
|
|
},
|
|
},
|
|
})
|
|
}
|