feat(iprule): manage policy-routing rules and tables

This commit is contained in:
ahp
2026-10-15 14:29:19 +03:30
parent 83871815c2
commit 181fe69480
2 changed files with 426 additions and 0 deletions
+115
View File
@@ -0,0 +1,115 @@
// SPDX-License-Identifier: MIT
package iprule
import (
"context"
"strings"
"testing"
"prx/internal/execx"
)
func TestParseRules(t *testing.T) {
out := `0: from all lookup local
50: from all to 198.51.100.0/24 lookup 102
100: from all fwmark 0x100 lookup 100
32766: from all lookup main
32767: from all lookup default
`
entries := parseRules(out)
if len(entries) != 5 {
t.Fatalf("expected 5 entries, got %d", len(entries))
}
if !entries[2].HasMark || entries[2].Mark != 0x100 || entries[2].Table != 100 || entries[2].Priority != 100 {
t.Errorf("mark entry parsed wrong: %+v", entries[2])
}
if !entries[1].HasTo || entries[1].To != "198.51.100.0/24" || entries[1].Table != 102 {
t.Errorf("bypass entry parsed wrong: %+v", entries[1])
}
}
func ruleRunner(out string) (*execx.Fake, *[]execx.Call) {
f := &execx.Fake{Handler: func(name string, args []string, _ []byte) (execx.Result, error) {
if len(args) >= 2 && args[len(args)-2] == "rule" && args[len(args)-1] == "show" {
return execx.Result{Stdout: []byte(out)}, nil
}
return execx.Result{}, nil
}}
return f, &f.Calls
}
func TestRuleExists(t *testing.T) {
f, _ := ruleRunner("100:\tfrom all fwmark 0x100 lookup 100\n")
ok, err := RuleExists(context.Background(), f, Spec{Family: 4, Mark: 0x100, Table: 100, Priority: 100})
if err != nil || !ok {
t.Fatalf("RuleExists = %v, %v", ok, err)
}
ok, err = RuleExists(context.Background(), f, Spec{Family: 4, Mark: 0x100, Table: 100, Priority: 200})
if err != nil || ok {
t.Fatalf("expected absent, got %v, %v", ok, err)
}
}
func TestEnsureRuleAddsWhenMissing(t *testing.T) {
f, calls := ruleRunner("")
changed, err := EnsureRule(context.Background(), f, Spec{Family: 4, Mark: 0x100, Table: 100, Priority: 100})
if err != nil || !changed {
t.Fatalf("EnsureRule = %v, %v", changed, err)
}
if !hasCall(*calls, "ip", "rule", "add", "fwmark", "0x100", "lookup", "100", "priority", "100") {
t.Fatalf("expected add call, got %+v", *calls)
}
}
func TestEnsureRuleNoopWhenPresent(t *testing.T) {
f, calls := ruleRunner("100:\tfrom all fwmark 0x100 lookup 100\n")
changed, err := EnsureRule(context.Background(), f, Spec{Family: 4, Mark: 0x100, Table: 100, Priority: 100})
if err != nil || changed {
t.Fatalf("expected no-op, got %v, %v", changed, err)
}
for _, c := range *calls {
if len(c.Args) >= 2 && c.Args[1] == "add" {
t.Fatalf("unexpected add call: %+v", c)
}
}
}
func TestRemoveRulesByTable(t *testing.T) {
out := `50: from all to 198.51.100.0/24 lookup 102
100: from all fwmark 0x100 lookup 100
110: from all fwmark 0x100 lookup 100
`
f, calls := ruleRunner(out)
n, err := RemoveRulesByTable(context.Background(), f, 4, 100)
if err != nil {
t.Fatalf("RemoveRulesByTable: %v", err)
}
if n != 2 {
t.Fatalf("removed %d, want 2", n)
}
for _, c := range *calls {
if len(c.Args) >= 2 && c.Args[1] == "del" && !strings.Contains(strings.Join(c.Args, " "), "lookup 100") {
t.Fatalf("deleted wrong rule: %+v", c)
}
}
}
func hasCall(calls []execx.Call, name string, args ...string) bool {
for _, c := range calls {
if c.Name != name || len(c.Args) != len(args) {
continue
}
match := true
for i := range args {
if c.Args[i] != args[i] {
match = false
break
}
}
if match {
return true
}
}
return false
}