Files
elmprodvpn/selective-vpn-api/app/transport_backends_test.go

1186 lines
36 KiB
Go

package app
import (
"context"
"fmt"
"net"
"os"
"path/filepath"
"strings"
"testing"
"time"
)
func TestSelectTransportBackendDefaultMock(t *testing.T) {
b := selectTransportBackend(TransportClient{
Kind: TransportClientSingBox,
Config: nil,
})
if b.ID() != "mock" {
t.Fatalf("expected mock backend, got %q", b.ID())
}
}
func TestSelectTransportBackendUnitInfersSystemd(t *testing.T) {
b := selectTransportBackend(TransportClient{
Kind: TransportClientPhoenix,
Config: map[string]any{
"unit": "phoenix.service",
},
})
if b.ID() != "systemd" {
t.Fatalf("expected systemd backend, got %q", b.ID())
}
}
func TestSelectTransportBackendRuntimeModeEmbeddedUnsupported(t *testing.T) {
b := selectTransportBackend(TransportClient{
Kind: TransportClientSingBox,
Config: map[string]any{
"runtime_mode": "embedded",
"runner": "systemd",
},
})
if b.ID() != "unsupported" {
t.Fatalf("expected unsupported backend for embedded runtime_mode, got %q", b.ID())
}
}
func TestSelectTransportBackendRuntimeModeSidecarUnsupported(t *testing.T) {
b := selectTransportBackend(TransportClient{
Kind: TransportClientPhoenix,
Config: map[string]any{
"runtime_mode": "sidecar",
"runner": "systemd",
},
})
if b.ID() != "unsupported" {
t.Fatalf("expected unsupported backend for sidecar runtime_mode, got %q", b.ID())
}
}
func TestTransportUnsupportedRuntimeBackendReturnsUnifiedError(t *testing.T) {
client := TransportClient{
ID: "ph-1",
Kind: TransportClientPhoenix,
Config: map[string]any{
"runtime_mode": "sidecar",
},
}
backend := selectTransportBackend(client)
act := backend.Action(client, "start")
if act.OK {
t.Fatalf("expected unsupported runtime action to fail")
}
if act.Code != "TRANSPORT_BACKEND_RUNTIME_MODE_UNSUPPORTED" {
t.Fatalf("unexpected action code: %#v", act)
}
prov := backend.Provision(client)
if prov.OK {
t.Fatalf("expected unsupported runtime provision to fail")
}
if prov.Code != "TRANSPORT_BACKEND_RUNTIME_MODE_UNSUPPORTED" {
t.Fatalf("unexpected provision code: %#v", prov)
}
health := backend.Health(client)
if health.OK {
t.Fatalf("expected unsupported runtime health to fail")
}
if health.Code != "TRANSPORT_BACKEND_RUNTIME_MODE_UNSUPPORTED" {
t.Fatalf("unexpected health code: %#v", health)
}
if health.Status != TransportClientDegraded {
t.Fatalf("expected degraded status for unsupported runtime_mode, got %#v", health)
}
}
func TestTransportSystemdActionUnitsDNSTTSSHTunnel(t *testing.T) {
client := TransportClient{
Kind: TransportClientDNSTT,
Config: map[string]any{
"runner": "systemd",
"unit": "dnstt-client.service",
"ssh_tunnel": true,
"ssh_unit": "dnstt-ssh.service",
},
}
unitsStart, code, msg := transportSystemdActionUnits(client, "start")
if code != "" || msg != "" {
t.Fatalf("unexpected action-unit error: code=%q msg=%q", code, msg)
}
if strings.Join(unitsStart, ",") != "dnstt-ssh.service,dnstt-client.service" {
t.Fatalf("unexpected start units order: %#v", unitsStart)
}
unitsStop, code, msg := transportSystemdActionUnits(client, "stop")
if code != "" || msg != "" {
t.Fatalf("unexpected action-unit error: code=%q msg=%q", code, msg)
}
if strings.Join(unitsStop, ",") != "dnstt-client.service,dnstt-ssh.service" {
t.Fatalf("unexpected stop units order: %#v", unitsStop)
}
}
func TestTransportSystemdBackendActionAndHealth(t *testing.T) {
orig := transportRunCommand
defer func() { transportRunCommand = orig }()
calls := make([]string, 0, 8)
transportRunCommand = func(_ time.Duration, name string, args ...string) (string, string, int, error) {
cmd := name + " " + strings.Join(args, " ")
calls = append(calls, cmd)
if cmd == "systemctl reset-failed dnstt-client.service" {
return "", "", 0, nil
}
if cmd == "systemctl reset-failed dnstt-ssh.service" {
return "", "", 0, nil
}
if cmd == "systemctl start dnstt-client.service" {
return "", "", 0, nil
}
if cmd == "systemctl start dnstt-ssh.service" {
return "", "", 0, nil
}
if cmd == "systemctl is-active dnstt-client.service" {
return "active\n", "", 0, nil
}
if cmd == "systemctl is-active dnstt-ssh.service" {
return "active\n", "", 0, nil
}
return "", "unexpected command", 1, fmt.Errorf("unexpected command: %s", cmd)
}
client := TransportClient{
Kind: TransportClientDNSTT,
Config: map[string]any{
"runner": "systemd",
"unit": "dnstt-client.service",
"ssh_tunnel": true,
"ssh_unit": "dnstt-ssh.service",
},
}
backend := selectTransportBackend(client)
res := backend.Action(client, "start")
if !res.OK {
t.Fatalf("expected action success, got %#v", res)
}
if len(calls) < 4 {
t.Fatalf("expected start calls, got %#v", calls)
}
if calls[0] != "systemctl reset-failed dnstt-ssh.service" ||
calls[1] != "systemctl reset-failed dnstt-client.service" ||
calls[2] != "systemctl start dnstt-ssh.service" ||
calls[3] != "systemctl start dnstt-client.service" {
t.Fatalf("unexpected call order: %#v", calls)
}
health := backend.Health(client)
if !health.OK || health.Status != TransportClientUp {
t.Fatalf("expected healthy up, got %#v", health)
}
}
func TestTransportSystemdBackendStopMissingUnitIsNoop(t *testing.T) {
orig := transportRunCommand
defer func() { transportRunCommand = orig }()
calls := make([]string, 0, 4)
transportRunCommand = func(_ time.Duration, name string, args ...string) (string, string, int, error) {
cmd := name + " " + strings.Join(args, " ")
calls = append(calls, cmd)
if cmd == "systemctl stop singbox@sg-missing.service" {
return "", "Failed to stop singbox@sg-missing.service: Unit singbox@sg-missing.service not loaded.", 5, fmt.Errorf("exit status 5")
}
return "", "unexpected command", 1, fmt.Errorf("unexpected command: %s", cmd)
}
client := TransportClient{
ID: "sg-missing",
Kind: TransportClientSingBox,
Config: map[string]any{
"runner": "systemd",
"unit": "singbox@.service",
},
}
backend := selectTransportBackend(client)
res := backend.Action(client, "stop")
if !res.OK {
t.Fatalf("expected stop noop success, got %#v", res)
}
if len(calls) != 1 || calls[0] != "systemctl stop singbox@sg-missing.service" {
t.Fatalf("unexpected calls: %#v", calls)
}
if !strings.Contains(strings.ToLower(res.Message), "systemctl stop ok") {
t.Fatalf("unexpected success message: %#v", res)
}
}
func TestTransportSystemdBackendStartAutoProvisionOnMissingUnit(t *testing.T) {
origRunner := transportRunCommand
origUnitsDir := transportSystemdUnitsDir
defer func() {
transportRunCommand = origRunner
transportSystemdUnitsDir = origUnitsDir
}()
tmpDir := t.TempDir()
transportSystemdUnitsDir = tmpDir
startCalls := 0
transportRunCommand = func(_ time.Duration, name string, args ...string) (string, string, int, error) {
cmd := name + " " + strings.Join(args, " ")
switch cmd {
case "systemctl reset-failed singbox@sg-auto.service":
return "", "", 0, nil
case "systemctl daemon-reload":
return "", "", 0, nil
case "systemctl start singbox@sg-auto.service":
startCalls++
if startCalls == 1 {
return "", "Failed to start singbox@sg-auto.service: Unit singbox@sg-auto.service not found.", 5, fmt.Errorf("exit status 5")
}
return "", "", 0, nil
default:
return "", "", 0, nil
}
}
client := TransportClient{
ID: "sg-auto",
Kind: TransportClientSingBox,
Config: map[string]any{
"runner": "systemd",
"unit": "singbox@.service",
"exec_start": "/usr/bin/sing-box run -c /tmp/sg-auto.json",
},
}
backend := selectTransportBackend(client)
res := backend.Action(client, "start")
if !res.OK {
t.Fatalf("expected start success after auto-provision, got %#v", res)
}
if startCalls != 2 {
t.Fatalf("expected start retried after auto-provision, got calls=%d", startCalls)
}
if _, err := os.Stat(filepath.Join(tmpDir, "singbox@.service")); err != nil {
t.Fatalf("expected provisioned template unit file, stat error: %v", err)
}
if _, err := os.Stat(filepath.Join(tmpDir, "singbox@sg-auto.service.d", transportSingBoxInstanceDropIn)); err != nil {
t.Fatalf("expected provisioned template drop-in file, stat error: %v", err)
}
}
func TestTransportSystemdBackendHealthOverlayMismatch(t *testing.T) {
orig := transportRunCommand
defer func() { transportRunCommand = orig }()
transportRunCommand = func(_ time.Duration, name string, args ...string) (string, string, int, error) {
cmd := name + " " + strings.Join(args, " ")
switch cmd {
case "systemctl is-active dnstt-client.service":
return "active\n", "", 0, nil
case "systemctl is-active dnstt-ssh.service":
return "inactive\n", "", 0, nil
default:
return "", "", 0, nil
}
}
client := TransportClient{
Kind: TransportClientDNSTT,
Config: map[string]any{
"runner": "systemd",
"unit": "dnstt-client.service",
"ssh_overlay": true,
"ssh_unit": "dnstt-ssh.service",
},
}
backend := selectTransportBackend(client)
health := backend.Health(client)
if health.OK {
t.Fatalf("expected unhealthy overlay mismatch, got %#v", health)
}
if health.Code != "TRANSPORT_BACKEND_HEALTH_FAILED" {
t.Fatalf("unexpected health code: %#v", health)
}
if health.Status != TransportClientDegraded {
t.Fatalf("unexpected health status: %#v", health)
}
}
func TestTransportCollectSingBoxConfigProbeEndpoints(t *testing.T) {
tmpDir := t.TempDir()
cfgPath := filepath.Join(tmpDir, "singbox.json")
cfg := `{
"outbounds": [
{ "type": "vless", "server": "n3.elmprod.tech", "server_port": 40903 },
{ "type": "wireguard", "server": "198.51.100.5", "server_port": "51820" }
]
}`
if err := os.WriteFile(cfgPath, []byte(cfg), 0o644); err != nil {
t.Fatalf("write config: %v", err)
}
client := TransportClient{
ID: "sg-probe",
Kind: TransportClientSingBox,
Config: map[string]any{
"runner": "systemd",
"unit": "singbox@.service",
"config_path": cfgPath,
},
}
endpoints := transportCollectSingBoxConfigProbeEndpoints(client)
got := make([]string, 0, len(endpoints))
for _, ep := range endpoints {
got = append(got, ep.address())
}
want := []string{"n3.elmprod.tech:40903", "198.51.100.5:51820"}
for _, w := range want {
found := false
for _, g := range got {
if g == w {
found = true
break
}
}
if !found {
t.Fatalf("missing endpoint %q in %#v", w, got)
}
}
}
func TestTransportSystemdBackendHealthIncludesLatencyProbe(t *testing.T) {
origRun := transportRunCommand
origDial := transportDialContext
defer func() {
transportRunCommand = origRun
transportDialContext = origDial
}()
tmpDir := t.TempDir()
cfgPath := filepath.Join(tmpDir, "singbox.json")
cfg := `{
"outbounds": [
{ "type": "vless", "server": "n3.elmprod.tech", "server_port": 40903 }
]
}`
if err := os.WriteFile(cfgPath, []byte(cfg), 0o644); err != nil {
t.Fatalf("write config: %v", err)
}
transportRunCommand = func(_ time.Duration, name string, args ...string) (string, string, int, error) {
cmd := name + " " + strings.Join(args, " ")
if cmd == "systemctl is-active singbox@sg-probe.service" {
return "active\n", "", 0, nil
}
return "", "unexpected command", 1, fmt.Errorf("unexpected command: %s", cmd)
}
calledAddr := ""
transportDialContext = func(_ context.Context, network, address string) (net.Conn, error) {
if network != "tcp4" {
return nil, fmt.Errorf("unexpected network: %s", network)
}
calledAddr = address
c1, c2 := net.Pipe()
_ = c2.Close()
return c1, nil
}
client := TransportClient{
ID: "sg-probe",
Kind: TransportClientSingBox,
Config: map[string]any{
"runner": "systemd",
"unit": "singbox@.service",
"config_path": cfgPath,
},
}
backend := selectTransportBackend(client)
health := backend.Health(client)
if !health.OK || health.Status != TransportClientUp {
t.Fatalf("expected healthy up, got %#v", health)
}
if health.LatencyMS <= 0 {
t.Fatalf("expected latency sample, got %#v", health)
}
if calledAddr != "n3.elmprod.tech:40903" {
t.Fatalf("unexpected dial addr: %q", calledAddr)
}
}
func TestTransportSystemdBackendHealthProbeFailureKeepsUpStatus(t *testing.T) {
origRun := transportRunCommand
origDial := transportDialContext
defer func() {
transportRunCommand = origRun
transportDialContext = origDial
}()
tmpDir := t.TempDir()
cfgPath := filepath.Join(tmpDir, "singbox.json")
cfg := `{
"outbounds": [
{ "type": "vless", "server": "n3.elmprod.tech", "server_port": 40903 }
]
}`
if err := os.WriteFile(cfgPath, []byte(cfg), 0o644); err != nil {
t.Fatalf("write config: %v", err)
}
transportRunCommand = func(_ time.Duration, name string, args ...string) (string, string, int, error) {
cmd := name + " " + strings.Join(args, " ")
if cmd == "systemctl is-active singbox@sg-probe.service" {
return "active\n", "", 0, nil
}
return "", "unexpected command", 1, fmt.Errorf("unexpected command: %s", cmd)
}
transportDialContext = func(_ context.Context, _ string, _ string) (net.Conn, error) {
return nil, fmt.Errorf("dial timeout")
}
client := TransportClient{
ID: "sg-probe",
Kind: TransportClientSingBox,
Config: map[string]any{
"runner": "systemd",
"unit": "singbox@.service",
"config_path": cfgPath,
},
}
backend := selectTransportBackend(client)
health := backend.Health(client)
if !health.OK || health.Status != TransportClientUp {
t.Fatalf("probe failure must not degrade systemd up status, got %#v", health)
}
if health.LatencyMS != 0 {
t.Fatalf("latency must be empty when probe fails, got %#v", health)
}
}
func TestTransportSystemdBackendHealthNetnsProbePreferred(t *testing.T) {
origRun := transportRunCommand
origDial := transportDialContext
defer func() {
transportRunCommand = origRun
transportDialContext = origDial
}()
tmpDir := t.TempDir()
cfgPath := filepath.Join(tmpDir, "singbox.json")
cfg := `{
"outbounds": [
{ "type": "vless", "server": "n3.elmprod.tech", "server_port": 40903 }
]
}`
if err := os.WriteFile(cfgPath, []byte(cfg), 0o644); err != nil {
t.Fatalf("write config: %v", err)
}
netnsProbeCalled := false
transportRunCommand = func(_ time.Duration, name string, args ...string) (string, string, int, error) {
cmd := name + " " + strings.Join(args, " ")
switch cmd {
case "systemctl is-active singbox@sg-probe.service":
return "active\n", "", 0, nil
}
if strings.HasPrefix(cmd, "ip netns exec svpn-sg bash -lc ") {
netnsProbeCalled = true
return "", "", 0, nil
}
return "", "unexpected command", 1, fmt.Errorf("unexpected command: %s", cmd)
}
hostDialCalled := false
transportDialContext = func(_ context.Context, _ string, _ string) (net.Conn, error) {
hostDialCalled = true
return nil, fmt.Errorf("must not use host dial when netns probe succeeds")
}
client := TransportClient{
ID: "sg-probe",
Kind: TransportClientSingBox,
Config: map[string]any{
"runner": "systemd",
"unit": "singbox@.service",
"config_path": cfgPath,
"netns_enabled": true,
"netns_name": "svpn-sg",
"netns_exec_mode": "ip",
"netns_ip_bin": "ip",
},
}
backend := selectTransportBackend(client)
health := backend.Health(client)
if !health.OK || health.Status != TransportClientUp {
t.Fatalf("expected healthy up, got %#v", health)
}
if health.LatencyMS <= 0 {
t.Fatalf("expected latency sample from netns probe, got %#v", health)
}
if !netnsProbeCalled {
t.Fatalf("expected netns probe command to be called")
}
if hostDialCalled {
t.Fatalf("host dial must not be called when netns probe succeeds")
}
}
func TestTransportSystemdBackendProvisionWritesUnits(t *testing.T) {
origRunner := transportRunCommand
origUnitsDir := transportSystemdUnitsDir
defer func() {
transportRunCommand = origRunner
transportSystemdUnitsDir = origUnitsDir
}()
tmpDir := t.TempDir()
transportSystemdUnitsDir = tmpDir
calls := make([]string, 0, 8)
transportRunCommand = func(_ time.Duration, name string, args ...string) (string, string, int, error) {
cmd := name + " " + strings.Join(args, " ")
calls = append(calls, cmd)
switch cmd {
case "systemctl daemon-reload", "systemctl enable dnstt-ssh.service", "systemctl enable dnstt-client.service":
return "", "", 0, nil
default:
return "", "", 0, nil
}
}
client := TransportClient{
ID: "dnstt-home",
Kind: TransportClientDNSTT,
Config: map[string]any{
"runner": "systemd",
"unit": "dnstt-client.service",
"doh_url": "https://dns.google/dns-query",
"pubkey": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
"domain": "tunnel.example.com",
"local_addr": "127.0.0.1:7001",
"ssh_tunnel": true,
"ssh_unit": "dnstt-ssh.service",
"ssh_exec_start": "/usr/bin/ssh -N -D 127.0.0.1:1080 root@example.com",
"enable_on_boot": true,
},
}
backend := selectTransportBackend(client)
res := backend.Provision(client)
if !res.OK {
t.Fatalf("expected provision success, got %#v", res)
}
if !strings.Contains(res.Message, "template:dnstt") {
t.Fatalf("expected template source in provision message, got %#v", res)
}
primaryPath := filepath.Join(tmpDir, "dnstt-client.service")
sshPath := filepath.Join(tmpDir, "dnstt-ssh.service")
primaryData, err := os.ReadFile(primaryPath)
if err != nil {
t.Fatalf("failed to read primary unit: %v", err)
}
sshData, err := os.ReadFile(sshPath)
if err != nil {
t.Fatalf("failed to read ssh unit: %v", err)
}
primaryText := string(primaryData)
if !strings.Contains(primaryText, "Requires=dnstt-ssh.service") {
t.Fatalf("primary unit missing ssh require: %s", primaryText)
}
if !strings.Contains(primaryText, "dnstt-client") {
t.Fatalf("primary unit missing dnstt command: %s", primaryText)
}
if !strings.Contains(primaryText, "ExecStart=/bin/sh -lc ") {
t.Fatalf("primary unit missing shell exec start: %s", primaryText)
}
if !strings.Contains(string(sshData), "ExecStart=/bin/sh -lc ") {
t.Fatalf("ssh unit missing shell exec start")
}
if len(calls) == 0 || calls[0] != "systemctl daemon-reload" {
t.Fatalf("expected daemon-reload call first, got %#v", calls)
}
}
func TestTransportSystemdBackendProvisionRequiresDNSTTTemplateFields(t *testing.T) {
backend := transportSystemdBackend{}
client := TransportClient{
ID: "dnstt-home",
Kind: TransportClientDNSTT,
Config: map[string]any{
"runner": "systemd",
"unit": "dnstt-client.service",
},
}
res := backend.Provision(client)
if res.OK {
t.Fatalf("expected provision failure without required dnstt template fields")
}
if res.Code != "TRANSPORT_BACKEND_PROVISION_CONFIG_REQUIRED" {
t.Fatalf("unexpected code: %#v", res)
}
if !strings.Contains(strings.ToLower(res.Message), "dnstt template requires") {
t.Fatalf("unexpected message: %#v", res)
}
}
func TestTransportSystemdBackendCleanupRemovesOwnedUnits(t *testing.T) {
origRunner := transportRunCommand
origUnitsDir := transportSystemdUnitsDir
defer func() {
transportRunCommand = origRunner
transportSystemdUnitsDir = origUnitsDir
}()
tmpDir := t.TempDir()
transportSystemdUnitsDir = tmpDir
unit := "sg-clean.service"
unitPath := filepath.Join(tmpDir, unit)
unitBody := "[Service]\nEnvironment=SVPN_TRANSPORT_ID=sg-clean\nExecStart=/usr/bin/sleep 60\n"
if err := os.WriteFile(unitPath, []byte(unitBody), 0o644); err != nil {
t.Fatalf("write unit file: %v", err)
}
calls := make([]string, 0, 8)
transportRunCommand = func(_ time.Duration, name string, args ...string) (string, string, int, error) {
cmd := name + " " + strings.Join(args, " ")
calls = append(calls, cmd)
return "", "", 0, nil
}
client := TransportClient{
ID: "sg-clean",
Kind: TransportClientSingBox,
Config: map[string]any{
"runner": "systemd",
"unit": unit,
},
}
backend := selectTransportBackend(client)
res := backend.Cleanup(client)
if !res.OK {
t.Fatalf("expected cleanup success, got %#v", res)
}
if _, err := os.Stat(unitPath); !os.IsNotExist(err) {
t.Fatalf("unit file was not removed: %v", err)
}
got := strings.Join(calls, " | ")
wantParts := []string{
"systemctl stop " + unit,
"systemctl disable " + unit,
"systemctl daemon-reload",
"systemctl reset-failed " + unit,
}
for _, part := range wantParts {
if !strings.Contains(got, part) {
t.Fatalf("expected cleanup calls to contain %q, got: %s", part, got)
}
}
}
func TestTransportSystemdBackendCleanupSkipsForeignUnits(t *testing.T) {
origRunner := transportRunCommand
origUnitsDir := transportSystemdUnitsDir
defer func() {
transportRunCommand = origRunner
transportSystemdUnitsDir = origUnitsDir
}()
tmpDir := t.TempDir()
transportSystemdUnitsDir = tmpDir
unit := "sg-foreign.service"
unitPath := filepath.Join(tmpDir, unit)
unitBody := "[Service]\nEnvironment=SVPN_TRANSPORT_ID=someone-else\nExecStart=/usr/bin/sleep 60\n"
if err := os.WriteFile(unitPath, []byte(unitBody), 0o644); err != nil {
t.Fatalf("write unit file: %v", err)
}
calls := make([]string, 0, 4)
transportRunCommand = func(_ time.Duration, name string, args ...string) (string, string, int, error) {
cmd := name + " " + strings.Join(args, " ")
calls = append(calls, cmd)
return "", "", 0, nil
}
client := TransportClient{
ID: "sg-clean",
Kind: TransportClientSingBox,
Config: map[string]any{
"runner": "systemd",
"unit": unit,
},
}
backend := selectTransportBackend(client)
res := backend.Cleanup(client)
if !res.OK {
t.Fatalf("expected cleanup success for foreign unit skip, got %#v", res)
}
if !strings.Contains(strings.ToLower(res.Message), "no managed unit artifacts") {
t.Fatalf("unexpected cleanup message: %#v", res)
}
if _, err := os.Stat(unitPath); err != nil {
t.Fatalf("foreign unit should stay intact, stat error: %v", err)
}
if len(calls) != 0 {
t.Fatalf("expected no systemctl calls for foreign unit, got %#v", calls)
}
}
func TestResolveTransportPrimaryExecStartManualOverride(t *testing.T) {
client := TransportClient{
Kind: TransportClientSingBox,
Config: map[string]any{
"exec_start": " /custom/bin/sing-box run -c /tmp/custom.json ",
},
}
cmd, source, err := resolveTransportPrimaryExecStart(client)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if source != "manual" {
t.Fatalf("expected manual source, got %q", source)
}
if cmd != "/custom/bin/sing-box run -c /tmp/custom.json" {
t.Fatalf("unexpected manual command: %q", cmd)
}
}
func TestResolveTransportPrimaryExecStartSingBoxTemplate(t *testing.T) {
client := TransportClient{
ID: "sg-eu",
Kind: TransportClientSingBox,
Config: map[string]any{
"bin": "/usr/bin/sing-box",
"config_path": "/etc/singbox/eu.json",
},
}
cmd, source, err := resolveTransportPrimaryExecStart(client)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if source != "template:singbox" {
t.Fatalf("unexpected source: %q", source)
}
if !strings.Contains(cmd, "'/usr/bin/sing-box' 'run' '-c' '/etc/singbox/eu.json'") {
t.Fatalf("unexpected template command: %q", cmd)
}
}
func TestResolveTransportPrimaryExecStartPhoenixTemplate(t *testing.T) {
client := TransportClient{
ID: "ph-eu",
Kind: TransportClientPhoenix,
Config: map[string]any{
"phoenix_bin": "/usr/local/bin/phoenix-client",
"config_path": "/etc/phoenix/client.toml",
},
}
cmd, source, err := resolveTransportPrimaryExecStart(client)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if source != "template:phoenix" {
t.Fatalf("unexpected source: %q", source)
}
if !strings.Contains(cmd, "'/usr/local/bin/phoenix-client' '-config' '/etc/phoenix/client.toml'") {
t.Fatalf("unexpected template command: %q", cmd)
}
}
func TestBuildTransportDNSTTClientCommandTemplate(t *testing.T) {
client := TransportClient{
ID: "dn-home",
Kind: TransportClientDNSTT,
Config: map[string]any{
"dnstt_bin": "/usr/local/bin/dnstt-client",
"doh_url": "https://dns.example.com/dns-query",
"pubkey": "deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef",
"domain": "tunnel.example.com",
"local_addr": "127.0.0.1:7002",
"utls": "HelloChrome_Auto",
},
}
cmd, err := buildTransportDNSTTClientCommand(client)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
wantParts := []string{
"'/usr/local/bin/dnstt-client'",
"'-doh' 'https://dns.example.com/dns-query'",
"'-pubkey' 'deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef'",
"'-utls' 'HelloChrome_Auto'",
"'tunnel.example.com' '127.0.0.1:7002'",
}
for _, part := range wantParts {
if !strings.Contains(cmd, part) {
t.Fatalf("command missing %q in %q", part, cmd)
}
}
}
func TestBuildTransportDNSTTClientCommandRequiresDomain(t *testing.T) {
client := TransportClient{
Kind: TransportClientDNSTT,
Config: map[string]any{
"doh_url": "https://dns.example.com/dns-query",
"pubkey": "cafebabecafebabecafebabecafebabecafebabecafebabecafebabecafebabe",
},
}
_, err := buildTransportDNSTTClientCommand(client)
if err == nil {
t.Fatalf("expected error for missing domain")
}
if !strings.Contains(err.Error(), "config.domain") {
t.Fatalf("unexpected error: %v", err)
}
}
func TestBuildTransportSingBoxCommandBundledProfilePrefersBinRoot(t *testing.T) {
tmpDir := t.TempDir()
binPath := filepath.Join(tmpDir, "sing-box")
if err := os.WriteFile(binPath, []byte("#!/bin/sh\nexit 0\n"), 0o755); err != nil {
t.Fatalf("write bundled binary: %v", err)
}
client := TransportClient{
ID: "sg-pack",
Kind: TransportClientSingBox,
Config: map[string]any{
"packaging_profile": "bundled",
"bin_root": tmpDir,
"require_binary": true,
"config_path": "/etc/singbox/eu.json",
},
}
cmd, err := buildTransportSingBoxCommand(client)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if !strings.Contains(cmd, shellQuoteArg(binPath)) {
t.Fatalf("expected bundled binary path in command, got %q", cmd)
}
}
func TestBuildTransportPhoenixClientCommandRequireBinaryMissing(t *testing.T) {
client := TransportClient{
ID: "ph-pack",
Kind: TransportClientPhoenix,
Config: map[string]any{
"packaging_profile": "bundled",
"bin_root": t.TempDir(),
"packaging_system_fallback": false,
"require_binary": true,
"config_path": "/etc/phoenix/client.toml",
},
}
_, err := buildTransportPhoenixClientCommand(client)
if err == nil {
t.Fatalf("expected error when required bundled binary is missing")
}
if !strings.Contains(err.Error(), "required phoenix binary not found") {
t.Fatalf("unexpected error: %v", err)
}
}
func TestBuildTransportSingBoxCommandRequireBinaryMissingManualOverride(t *testing.T) {
client := TransportClient{
Kind: TransportClientSingBox,
Config: map[string]any{
"singbox_bin": "/tmp/definitely-missing-sing-box-bin",
"require_binary": true,
"singbox_config_path": "/etc/singbox/eu.json",
},
}
_, err := buildTransportSingBoxCommand(client)
if err == nil {
t.Fatalf("expected manual binary validation error")
}
if !strings.Contains(err.Error(), "required singbox binary not found") {
t.Fatalf("unexpected error: %v", err)
}
}
func TestTransportSystemdBackendProvisionRendersServiceTuning(t *testing.T) {
origRunner := transportRunCommand
origUnitsDir := transportSystemdUnitsDir
defer func() {
transportRunCommand = origRunner
transportSystemdUnitsDir = origUnitsDir
}()
tmpDir := t.TempDir()
transportSystemdUnitsDir = tmpDir
transportRunCommand = func(_ time.Duration, _ string, _ ...string) (string, string, int, error) {
return "", "", 0, nil
}
client := TransportClient{
ID: "sg-eu",
Kind: TransportClientSingBox,
Config: map[string]any{
"runner": "systemd",
"unit": "singbox@.service",
"exec_start": "/usr/bin/sing-box run -c /etc/singbox/eu.json",
"restart_policy": "on-failure",
"restart_sec": 7,
"start_limit_interval_sec": 900,
"start_limit_burst": 11,
"timeout_start_sec": 55,
"timeout_stop_sec": 33,
"watchdog_sec": 20,
},
}
backend := selectTransportBackend(client)
res := backend.Provision(client)
if !res.OK {
t.Fatalf("expected provision success, got %#v", res)
}
unitPath := filepath.Join(tmpDir, "singbox@sg-eu.service.d", transportSingBoxInstanceDropIn)
data, err := os.ReadFile(unitPath)
if err != nil {
t.Fatalf("failed to read drop-in unit: %v", err)
}
text := string(data)
want := []string{
"StartLimitIntervalSec=900",
"StartLimitBurst=11",
"Restart=on-failure",
"RestartSec=7",
"TimeoutStartSec=55",
"TimeoutStopSec=33",
"WatchdogSec=20",
"NotifyAccess=main",
}
for _, part := range want {
if !strings.Contains(text, part) {
t.Fatalf("expected unit to contain %q, got: %s", part, text)
}
}
}
func TestTransportSystemdBackendProvisionRendersSSHServiceTuningOverrides(t *testing.T) {
origRunner := transportRunCommand
origUnitsDir := transportSystemdUnitsDir
defer func() {
transportRunCommand = origRunner
transportSystemdUnitsDir = origUnitsDir
}()
tmpDir := t.TempDir()
transportSystemdUnitsDir = tmpDir
transportRunCommand = func(_ time.Duration, _ string, _ ...string) (string, string, int, error) {
return "", "", 0, nil
}
client := TransportClient{
ID: "dn-home",
Kind: TransportClientDNSTT,
Config: map[string]any{
"runner": "systemd",
"unit": "dnstt-home.service",
"doh_url": "https://dns.google/dns-query",
"pubkey": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
"domain": "tunnel.example.com",
"local_addr": "127.0.0.1:7003",
"ssh_tunnel": true,
"ssh_unit": "dnstt-home-ssh.service",
"ssh_exec_start": "/usr/bin/ssh -N -D 127.0.0.1:1080 root@example.com",
"restart_sec": 4,
"ssh_restart_sec": 9,
"watchdog_sec": 0,
"ssh_watchdog_sec": 25,
"ssh_restart_policy": "on-failure",
},
}
backend := selectTransportBackend(client)
res := backend.Provision(client)
if !res.OK {
t.Fatalf("expected provision success, got %#v", res)
}
primaryData, err := os.ReadFile(filepath.Join(tmpDir, "dnstt-home.service"))
if err != nil {
t.Fatalf("failed to read primary unit: %v", err)
}
sshData, err := os.ReadFile(filepath.Join(tmpDir, "dnstt-home-ssh.service"))
if err != nil {
t.Fatalf("failed to read ssh unit: %v", err)
}
primaryText := string(primaryData)
sshText := string(sshData)
if !strings.Contains(primaryText, "RestartSec=4") {
t.Fatalf("expected primary restart sec from base config: %s", primaryText)
}
if strings.Contains(primaryText, "WatchdogSec=") {
t.Fatalf("did not expect primary watchdog line: %s", primaryText)
}
if !strings.Contains(sshText, "RestartSec=9") {
t.Fatalf("expected ssh restart sec override: %s", sshText)
}
if !strings.Contains(sshText, "Restart=on-failure") {
t.Fatalf("expected ssh restart policy override: %s", sshText)
}
if !strings.Contains(sshText, "WatchdogSec=25") {
t.Fatalf("expected ssh watchdog override: %s", sshText)
}
}
func TestTransportSystemdBackendProvisionDefaultHardeningEnabled(t *testing.T) {
origRunner := transportRunCommand
origUnitsDir := transportSystemdUnitsDir
defer func() {
transportRunCommand = origRunner
transportSystemdUnitsDir = origUnitsDir
}()
tmpDir := t.TempDir()
transportSystemdUnitsDir = tmpDir
transportRunCommand = func(_ time.Duration, _ string, _ ...string) (string, string, int, error) {
return "", "", 0, nil
}
client := TransportClient{
ID: "sg-hardened",
Kind: TransportClientSingBox,
Config: map[string]any{
"runner": "systemd",
"unit": "sg-hardened.service",
"exec_start": "/usr/bin/sing-box run -c /etc/singbox/eu.json",
},
}
backend := selectTransportBackend(client)
res := backend.Provision(client)
if !res.OK {
t.Fatalf("expected provision success, got %#v", res)
}
data, err := os.ReadFile(filepath.Join(tmpDir, "sg-hardened.service"))
if err != nil {
t.Fatalf("failed to read unit: %v", err)
}
text := string(data)
want := []string{
"NoNewPrivileges=yes",
"PrivateTmp=yes",
"ProtectSystem=full",
"ProtectHome=read-only",
"ProtectControlGroups=yes",
"ProtectKernelModules=yes",
"ProtectKernelTunables=yes",
"RestrictSUIDSGID=yes",
"LockPersonality=yes",
"UMask=0077",
}
for _, part := range want {
if !strings.Contains(text, part) {
t.Fatalf("expected baseline hardening line %q in unit: %s", part, text)
}
}
}
func TestTransportSystemdBackendProvisionHardeningCanBeDisabled(t *testing.T) {
origRunner := transportRunCommand
origUnitsDir := transportSystemdUnitsDir
defer func() {
transportRunCommand = origRunner
transportSystemdUnitsDir = origUnitsDir
}()
tmpDir := t.TempDir()
transportSystemdUnitsDir = tmpDir
transportRunCommand = func(_ time.Duration, _ string, _ ...string) (string, string, int, error) {
return "", "", 0, nil
}
client := TransportClient{
ID: "sg-soft",
Kind: TransportClientSingBox,
Config: map[string]any{
"runner": "systemd",
"unit": "sg-soft.service",
"exec_start": "/usr/bin/sing-box run -c /etc/singbox/eu.json",
"hardening_enabled": false,
},
}
backend := selectTransportBackend(client)
res := backend.Provision(client)
if !res.OK {
t.Fatalf("expected provision success, got %#v", res)
}
data, err := os.ReadFile(filepath.Join(tmpDir, "sg-soft.service"))
if err != nil {
t.Fatalf("failed to read unit: %v", err)
}
text := string(data)
blocked := []string{
"NoNewPrivileges=",
"ProtectSystem=",
"ProtectHome=",
"RestrictSUIDSGID=",
"UMask=",
}
for _, part := range blocked {
if strings.Contains(text, part) {
t.Fatalf("expected hardening line %q to be absent when disabled: %s", part, text)
}
}
}
func TestTransportSystemdBackendProvisionSSHHardeningOverrideDisabled(t *testing.T) {
origRunner := transportRunCommand
origUnitsDir := transportSystemdUnitsDir
defer func() {
transportRunCommand = origRunner
transportSystemdUnitsDir = origUnitsDir
}()
tmpDir := t.TempDir()
transportSystemdUnitsDir = tmpDir
transportRunCommand = func(_ time.Duration, _ string, _ ...string) (string, string, int, error) {
return "", "", 0, nil
}
client := TransportClient{
ID: "dn-hard",
Kind: TransportClientDNSTT,
Config: map[string]any{
"runner": "systemd",
"unit": "dn-hard.service",
"doh_url": "https://dns.google/dns-query",
"pubkey": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
"domain": "tunnel.example.com",
"local_addr": "127.0.0.1:7004",
"ssh_tunnel": true,
"ssh_unit": "dn-hard-ssh.service",
"ssh_exec_start": "/usr/bin/ssh -N -D 127.0.0.1:1080 root@example.com",
"hardening_profile": "strict",
"ssh_hardening_enabled": false,
},
}
backend := selectTransportBackend(client)
res := backend.Provision(client)
if !res.OK {
t.Fatalf("expected provision success, got %#v", res)
}
primaryData, err := os.ReadFile(filepath.Join(tmpDir, "dn-hard.service"))
if err != nil {
t.Fatalf("failed to read primary unit: %v", err)
}
sshData, err := os.ReadFile(filepath.Join(tmpDir, "dn-hard-ssh.service"))
if err != nil {
t.Fatalf("failed to read ssh unit: %v", err)
}
primaryText := string(primaryData)
sshText := string(sshData)
if !strings.Contains(primaryText, "ProtectSystem=strict") {
t.Fatalf("expected strict hardening in primary unit: %s", primaryText)
}
if !strings.Contains(primaryText, "PrivateDevices=yes") {
t.Fatalf("expected strict private devices in primary unit: %s", primaryText)
}
if strings.Contains(sshText, "NoNewPrivileges=") || strings.Contains(sshText, "ProtectSystem=") {
t.Fatalf("expected ssh unit hardening to be disabled by ssh_hardening_enabled=false: %s", sshText)
}
}