package nftables import ( "fmt" "github.com/google/nftables" "github.com/google/nftables/binaryutil" "github.com/google/nftables/expr" "golang.org/x/sys/unix" "os" "os/exec" ) func getNFTConn() nftables.Conn { return nftables.Conn{} } // EnsureKernelModules ensures that nft_tproxy kernel module is loaded. func EnsureKernelModules() error { // Check if directory /sys/module/nft_tproxy exists _, err := os.Stat("/sys/module/nft_tproxy") if err == nil { return nil } // If not, run "modprobe nft_tproxy" _, err = exec.Command("modprobe", "nft_tproxy").Output() if err != nil { return err } return nil } func ifname(n string) []byte { b := make([]byte, 16) copy(b, []byte(n+"\x00")) return b } // InsertProxyChain create a new nftables table with a single chain with a tproxy rule in it. // // We create a new chain with a custom priority to ensure that we can cleanly add and remove it // without affecting other rules or knowing the setup. // // Generates a chain that looks like this: // // table ip filter { // chain tpuproxy { // type filter hook prerouting priority -2147483648; policy accept; // iifname "enp6s0" udp dport { 8003, 8004, 8005 } tproxy to :51211 // } // } // func InsertProxyChain(destPorts []uint16, redirectPort uint16, iface string) error { c := getNFTConn() // Create a new table just in case the host isn't using nft. table := c.AddTable(&nftables.Table{ Name: "filter", Family: nftables.TableFamilyIPv4, }) // Create our own chain so we can cleanly remove it later. chain := c.AddChain(&nftables.Chain{ Name: "tpuproxy", Table: table, Type: nftables.ChainTypeFilter, Hooknum: nftables.ChainHookPrerouting, Priority: nftables.ChainPriorityFirst, }) c.FlushChain(chain) set := &nftables.Set{ Anonymous: true, Constant: true, Table: table, KeyType: nftables.TypeInetService, } elements := make([]nftables.SetElement, len(destPorts)) for i, port := range destPorts { elements[i] = nftables.SetElement{ Key: binaryutil.BigEndian.PutUint16(port), } } if err := c.AddSet(set, elements); err != nil { return fmt.Errorf("failed to add set: %v", err) } // Add a rule to the chain that matches on the destination port and redirects to the redirect port. c.AddRule(&nftables.Rule{ Table: table, Chain: chain, Exprs: []expr.Any{ // [ meta load iifname => reg 1 ] &expr.Meta{Key: expr.MetaKeyIIF, Register: 1}, // [ cmp eq reg 1 0x696c7075 0x00306b6e 0x00000000 0x00000000 ] &expr.Cmp{Op: expr.CmpOpNeq, Register: 1, Data: binaryutil.NativeEndian.PutUint32(uint32(1)) /* lo */}, // TODO // [ payload load 1b @ network header + 9 => reg 1 ] &expr.Payload{DestRegister: 1, Base: expr.PayloadBaseNetworkHeader, Offset: 9, Len: 1}, // [ cmp eq reg 1 ] &expr.Cmp{Op: expr.CmpOpEq, Register: 1, Data: []byte{unix.IPPROTO_UDP}}, // [ payload load 2b @ transport header + 2 => reg 1 ] &expr.Payload{DestRegister: 1, Base: expr.PayloadBaseTransportHeader, Offset: 2, Len: 2}, // [ lookup reg 1 set __set%d ] &expr.Lookup{SourceRegister: 1, SetName: set.Name, SetID: set.ID}, // [ immediate reg 1 ] &expr.Immediate{Register: 1, Data: binaryutil.BigEndian.PutUint16(redirectPort)}, // [ tproxy ip port reg 1 ] &expr.TProxy{ Family: byte(nftables.TableFamilyIPv4), TableFamily: byte(nftables.TableFamilyIPv4), RegPort: 1, }, }, }) if err := c.Flush(); err != nil { return err } return nil } func DeleteProxyChain() error { c := getNFTConn() // Delete the chain. chain := c.AddChain(&nftables.Chain{ Name: "tpuproxy", Table: &nftables.Table{ Name: "filter", Family: nftables.TableFamilyIPv4, }, }) c.DelChain(chain) if err := c.Flush(); err != nil { return err } return nil }