fix: prevent flow offload hijack packet

This commit is contained in:
SunBK201 2025-11-25 02:35:51 +08:00
parent 973a33432a
commit 8abb282547
3 changed files with 143 additions and 3 deletions

View File

@ -9,7 +9,9 @@ import (
"net"
"os/exec"
"os/user"
"strings"
"github.com/coreos/go-iptables/iptables"
"github.com/gonetx/ipset"
"github.com/sunbk201/ua3f/internal/config"
"sigs.k8s.io/knftables"
@ -255,8 +257,8 @@ func (f *Firewall) DeleteTproxyRoute(fwmark, routeTable string) error {
}
func detectFirewallBackend(cfg *config.Config) string {
nftAvailable := isCommandAvailable("nft")
iptAvailable := isCommandAvailable("iptables")
nftAvailable := IsCommandAvailable("nft")
iptAvailable := IsCommandAvailable("iptables")
nftTproxyAvailable := isOpkgPackageInstalled("kmod-nft-tproxy") && nftAvailable
nftNfqueueAvailable := isOpkgPackageInstalled("kmod-nft-queue") && nftAvailable
tproxyNeeded := cfg.ServerMode == config.ServerModeTProxy
@ -334,7 +336,7 @@ func getLocalIPv4CIDRs() ([]string, error) {
return cidrs, nil
}
func isCommandAvailable(cmd string) bool {
func IsCommandAvailable(cmd string) bool {
_, err := exec.LookPath(cmd)
return err == nil
}
@ -430,3 +432,67 @@ func initLanCidrs() {
}
LAN_CIDRS = append(LAN_CIDRS, localCIDRs...)
}
func GetLanDevice() (string, error) {
out, err := exec.Command("ubus", "call", "network.interface.lan", "status").Output()
if err != nil {
return "", err
}
var lanInterface struct {
Device string `json:"device"`
}
if err := json.Unmarshal(out, &lanInterface); err != nil {
return "", err
}
if lanInterface.Device == "" {
return "", errors.New("no device found for lan interface")
}
// get real device if it's a bridge
out, err = exec.Command("ubus", "call", "network.device", "status").Output()
if err != nil {
return "", err
}
var devices map[string]struct {
Type string `json:"type"`
Bridges []string `json:"bridge-members"`
}
if err := json.Unmarshal(out, &devices); err != nil {
return "", err
}
dev, ok := devices[lanInterface.Device]
if !ok {
return "", fmt.Errorf("device %s not found", lanInterface.Device)
}
if dev.Type != "bridge" {
return lanInterface.Device, nil
}
if len(dev.Bridges) == 0 {
return "", fmt.Errorf("bridge %s has no members", lanInterface.Device)
}
return dev.Bridges[0], nil
}
func FlowOffloadEnabled() bool {
cmd := exec.Command("nft", "list", "chain", string(knftables.InetFamily), "fw4", "forward")
if output, err := cmd.CombinedOutput(); err == nil {
if strings.Contains(string(output), "flow add") {
return true
}
}
ipt, err := iptables.New()
if err != nil {
return false
}
rules, err := ipt.List("filter", "forward")
if err != nil {
return false
}
for _, rule := range rules {
if strings.Contains(rule, "FLOWOFFLOAD") {
return true
}
}
return false
}

View File

@ -3,10 +3,13 @@
package netlink
import (
"context"
"errors"
"strconv"
"github.com/coreos/go-iptables/iptables"
"github.com/sunbk201/ua3f/internal/netfilter"
"sigs.k8s.io/knftables"
)
const (
@ -51,6 +54,12 @@ func (s *Server) iptSetup() error {
if err != nil {
return err
}
if netfilter.FlowOffloadEnabled() {
err = s.IptSetTTLIngress(ipt)
if err != nil {
return err
}
}
}
if s.cfg.DelTCPTimestamp && !s.cfg.SetIPID {
err = s.IptDelTCPTS(ipt)
@ -75,6 +84,9 @@ func (s *Server) iptCleanup() error {
_ = ipt.DeleteIfExists(table, chain, RuleTTL...)
_ = ipt.DeleteIfExists(table, chain, RuleIP...)
_ = ipt.DeleteIfExists(table, chain, RuleDelTCPTS...)
if s.cfg.SetTTL {
_ = s.NftCleanup()
}
return nil
}
@ -101,3 +113,26 @@ func (s *Server) IptSetIP(ipt *iptables.IPTables) error {
}
return nil
}
func (s *Server) IptSetTTLIngress(ipt *iptables.IPTables) error {
if !netfilter.IsCommandAvailable("nft") {
return errors.New("nft command not available")
}
nft, err := knftables.New(s.Nftable.Family, s.Nftable.Name)
if err != nil {
return err
}
tx := nft.NewTransaction()
tx.Add(s.Nftable)
if err := nft.Run(context.TODO(), tx); err != nil {
return err
}
lanDev, err := netfilter.GetLanDevice()
if err != nil {
return err
}
return s.NftSetTTLIngress(nft, s.Nftable, lanDev)
}

View File

@ -7,6 +7,7 @@ import (
"fmt"
"log/slog"
"github.com/sunbk201/ua3f/internal/netfilter"
"sigs.k8s.io/knftables"
)
@ -37,6 +38,18 @@ func (s *Server) nftSetup() error {
if err := nft.Run(context.TODO(), tx); err != nil {
return err
}
if s.cfg.SetTTL && netfilter.FlowOffloadEnabled() {
lanDev, err := netfilter.GetLanDevice()
if err != nil {
slog.Warn("nftSetup netfilter.GetLanDevice", slog.Any("error", err))
} else {
err = s.NftSetTTLIngress(nft, s.Nftable, lanDev)
if err != nil {
slog.Warn("NftSetTTLIngress", slog.Any("error", err))
}
}
}
return nil
}
@ -73,6 +86,32 @@ func (s *Server) NftSetTTL(tx *knftables.Transaction, table *knftables.Table) {
tx.Add(rule)
}
func (s *Server) NftSetTTLIngress(nft knftables.Interface, table *knftables.Table, device string) error {
tx := nft.NewTransaction()
chain := &knftables.Chain{
Name: "TTL64_INGRESS",
Table: table.Name,
Type: knftables.PtrTo(knftables.FilterType),
Hook: knftables.PtrTo(knftables.IngressHook),
Priority: knftables.PtrTo(knftables.ManglePriority),
Device: knftables.PtrTo(device),
}
rule := &knftables.Rule{
Chain: chain.Name,
Rule: knftables.Concat(
"ip ttl set 65",
),
}
tx.Add(chain)
tx.Add(rule)
if err := nft.Run(context.TODO(), tx); err != nil {
return err
}
return nil
}
func (s *Server) NftDelTCPTS(tx *knftables.Transaction, table *knftables.Table) {
chain := &knftables.Chain{
Name: "DEL_TCPTS",