// Linux test for tsnet — loads the shared library via CGo and tests the tunnel. // // Run from macOS via Docker: // docker run --rm --platform linux/amd64 \ // -v /path/to/flutter-tsnet:/src -w /src/test \ // golang:latest go run linux_test.go \ // -authkey=tskey-auth-xxx \ // -heater-ip=100.x.x.x package main /* #cgo LDFLAGS: -L${SRCDIR}/../linux -ltailscale -lresolv -lpthread #include extern char* TailscaleStart(char*, char*, char*); extern char* TailscaleStartProxy(char*, int, int*); extern char* TailscaleStopProxy(); extern char* TailscaleStop(); extern char* TailscaleStatus(char**); extern char* TailscaleIP(char**); extern void TailscaleFreeString(char*); */ import "C" import ( "flag" "fmt" "net" "os" "time" "unsafe" ) func goString(cs *C.char) string { if cs == nil { return "" } s := C.GoString(cs) C.TailscaleFreeString(cs) return s } func main() { authKey := flag.String("authkey", "", "Tailscale auth key") heaterIP := flag.String("heater-ip", "", "Heater's Tailscale IP") heaterPort := flag.Int("heater-port", 5050, "Heater's port") flag.Parse() if *authKey == "" || *heaterIP == "" { fmt.Fprintf(os.Stderr, "Usage: go run linux_test.go -authkey=tskey-auth-xxx -heater-ip=100.x.x.x\n") os.Exit(1) } // State dir stateDir := "/tmp/tsnet-linux-test" os.MkdirAll(stateDir, 0o700) // 1. Start fmt.Println("[1/4] Starting tsnet...") start := time.Now() dirC := C.CString(stateDir) keyC := C.CString(*authKey) hostC := C.CString("tsnet-linux-test") err := goString(C.TailscaleStart(dirC, keyC, hostC)) C.free(unsafe.Pointer(dirC)) C.free(unsafe.Pointer(keyC)) C.free(unsafe.Pointer(hostC)) if err != "" { fmt.Printf("[1/4] FAIL: %s\n", err) os.Exit(1) } fmt.Printf("[1/4] Started in %dms\n", time.Since(start).Milliseconds()) // 2. Get IP (with retry) fmt.Println("[2/4] Getting Tailscale IP...") var ip string for i := 0; i < 30; i++ { var errOut *C.char ipC := C.TailscaleIP(&errOut) e := goString(errOut) if e == "" { ip = goString(ipC) break } C.TailscaleFreeString(ipC) time.Sleep(500 * time.Millisecond) } fmt.Printf("[2/4] Our IP: %s\n", ip) // 3. Start proxy fmt.Println("[3/4] Starting proxy...") ipC := C.CString(*heaterIP) var localPort C.int errStr := goString(C.TailscaleStartProxy(ipC, C.int(*heaterPort), &localPort)) C.free(unsafe.Pointer(ipC)) if errStr != "" { fmt.Printf("[3/4] FAIL: %s\n", errStr) os.Exit(1) } fmt.Printf("[3/4] Proxy: localhost:%d → %s:%d\n", int(localPort), *heaterIP, *heaterPort) // 4. Test TCP fmt.Println("[4/4] Testing TCP connection...") conn, connErr := net.DialTimeout("tcp", fmt.Sprintf("127.0.0.1:%d", int(localPort)), 10*time.Second) if connErr != nil { fmt.Printf("[4/4] TCP FAILED: %v\n", connErr) } else { fmt.Println("[4/4] TCP SUCCESS") conn.Close() } // Cleanup C.TailscaleStopProxy() C.TailscaleStop() fmt.Println("\nALL TESTS PASSED") }