UA3F/src/internal/server/netlink/nftables.go
2025-11-29 18:09:15 +08:00

190 lines
4.4 KiB
Go

//go:build linux
package netlink
import (
"context"
"fmt"
"log/slog"
"github.com/sunbk201/ua3f/internal/netfilter"
"sigs.k8s.io/knftables"
)
func (s *Server) nftSetup() error {
if !s.cfg.SetTTL && !s.cfg.DelTCPTimestamp && !s.cfg.SetIPID {
slog.Info("No packet modification features enabled, skipping nftables setup")
return nil
}
nft, err := knftables.New(s.Nftable.Family, s.Nftable.Name)
if err != nil {
return err
}
tx := nft.NewTransaction()
tx.Add(s.Nftable)
if s.cfg.SetTTL {
s.NftSetTTL(tx, s.Nftable)
}
if (s.cfg.DelTCPTimestamp || s.cfg.SetTCPInitialWindow) && !s.cfg.SetIPID {
s.NftHookTCPSyn(tx, s.Nftable)
}
if s.cfg.SetIPID {
s.NftSetIP(tx, s.Nftable)
}
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
}
func (s *Server) nftCleanup() error {
nft, err := knftables.New(s.Nftable.Family, s.Nftable.Name)
if err != nil {
return err
}
tx := nft.NewTransaction()
tx.Delete(s.Nftable)
if err := nft.Run(context.TODO(), tx); err != nil {
return err
}
return nil
}
func (s *Server) NftSetTTL(tx *knftables.Transaction, table *knftables.Table) {
chain := &knftables.Chain{
Name: "TTL64",
Table: table.Name,
Type: knftables.PtrTo(knftables.FilterType),
Hook: knftables.PtrTo(knftables.PostroutingHook),
Priority: knftables.PtrTo(knftables.ManglePriority),
}
rule := &knftables.Rule{
Chain: chain.Name,
Rule: knftables.Concat(
"ip ttl set 64",
),
}
tx.Add(chain)
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) NftHookTCPSyn(tx *knftables.Transaction, table *knftables.Table) {
chain := &knftables.Chain{
Name: "HOOK_TCP_SYN",
Table: table.Name,
Type: knftables.PtrTo(knftables.FilterType),
Hook: knftables.PtrTo(knftables.PostroutingHook),
Priority: knftables.PtrTo(knftables.ManglePriority),
}
tx.Add(chain)
var rule *knftables.Rule
rule = &knftables.Rule{
Chain: chain.Name,
Rule: knftables.Concat(
"tcp flags syn",
fmt.Sprintf("counter queue num %d bypass", s.nfqServer.QueueNum),
),
}
tx.Add(rule)
}
func (s *Server) NftSetIP(tx *knftables.Transaction, table *knftables.Table) {
chain := &knftables.Chain{
Name: "HELPER_QUEUE",
Table: table.Name,
Type: knftables.PtrTo(knftables.FilterType),
Hook: knftables.PtrTo(knftables.PostroutingHook),
Priority: knftables.PtrTo(knftables.ManglePriority),
}
rule := &knftables.Rule{
Chain: chain.Name,
Rule: knftables.Concat(
"meta l4proto tcp",
fmt.Sprintf("counter queue num %d bypass", s.nfqServer.QueueNum),
),
}
tx.Add(chain)
tx.Add(rule)
}
// unused currently
func ResetOptionAvailable() bool {
const TestName = "UA3F_TEST_RESET"
table := &knftables.Table{
Name: TestName,
Family: knftables.InetFamily,
}
nft, err := knftables.New(table.Family, table.Name)
if err != nil {
slog.Error("ResetOptionAvailable knftables.New", slog.Any("error", err))
return false
}
tx := nft.NewTransaction()
chain := &knftables.Chain{
Name: TestName,
Table: table.Name,
Type: knftables.PtrTo(knftables.FilterType),
Hook: knftables.PtrTo(knftables.PostroutingHook),
Priority: knftables.PtrTo(knftables.ManglePriority),
}
rule := &knftables.Rule{
Chain: chain.Name,
Rule: knftables.Concat(
"tcp option timestamp exists",
"counter reset tcp option timestamp",
),
}
tx.Add(table)
tx.Add(chain)
tx.Add(rule)
err = nft.Check(context.TODO(), tx)
if err != nil {
slog.Info("tcp option reset is not available")
}
return err == nil
}