go-firewall/types_test.go
2026-08-10 17:17:03 -05:00

110 lines
4.2 KiB
Go

package firewall
import (
"encoding/json"
"testing"
"github.com/stretchr/testify/require"
)
// ParseICMPType resolves a numeric or named ICMP type, selecting the ICMPv6 name
// table when v6 is set so a name shared with ICMPv4 maps to its v6 number. It is
// the exported resolver the CLI relies on, so its name coverage must match the
// tables the backends emit.
func TestParseICMPType(t *testing.T) {
cases := []struct {
tok string
v6 bool
want uint8
ok bool
}{
{"8", false, 8, true}, // numeric parses in either family
{"255", true, 255, true}, // numbers are family-independent
{"echo-request", false, 8, true},
{"ECHO-REQUEST", false, 8, true}, // case-insensitive
{"echo-request", true, 128, true}, // same name, different v6 number
{"destination-unreachable", false, 3, true},
{"destination-unreachable", true, 1, true},
{"nd-neighbor-solicit", true, 135, true},
// info-request/info-reply live only in the v4 table; route them through
// here to prove they resolve.
{"info-request", false, 15, true},
{"info-reply", false, 16, true},
// A v4-only name is unknown under v6, and vice versa.
{"source-quench", true, 0, false},
{"packet-too-big", false, 0, false},
{"not-a-type", false, 0, false},
}
for _, c := range cases {
got, ok := ParseICMPType(c.tok, c.v6)
require.Equalf(t, c.ok, ok, "ParseICMPType(%q, v6=%v) ok", c.tok, c.v6)
if c.ok {
require.Equalf(t, c.want, got, "ParseICMPType(%q, v6=%v)", c.tok, c.v6)
}
}
}
// Enums render as their stable string name (not a bare number) and round-trip
// through encoding/json. A backup must stay readable and meaningful even if an
// iota constant is later reordered.
func TestEnumJSON(t *testing.T) {
cases := []struct {
name string
in any
want string // the quoted JSON string expected
}{
{"action-accept", Accept, `"accept"`},
{"action-drop", Drop, `"drop"`},
{"action-invalid", ActionInvalid, `"invalid"`},
{"family-v4", IPv4, `"ipv4"`},
{"family-v6", IPv6, `"ipv6"`},
{"family-any", FamilyAny, `"any"`},
{"proto-tcp", TCP, `"tcp"`},
{"proto-sctp", SCTP, `"sctp"`},
{"proto-any", ProtocolAny, `"any"`},
{"natkind", DNAT, `"dnat"`},
{"rateunit", PerMinute, `"minute"`},
{"direction", DirForward, `"forward"`},
{"settype", SetHashNet, `"hash:net"`},
{"connstate", ConnState(StateEstablished | StateRelated), `"established,related"`},
{"connstate-zero", ConnState(0), `""`},
}
for _, c := range cases {
out, err := json.Marshal(c.in)
require.NoError(t, err, c.name)
require.Equal(t, c.want, string(out), "%s: marshal", c.name)
}
// Round-trip each value through marshal -> unmarshal.
roundTrips := []struct {
name string
mk func() any // fresh addressable value to unmarshal into
eq func(any) bool // reports whether it equals the marshal source
}{
{"action", func() any { var v Action; return &v }, func(g any) bool { return *g.(*Action) == Accept }},
{"family", func() any { var v Family; return &v }, func(g any) bool { return *g.(*Family) == IPv4 }},
{"proto", func() any { var v Protocol; return &v }, func(g any) bool { return *g.(*Protocol) == TCP }},
{"connstate", func() any { var v ConnState; return &v }, func(g any) bool { return *g.(*ConnState) == (StateNew | StateEstablished) }},
{"rateunit", func() any { var v RateUnit; return &v }, func(g any) bool { return *g.(*RateUnit) == PerHour }},
{"natkind", func() any { var v NATKind; return &v }, func(g any) bool { return *g.(*NATKind) == Masquerade }},
{"direction", func() any { var v Direction; return &v }, func(g any) bool { return *g.(*Direction) == DirOutput }},
{"settype", func() any { var v SetType; return &v }, func(g any) bool { return *g.(*SetType) == SetHashIP }},
}
marshalVals := map[string]any{
"action": Accept,
"family": IPv4,
"proto": TCP,
"connstate": ConnState(StateNew | StateEstablished),
"rateunit": PerHour,
"natkind": Masquerade,
"direction": DirOutput,
"settype": SetHashIP,
}
for _, rt := range roundTrips {
data, err := json.Marshal(marshalVals[rt.name])
require.NoError(t, err, rt.name)
dst := rt.mk()
require.NoError(t, json.Unmarshal(data, dst), "%s: unmarshal %s", rt.name, data)
require.True(t, rt.eq(dst), "%s: round-trip mismatch (got %+v)", rt.name, dst)
}
}