200 lines
5.7 KiB
Go
200 lines
5.7 KiB
Go
// SPDX-License-Identifier: MIT
|
|||
|
|
|
||
|
|
package config
|
||
|
|
|
||
|
|
import (
|
||
|
|
"path/filepath"
|
||
|
|
"testing"
|
||
|
|
)
|
||
|
|
|
||
|
|
func TestDefaultIsValid(t *testing.T) {
|
||
|
|
if err := Default().Validate(); err != nil {
|
||
|
|
t.Fatalf("default config invalid: %v", err)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestParseCIDR(t *testing.T) {
|
||
|
|
tests := []struct {
|
||
|
|
in string
|
||
|
|
want string
|
||
|
|
bad bool
|
||
|
|
}{
|
||
|
|
{"192.168.1.0/24", "192.168.1.0/24", false},
|
||
|
|
{"1.2.3.4", "1.2.3.4/32", false},
|
||
|
|
{"2001:db8::/32", "2001:db8::/32", false},
|
||
|
|
{"::1", "::1/128", false},
|
||
|
|
{"10.0.0.0/8", "10.0.0.0/8", false},
|
||
|
|
{"not-an-ip", "", true},
|
||
|
|
{"", "", true},
|
||
|
|
}
|
||
|
|
for _, tt := range tests {
|
||
|
|
got, err := ParseCIDR(tt.in)
|
||
|
|
if tt.bad {
|
||
|
|
if err == nil {
|
||
|
|
t.Errorf("ParseCIDR(%q): expected error", tt.in)
|
||
|
|
}
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
if err != nil {
|
||
|
|
t.Errorf("ParseCIDR(%q): %v", tt.in, err)
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
if got != tt.want {
|
||
|
|
t.Errorf("ParseCIDR(%q) = %q, want %q", tt.in, got, tt.want)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestAddRemoveExtraCIDR(t *testing.T) {
|
||
|
|
c := Default()
|
||
|
|
changed, err := c.AddExtraCIDR("203.0.113.7")
|
||
|
|
if err != nil || !changed {
|
||
|
|
t.Fatalf("AddExtraCIDR: changed=%v err=%v", changed, err)
|
||
|
|
}
|
||
|
|
changed, err = c.AddExtraCIDR("203.0.113.7/32")
|
||
|
|
if err != nil || changed {
|
||
|
|
t.Fatalf("duplicate should be a no-op: changed=%v err=%v", changed, err)
|
||
|
|
}
|
||
|
|
if _, err := c.AddExtraCIDR("0.0.0.0/0"); err == nil {
|
||
|
|
t.Fatal("expected refusal to bypass default route")
|
||
|
|
}
|
||
|
|
changed, err = c.RemoveExtraCIDR("203.0.113.7")
|
||
|
|
if err != nil || !changed {
|
||
|
|
t.Fatalf("RemoveExtraCIDR: changed=%v err=%v", changed, err)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestValidateRejectsBadValues(t *testing.T) {
|
||
|
|
cases := map[string]func(*Config){
|
||
|
|
"zero mark": func(c *Config) { c.Marks.InboundMark = 0 },
|
||
|
|
"reserved table": func(c *Config) { c.Routing.TableV4 = 254 },
|
||
|
|
"bad priority": func(c *Config) { c.Routing.PriorityV4 = 70000 },
|
||
|
|
"bad cidr": func(c *Config) { c.Bypass.ExtraCIDRs = []string{"nope"} },
|
||
|
|
"relative state": func(c *Config) { c.State.Path = "state.json" },
|
||
|
|
"empty type": func(c *Config) { c.Engine.Type = "" },
|
||
|
|
"bad service": func(c *Config) { c.Engine.ServiceName = "bad name!" },
|
||
|
|
"bad wan iface": func(c *Config) { c.Network.WANInterface = "bad iface" },
|
||
|
|
"bad gateway": func(c *Config) { c.Network.WANGateway = "nope" },
|
||
|
|
"relative engine": func(c *Config) { c.Engine.ConfigPath = "rel.json" },
|
||
|
|
}
|
||
|
|
for name, mutate := range cases {
|
||
|
|
c := Default()
|
||
|
|
mutate(c)
|
||
|
|
if err := c.Validate(); err == nil {
|
||
|
|
t.Errorf("case %q: expected validation error", name)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestSaveLoadRoundTrip(t *testing.T) {
|
||
|
|
dir := t.TempDir()
|
||
|
|
path := filepath.Join(dir, "prx", "config.json")
|
||
|
|
c := Default()
|
||
|
|
if changed, _ := c.AddExtraCIDR("198.51.100.0/24"); !changed {
|
||
|
|
t.Fatal("expected change")
|
||
|
|
}
|
||
|
|
if err := Save(path, c); err != nil {
|
||
|
|
t.Fatalf("Save: %v", err)
|
||
|
|
}
|
||
|
|
got, err := Load(path)
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("Load: %v", err)
|
||
|
|
}
|
||
|
|
if len(got.Bypass.ExtraCIDRs) != 1 || got.Bypass.ExtraCIDRs[0] != "198.51.100.0/24" {
|
||
|
|
t.Fatalf("unexpected extra cidrs: %+v", got.Bypass.ExtraCIDRs)
|
||
|
|
}
|
||
|
|
if got.Marks.InboundMark != DefaultMark {
|
||
|
|
t.Fatalf("mark not preserved: %x", got.Marks.InboundMark)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestLoadMissingReturnsDefault(t *testing.T) {
|
||
|
|
got, err := Load(filepath.Join(t.TempDir(), "absent.json"))
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("Load: %v", err)
|
||
|
|
}
|
||
|
|
if got.Engine.Type != "sing-box" {
|
||
|
|
t.Fatalf("expected default engine, got %q", got.Engine.Type)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestAddDomainValidatesAndDedupes(t *testing.T) {
|
||
|
|
c := Default()
|
||
|
|
if _, err := c.AddDomain("not a domain"); err == nil {
|
||
|
|
t.Fatal("expected error for invalid domain")
|
||
|
|
}
|
||
|
|
if _, err := c.AddDomain(""); err == nil {
|
||
|
|
t.Fatal("expected error for empty domain")
|
||
|
|
}
|
||
|
|
changed, err := c.AddDomain("Example.com")
|
||
|
|
if err != nil || !changed {
|
||
|
|
t.Fatalf("AddDomain: changed=%v err=%v", changed, err)
|
||
|
|
}
|
||
|
|
if changed, _ := c.AddDomain("example.com"); changed {
|
||
|
|
t.Fatal("duplicate domain should be a no-op (case-insensitive)")
|
||
|
|
}
|
||
|
|
if changed, _ := c.AddDomain("*.wild.example.com"); !changed {
|
||
|
|
t.Fatal("wildcard domain should be accepted")
|
||
|
|
}
|
||
|
|
if !c.RemoveDomain("EXAMPLE.COM") {
|
||
|
|
t.Fatal("RemoveDomain should be case-insensitive")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestValidDomain(t *testing.T) {
|
||
|
|
valid := []string{"example.com", "a.b.c.example.org", "*.example.com", "xn--bcher-kva.example"}
|
||
|
|
for _, d := range valid {
|
||
|
|
if !ValidDomain(d) {
|
||
|
|
t.Errorf("ValidDomain(%q) = false, want true", d)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
invalid := []string{"", "no_dots", "trailing-.com", "-leading.com", "sp ace.com", "*.", "a..b.com"}
|
||
|
|
for _, d := range invalid {
|
||
|
|
if ValidDomain(d) {
|
||
|
|
t.Errorf("ValidDomain(%q) = true, want false", d)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestHealthProbeValidation(t *testing.T) {
|
||
|
|
ok := Default()
|
||
|
|
if err := ok.Validate(); err != nil {
|
||
|
|
t.Fatalf("default probe rejected: %v", err)
|
||
|
|
}
|
||
|
|
disabled := Default()
|
||
|
|
disabled.Health.Probe = ""
|
||
|
|
if err := disabled.Validate(); err != nil {
|
||
|
|
t.Fatalf("empty probe should disable the check: %v", err)
|
||
|
|
}
|
||
|
|
for _, bad := range []string{"nope", ":443", "1.1.1.1:0", "1.1.1.1:99999"} {
|
||
|
|
c := Default()
|
||
|
|
c.Health.Probe = bad
|
||
|
|
if err := c.Validate(); err == nil {
|
||
|
|
t.Errorf("Health.Probe %q: expected validation error", bad)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestCheckTableIDRejectsReserved(t *testing.T) {
|
||
|
|
for _, id := range []int{0, 253, 254, 255, -1, MaxTableID + 1} {
|
||
|
|
if err := CheckTableID("table", id); err == nil {
|
||
|
|
t.Errorf("CheckTableID(%d): expected error", id)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if err := CheckTableID("table", 100); err != nil {
|
||
|
|
t.Errorf("CheckTableID(100): unexpected error: %v", err)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestWANGateway6Validation(t *testing.T) {
|
||
|
|
c := Default()
|
||
|
|
c.Network.WANGateway6 = "2001:db8::1"
|
||
|
|
if err := c.Validate(); err != nil {
|
||
|
|
t.Fatalf("valid IPv6 gateway rejected: %v", err)
|
||
|
|
}
|
||
|
|
c.Network.WANGateway6 = "192.168.1.1"
|
||
|
|
if err := c.Validate(); err == nil {
|
||
|
|
t.Fatal("IPv4 address in wan_gateway6 should be rejected")
|
||
|
|
}
|
||
|
|
}
|