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

200 lines
5.9 KiB
Go

package app
import (
"os"
"path/filepath"
"strings"
"testing"
"time"
)
func TestTransportWrapExecWithNetns(t *testing.T) {
client := TransportClient{
ID: "sg-1",
Config: map[string]any{
"netns_enabled": true,
"netns_name": "svpn-test",
"netns_exec_mode": "ip",
},
}
cmd := transportWrapExecWithNetns(client, "/usr/bin/sing-box run -c /etc/singbox/test.json")
if !strings.Contains(cmd, "'ip' 'netns' 'exec' 'svpn-test'") {
t.Fatalf("expected wrapped command with netns exec, got: %s", cmd)
}
if !strings.Contains(cmd, "'/bin/sh' '-lc'") {
t.Fatalf("expected wrapped command to keep payload shell execution, got: %s", cmd)
}
}
func TestTransportWrapExecWithNetnsNsenter(t *testing.T) {
client := TransportClient{
ID: "sg-1",
Config: map[string]any{
"netns_enabled": true,
"netns_name": "svpn-test",
"netns_exec_mode": "nsenter",
"netns_nsenter_bin": "/usr/bin/nsenter",
},
}
cmd := transportWrapExecWithNetns(client, "/usr/bin/sing-box run -c /etc/singbox/test.json")
if !strings.Contains(cmd, "'/usr/bin/nsenter' '--net=/var/run/netns/svpn-test' '--'") {
t.Fatalf("expected wrapped command with nsenter, got: %s", cmd)
}
if !strings.Contains(cmd, "'/bin/sh' '-lc'") {
t.Fatalf("expected wrapped command to keep payload shell execution, got: %s", cmd)
}
}
func TestTransportNetnsExecCommandModes(t *testing.T) {
clientNS := TransportClient{
ID: "sg-1",
Config: map[string]any{
"netns_exec_mode": "nsenter",
"netns_nsenter_bin": "/usr/bin/nsenter",
},
}
name, args, err := transportNetnsExecCommand(clientNS, "svpn-test", "ip", "link", "show")
if err != nil {
t.Fatalf("nsenter mode command error: %v", err)
}
if name != "/usr/bin/nsenter" {
t.Fatalf("expected nsenter binary, got: %q", name)
}
if len(args) < 5 || args[0] != "--net=/var/run/netns/svpn-test" || args[1] != "--" || args[2] != "ip" {
t.Fatalf("unexpected nsenter args: %#v", args)
}
clientIP := TransportClient{
ID: "sg-1",
Config: map[string]any{
"netns_exec_mode": "ip",
"netns_ip_bin": "/sbin/ip",
},
}
name, args, err = transportNetnsExecCommand(clientIP, "svpn-test", "ip", "link", "show")
if err != nil {
t.Fatalf("ip mode command error: %v", err)
}
if name != "/sbin/ip" {
t.Fatalf("expected ip binary, got: %q", name)
}
if len(args) < 6 || args[0] != "netns" || args[1] != "exec" || args[2] != "svpn-test" || args[3] != "ip" {
t.Fatalf("unexpected ip args: %#v", args)
}
}
func TestTransportSystemdBackendProvisionWrapsNetnsExec(t *testing.T) {
origRunner := transportRunCommand
origUnitsDir := transportSystemdUnitsDir
defer func() {
transportRunCommand = origRunner
transportSystemdUnitsDir = origUnitsDir
}()
tmpDir := t.TempDir()
transportSystemdUnitsDir = tmpDir
transportRunCommand = func(_ time.Duration, name string, args ...string) (string, string, int, error) {
cmd := name + " " + strings.Join(args, " ")
if cmd == "systemctl daemon-reload" {
return "", "", 0, nil
}
return "", "", 0, nil
}
client := TransportClient{
ID: "sg-netns",
Kind: TransportClientSingBox,
Config: map[string]any{
"runner": "systemd",
"unit": "sg-netns.service",
"exec_start": "/usr/bin/sing-box run -c /etc/singbox/eu.json",
"netns_enabled": true,
"netns_name": "svpn-test",
},
}
res := selectTransportBackend(client).Provision(client)
if !res.OK {
t.Fatalf("expected provision success, got %#v", res)
}
data, err := os.ReadFile(filepath.Join(tmpDir, "sg-netns.service"))
if err != nil {
t.Fatalf("failed to read unit: %v", err)
}
text := string(data)
if !strings.Contains(text, "netns") || !strings.Contains(text, "svpn-test") {
t.Fatalf("expected netns exec in unit ExecStart, got: %s", text)
}
}
func TestTransportSystemdBackendActionNetnsStrictFailure(t *testing.T) {
origRunner := transportRunCommand
defer func() { transportRunCommand = origRunner }()
transportRunCommand = func(_ time.Duration, name string, args ...string) (string, string, int, error) {
cmd := name + " " + strings.Join(args, " ")
if cmd == "ip netns list" {
return "", "cannot list netns", 1, nil
}
return "", "", 0, nil
}
client := TransportClient{
ID: "sg-netns",
Kind: TransportClientSingBox,
Config: map[string]any{
"runner": "systemd",
"unit": "singbox-test.service",
"netns_enabled": true,
"netns_setup_strict": true,
},
}
res := selectTransportBackend(client).Action(client, "start")
if res.OK {
t.Fatalf("expected strict netns setup failure, got %#v", res)
}
if res.Code != "TRANSPORT_BACKEND_NETNS_SETUP_FAILED" {
t.Fatalf("unexpected error code: %#v", res)
}
}
func TestTransportSystemdBackendActionNetnsWarningNonStrict(t *testing.T) {
origRunner := transportRunCommand
defer func() { transportRunCommand = origRunner }()
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 == "ip netns list" {
return "", "cannot list netns", 1, nil
}
if cmd == "systemctl start singbox-test.service" {
return "", "", 0, nil
}
return "", "", 0, nil
}
client := TransportClient{
ID: "sg-netns",
Kind: TransportClientSingBox,
Config: map[string]any{
"runner": "systemd",
"unit": "singbox-test.service",
"netns_enabled": true,
},
}
res := selectTransportBackend(client).Action(client, "start")
if !res.OK {
t.Fatalf("expected non-strict start success with warning, got %#v", res)
}
if !strings.Contains(strings.ToLower(res.Message), "warnings") {
t.Fatalf("expected warning marker in message, got %#v", res)
}
if !strings.Contains(strings.ToLower(res.Stderr), "netns:") {
t.Fatalf("expected netns warning in stderr, got %#v", res)
}
if !strings.Contains(strings.Join(calls, " | "), "systemctl start singbox-test.service") {
t.Fatalf("expected systemctl start call, got %#v", calls)
}
}