package render import ( "regexp" "strings" "testing" "housecarl/internal/model" ) // TestShadow: in shadow mode nothing in the ruleset drops, rejects, // translates or changes a packet, and what would is counted and marked. func TestShadow(t *testing.T) { st := router() st.Rules = append(st.Rules, &model.Rule{ID: 950, Chain: model.ChainInput, Position: 99, Raw: `tcp dport 9999 counter reject with tcp reset comment "old"`}, &model.Rule{ID: 951, Chain: model.ChainForward, Position: 99, Raw: "meta mark set 0x1 accept"}) st.Settings.OutputPolicy = model.PolicyDrop text := Render(st, Options{Shadow: true}).Text // What acts on packets: verdicts, NAT, changes, the flowtable. acts := regexp.MustCompile(`(?m)(\b(drop|reject|dnat|snat|masquerade|redirect)\b|maxseg size set|flow add|flowtable|policy drop)[^"\n]*$`) for _, l := range strings.Split(text, "\n") { code, _, _ := strings.Cut(l, "comment ") code = strings.ReplaceAll(code, "ct status dnat", "") // a match if strings.HasPrefix(strings.TrimSpace(l), "#") { continue } if m := acts.FindString(code); m != "" && !strings.Contains(code, "log prefix") { t.Errorf("acts on packets in shadow mode: %s", l) } } for _, want := range []string{ "type filter hook input priority filter + 10; policy accept;", "type filter hook prerouting priority raw + 10; policy accept;", "type nat hook prerouting priority dstnat - 10; policy accept;", "type nat hook postrouting priority srcnat - 10; policy accept;", `ct state invalid counter accept comment "hc:b-invalid [would drop]"`, `counter comment "hc:p-input [would drop]"`, `counter comment "hc:p-output [would drop]"`, `log prefix "hc output policy drop: "`, `[would reject]`, `[would dnat] jellyfin`, `[would masquerade]`, `[would redirect]`, `tcp dport 9999 counter accept comment "[would reject] old"`, "# left out in shadow mode: meta mark set 0x1 accept", "# Shadow mode:", } { if !strings.Contains(text, want) { t.Errorf("no %q in\n%s", want, text) } } before := Render(st, Options{Shadow: true, ShadowBefore: true}).Text for _, want := range []string{"priority filter - 10;", "priority raw - 10;", "priority dstnat - 10;"} { if !strings.Contains(before, want) { t.Errorf("before: no %q", want) } } // Enforcing is unchanged by it. if strings.Contains(Render(st, Options{}).Text, "would") { t.Error("shadow markers when enforcing") } if got := ShadowLeftOut(st); len(got) != 1 || got[0].ID != 951 { t.Errorf("left out: %v", got) } } func TestShadowRaw(t *testing.T) { cases := []struct{ in, out string }{ {"meta skuid 0 counter accept", "meta skuid 0 counter accept"}, {"tcp dport 22 drop", `tcp dport 22 accept comment "[would drop]"`}, {`tcp dport 22 counter drop comment "no ssh"`, `tcp dport 22 counter accept comment "[would drop] no ssh"`}, {"ip saddr 10.0.0.1 reject with icmpx type admin-prohibited", `ip saddr 10.0.0.1 accept comment "[would reject]"`}, {"tcp dport 22 reject with tcp reset", `tcp dport 22 accept comment "[would reject]"`}, {`log prefix "drop me: " group 5 accept`, `log prefix "drop me: " group 5 accept`}, {"ip saddr { 10.0.0.1, 10.0.0.2 } counter", "ip saddr { 10.0.0.1, 10.0.0.2 } counter"}, {"tcp dport vmap { 22 : drop, 80 : accept }", ""}, {"meta mark set 1", ""}, {"ip daddr 1.2.3.4 dnat ip to 10.0.0.1", ""}, {"tcp dport 22 jump other", ""}, {"drop accept", ""}, {"meta nftrace set 1", ""}, {"ct state new notrack", ""}, } for _, c := range cases { out, ok := ShadowRaw(c.in) if ok != (c.out != "") || out != c.out { t.Errorf("%q: got %q %v, want %q", c.in, out, ok, c.out) } } }