package main import ( "context" "flag" "net" "os" "os/exec" "testing" "time" "golang.org/x/net/proxy" ) type testWriter struct { t *testing.T prefix string } func (w testWriter) Write(p []byte) (int, error) { w.t.Logf("%s%s", w.prefix, p) return len(p), nil } func TestMain(m *testing.M) { runProxy := flag.Bool("runproxy", false, "Start the command instead of running the tests") flag.Parse() if *runProxy { os.Args = append(os.Args[0:1], flag.Args()...) main() return } os.Exit(m.Run()) } func TestRoundTrip(t *testing.T) { // Setup and exec the proxy: ctx, cancel := context.WithCancel(context.Background()) defer cancel() // Instead of passing a listener to the command or doing IPC to get the // address of the listener created by the command back out, which would all // require changes to the actual binary for something that has no use outside // of tests and is just another potential source of errors, just start and // stop a listener to get a random port and then pass that in (for the command // to re-open) as a string. It's not ideal, but it's simple. ln, err := net.Listen("tcp", "[::1]:0") if err != nil { t.Fatalf("error listening: %v", err) } addr := ln.Addr() err = ln.Close() if err != nil { t.Fatalf("error closing listener: %v", err) } cmd := exec.CommandContext(ctx, os.Args[0], "-runproxy", "--", "-addr", addr.String(), "-proxy", "37.218.241.98:4430") cmd.Stdout = testWriter{prefix: "stdout ", t: t} cmd.Stderr = testWriter{prefix: "stderr ", t: t} t.Logf("running proxy command %v", cmd.Args) err = cmd.Start() if err != nil { t.Fatalf("error starting proxy: %v", err) } // Once the proxy is running, try to connect: ln, err = net.Listen("tcp", "[::1]:0") if err != nil { t.Fatalf("error listening for connection: %v", err) } go func() { conn, err := ln.Accept() if err != nil { t.Logf("error accepting connection: %v", err) } t.Logf("got conn: %v", conn) }() dialer, err := proxy.SOCKS5("tcp", addr.String(), nil, proxy.Direct) if err != nil { t.Fatalf("error creating socks dialer: %v", err) } // TODO: this is slow, flakey, and generally jank. Can we watch /proc for a // new file descriptor or just poll until the listener is open? t.Logf("waiting 3 seconds for command to start…") time.Sleep(3 * time.Second) _, err = dialer.Dial("tcp", ln.Addr().String()) if err != nil { t.Fatalf("error dialing: %v", err) } select {} }