mirror of
https://github.com/qdm12/gluetun.git
synced 2026-08-11 14:52:56 +02:00
wip ai
This commit is contained in:
@@ -0,0 +1,166 @@
|
||||
package nftables
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/google/nftables"
|
||||
"github.com/google/nftables/expr"
|
||||
)
|
||||
|
||||
// RedirectPort redirects incoming traffic on the specified source port to the
|
||||
// specified destination port, for both TCP and UDP protocols, on the interface intf.
|
||||
// If intf is empty or "*", the interface is not used as a filter. If remove is true,
|
||||
// the redirection is removed instead of added. This is used for VPN server side
|
||||
// port forwarding, with intf set to the VPN tunnel interface.
|
||||
func (f *Firewall) RedirectPort(_ context.Context, intf string,
|
||||
sourcePort, destinationPort uint16, remove bool,
|
||||
) (err error) {
|
||||
f.mutex.Lock()
|
||||
defer f.mutex.Unlock()
|
||||
|
||||
conn, err := nftables.New()
|
||||
if err != nil {
|
||||
return fmt.Errorf("creating nftables connection: %w", err)
|
||||
}
|
||||
|
||||
table, inputChain, _, _ := setupFilterWithBaseChains(conn)
|
||||
|
||||
natTable := conn.AddTable(&nftables.Table{
|
||||
Family: nftables.TableFamilyINet,
|
||||
Name: "nat",
|
||||
})
|
||||
|
||||
preroutingChain := conn.AddChain(&nftables.Chain{
|
||||
Name: "prerouting",
|
||||
Table: natTable,
|
||||
Type: nftables.ChainTypeNAT,
|
||||
Hooknum: nftables.ChainHookPrerouting,
|
||||
Priority: nftables.ChainPriorityNATDest,
|
||||
})
|
||||
|
||||
sourcePortBytes := []byte{byte(sourcePort >> 8), byte(sourcePort)} //nolint:mnd
|
||||
destinationPortBytes := []byte{byte(destinationPort >> 8), byte(destinationPort)} //nolint:mnd
|
||||
const tcp, udp uint8 = 6, 17 //nolint:mnd
|
||||
protocols := []uint8{tcp, udp}
|
||||
|
||||
var rulesToDelete []*nftables.Rule
|
||||
|
||||
for _, protocol := range protocols {
|
||||
prerouteRule := buildRedirectRule(conn, natTable, preroutingChain,
|
||||
intf, protocol, sourcePortBytes, destinationPort)
|
||||
|
||||
if !remove {
|
||||
conn.AddRule(prerouteRule)
|
||||
f.rules = append(f.rules, prerouteRule)
|
||||
} else {
|
||||
err = f.deleteRule(conn, prerouteRule)
|
||||
if err != nil {
|
||||
rulesToDelete = append(rulesToDelete, prerouteRule)
|
||||
}
|
||||
}
|
||||
|
||||
inputRule := buildRedirectInputRule(table, inputChain,
|
||||
intf, protocol, destinationPortBytes)
|
||||
|
||||
if !remove {
|
||||
conn.AddRule(inputRule)
|
||||
f.rules = append(f.rules, inputRule)
|
||||
} else {
|
||||
err = f.deleteRule(conn, inputRule)
|
||||
if err != nil {
|
||||
rulesToDelete = append(rulesToDelete, inputRule)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
err = conn.Flush()
|
||||
if err != nil && !isTableDoesNotExist(err) {
|
||||
if !remove {
|
||||
removeFailedRules(f.rules, rulesToDelete)
|
||||
}
|
||||
return fmt.Errorf("redirecting source port %d to destination port %d on interface %s: %w",
|
||||
sourcePort, destinationPort, intf, err)
|
||||
}
|
||||
|
||||
if isTableDoesNotExist(err) && !remove {
|
||||
f.logger.Warnf("IPv6 port redirection disabled because your kernel does not support IPv6 NAT: %s", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func buildRedirectRule(_ *nftables.Conn, natTable *nftables.Table,
|
||||
preroutingChain *nftables.Chain, intf string, protocol uint8,
|
||||
sourcePortBytes []byte, destinationPort uint16,
|
||||
) *nftables.Rule {
|
||||
const regProto uint32 = 2
|
||||
portReg := make([]byte, regProto)
|
||||
binary.BigEndian.PutUint16(portReg, destinationPort)
|
||||
|
||||
exprs := buildRedirectMatchExprs(intf, protocol, sourcePortBytes)
|
||||
exprs = append(exprs,
|
||||
&expr.Immediate{Register: regProto, Data: portReg},
|
||||
&expr.NAT{
|
||||
Type: expr.NATTypeDestNAT,
|
||||
Family: uint32(nftables.TableFamilyINet),
|
||||
RegProtoMin: regProto,
|
||||
RegProtoMax: regProto,
|
||||
},
|
||||
)
|
||||
|
||||
return &nftables.Rule{
|
||||
Table: natTable,
|
||||
Chain: preroutingChain,
|
||||
Exprs: exprs,
|
||||
}
|
||||
}
|
||||
|
||||
func buildRedirectInputRule(table *nftables.Table, inputChain *nftables.Chain,
|
||||
intf string, protocol uint8, destinationPortBytes []byte,
|
||||
) *nftables.Rule {
|
||||
exprs := buildRedirectMatchExprs(intf, protocol, destinationPortBytes)
|
||||
exprs = append(exprs, &expr.Verdict{Kind: expr.VerdictAccept})
|
||||
|
||||
return &nftables.Rule{
|
||||
Table: table,
|
||||
Chain: inputChain,
|
||||
Exprs: exprs,
|
||||
}
|
||||
}
|
||||
|
||||
func buildRedirectMatchExprs(intf string, protocol uint8, portBytes []byte) []expr.Any {
|
||||
const maxExprsLen = 6
|
||||
exprs := make([]expr.Any, 0, maxExprsLen)
|
||||
|
||||
if intf != "" && intf != "*" {
|
||||
exprs = append(exprs,
|
||||
&expr.Meta{Key: expr.MetaKeyIIFNAME, Register: 1},
|
||||
&expr.Cmp{Op: expr.CmpOpEq, Register: 1, Data: []byte(intf + "\x00")},
|
||||
)
|
||||
}
|
||||
|
||||
exprs = append(exprs,
|
||||
&expr.Payload{DestRegister: 1, Base: expr.PayloadBaseNetworkHeader, Offset: 9, Len: 1}, //nolint:mnd
|
||||
&expr.Cmp{Op: expr.CmpOpEq, Register: 1, Data: []byte{protocol}},
|
||||
&expr.Payload{DestRegister: 1, Base: expr.PayloadBaseTransportHeader, Offset: 2, Len: 2}, //nolint:mnd
|
||||
&expr.Cmp{Op: expr.CmpOpEq, Register: 1, Data: portBytes},
|
||||
)
|
||||
|
||||
return exprs
|
||||
}
|
||||
|
||||
func isTableDoesNotExist(err error) bool {
|
||||
return strings.Contains(err.Error(), "Table does not exist")
|
||||
}
|
||||
|
||||
func removeFailedRules(rules []*nftables.Rule, failed []*nftables.Rule) {
|
||||
for i := len(rules) - 1; i >= 0; i-- {
|
||||
if slices.Contains(failed, rules[i]) {
|
||||
rules = append(rules[:i], rules[i+1:]...)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user