diff options
author | atanarjuat <atanarjuat@example.com> | 2022-05-21 01:50:47 +0200 |
---|---|---|
committer | atanarjuat <atanarjuat@example.com> | 2022-05-21 05:04:09 +0200 |
commit | 182e3e76908f0824eac155b5e14775b50fe0aca5 (patch) | |
tree | e0d20e237ff6159b8967a3fd4592134a35bbcf77 /server | |
parent | a809112a20b31b9a4adca31ae564d943a41e9023 (diff) |
simplify testing of client and server
Diffstat (limited to 'server')
-rw-r--r-- | server/Makefile | 11 | ||||
-rw-r--r-- | server/main.go | 245 | ||||
-rw-r--r-- | server/main_test.go | 90 | ||||
-rw-r--r-- | server/test_data/obfs4.json | 1 |
4 files changed, 347 insertions, 0 deletions
diff --git a/server/Makefile b/server/Makefile new file mode 100644 index 0000000..853469b --- /dev/null +++ b/server/Makefile @@ -0,0 +1,11 @@ +RHOST=163.172.126.44:443 +LHOST="10.0.0.209:443" + +build: + go build + +run: + sudo ./obfsproxy -addr ${LHOST} -vpn ${RHOST} -state test_data -c test_data/obfs4.json + +stop: + pkill -9 obfsproxy diff --git a/server/main.go b/server/main.go new file mode 100644 index 0000000..5427184 --- /dev/null +++ b/server/main.go @@ -0,0 +1,245 @@ +// The obfsproxy command creates a SOCKS5 obfuscating proxy. +package main + +import ( + "context" + "encoding/json" + "errors" + "flag" + "fmt" + "io" + "log" + "net" + "os" + "os/signal" + + "0xacab.org/leap/obfsvpn" + pt "git.torproject.org/pluggable-transports/goptlib.git" +) + +const transportName = "obfs4" + +type Config struct { + NodeID string `json:"node-id"` + PrivateKey string `json:"private-key"` + PublicKey string `json:"public-key"` + DRBGSeed string `json:"drbg-seed"` + IatMode int `json:"iat-mode"` +} + +func main() { + // Setup logging. + logger := log.New(os.Stderr, "", log.LstdFlags) + debug := log.New(io.Discard, "DEBUG ", log.LstdFlags) + + // Setup command line flags. + var ( + verbose bool + vpnAddr string + cfgFile string + stateDir string + addr = "[::1]:0" + ) + flags := flag.NewFlagSet(os.Args[0], flag.ContinueOnError) + flags.BoolVar(&verbose, "v", verbose, "Enable verbose logging") + flags.StringVar(&addr, "addr", addr, "The address to listen on for client connections") + flags.StringVar(&vpnAddr, "vpn", vpnAddr, "The address of the OpenVPN server to connect to") + flags.StringVar(&cfgFile, "c", cfgFile, "The JSON config file to load") + flags.StringVar(&stateDir, "state", stateDir, "A directory in which to store bridge state") + err := flags.Parse(os.Args[1:]) + if err != nil { + logger.Fatalf("error parsing flags: %v", err) + } + + if vpnAddr == "" { + flags.PrintDefaults() + logger.Fatal("must specify -vpn") + } + + // TODO this needs to be configurable if we switch to UDP mode for OpenVPN. + tcpVPNAddr, err := net.ResolveTCPAddr("tcp", vpnAddr) + log.Println("target:", tcpVPNAddr) + if err != nil { + logger.Fatalf("error resolving VPN address: %v", err) + } + + var cfg Config + fd, err := os.Open(cfgFile) + log.Println("opening:", cfgFile) + if err != nil { + logger.Fatalf("error opening config file: %v", err) + } + err = json.NewDecoder(fd).Decode(&cfg) + if err != nil { + logger.Fatalf("error decoding config: %v", err) + } + + // Configure logging. + if verbose { + debug.SetOutput(os.Stderr) + } + + log.Println("config:", cfg) + + // Setup graceful shutdown. + ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt) + + listenConfig, err := obfsvpn.NewListenConfig( + cfg.NodeID, cfg.PrivateKey, cfg.PublicKey, + cfg.DRBGSeed, + stateDir) + if err != nil { + logger.Fatalf("Error creating listener from config: %v", err) + } + + logger.Printf("DEBUG: %v", listenConfig) + + ln, err := listenConfig.Listen(ctx, "tcp", addr) + if err != nil { + logger.Fatalf("error binding to %s: %v", addr, err) + } + + go func() { + <-ctx.Done() + // Stop releases the signal handling and falls back to the default behavior, + // so sending another interrupt will immediately terminate. + stop() + logger.Printf("shutting down…") + err := ln.Close() + if err != nil { + logger.Printf("error closing listener: %v", err) + } + }() + + info := &pt.ServerInfo{ + OrAddr: tcpVPNAddr, + } + + logger.Printf("Listening on %s…", ln.Addr()) + + for { + conn, err := ln.Accept() + if err != nil { + debug.Printf("error accepting connection: %v", err) + return + } + debug.Printf("accepted connection %v…", conn) + go proxyConn(ctx, info, conn, logger, debug) + } +} + +// proxyConn is a connection to the obfs4 client that we have accepted. we will dial to the remote contained in info +func proxyConn(ctx context.Context, info *pt.ServerInfo, obfsConn net.Conn, logger, debug *log.Logger) { + defer func() { + err := obfsConn.Close() + if err != nil { + debug.Printf("Error closing connection: %v", err) + } + }() + + // FIXME scrub ips in other than debug mode! + log.Println("Dialing:", info.OrAddr) + log.Println("Obfs4 client:", obfsConn.RemoteAddr().String()) + + /* + in the case of Tor, pt.DialOr returns a *net.TCPConn after dialing info.OrAddr. + in the vpn case (or any transparent proxy really), we do use + the pt.DialOr method to simply get a dialer to our upstream VPN remote. + + keeping this terminology is a bit stupid and slightly confusing, instead + we could get the clearConn just by doing: + + s, err := net.DialTCP("tcp", nil, info.ExtendedOrAddr) + if err != nil { + return nil, err + } + + that is precisely what the code in ptlib is doing. + + We also aspire at being a generic PT at some point, so perhaps it's better to keep the usage + of DialOr? + + Maybe not, and so we don't need to keep the confusing info struct. + */ + + // we'll refer to the connection to the usptream node as "clearConn", as opposed to the obfuscated conn. + // but for sure openvpn or whatever protocol you wrap has its own layer of encryption :) + + clearConn, err := pt.DialOr(info, obfsConn.RemoteAddr().String(), transportName) + + if err != nil { + logger.Printf("error dialing remote: %v", err) + return + } + + if err = CopyLoop(clearConn, obfsConn); err != nil { + debug.Printf("%s - closed connection: %s", "obfsvpn", err.Error()) + } else { + debug.Printf("%s - closed connection", "obfsvpn") + } +} + +// CopyLoop is a standard copy loop. We don't care too much who's client and +// who's server +func CopyLoop(left net.Conn, right net.Conn) error { + + fmt.Println("--> Entering copy loop.") + + if left == nil { + fmt.Fprintln(os.Stderr, "--> Copy loop has a nil connection (left).") + return errors.New("copy loop has a nil connection (left)") + } + + if right == nil { + fmt.Fprintln(os.Stderr, "--> Copy loop has a nil connection (right).") + return errors.New("copy loop has a nil connection (right)") + } + + // Note: right is always the pt connection. + lockL := make(chan bool) + lockR := make(chan bool) + errChan := make(chan error) + + go CopyLeftToRight(left, right, lockL, errChan) + go CopyRightToLeft(left, right, lockR, errChan) + + leftUp := true + rightUp := true + + var copyErr error + + for leftUp || rightUp { + select { + case <-lockL: + leftUp = false + case <-lockR: + rightUp = false + case copyErr = <-errChan: + log.Println("Error while copying") + } + } + + // XXX better to defer? + left.Close() + right.Close() + + return copyErr +} + +// TODO check for data races + +func CopyLeftToRight(l net.Conn, r net.Conn, ll chan bool, errChan chan error) { + _, e := io.Copy(r, l) + ll <- true + if e != nil { + errChan <- e + } +} + +func CopyRightToLeft(l net.Conn, r net.Conn, lr chan bool, errChan chan error) { + _, e := io.Copy(l, r) + lr <- true + if e != nil { + errChan <- e + } +} diff --git a/server/main_test.go b/server/main_test.go new file mode 100644 index 0000000..ecbb364 --- /dev/null +++ b/server/main_test.go @@ -0,0 +1,90 @@ +package main + +import ( + "flag" + "os" + "testing" +) + +/* +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 {} +} +*/ diff --git a/server/test_data/obfs4.json b/server/test_data/obfs4.json new file mode 100644 index 0000000..eb4a1e8 --- /dev/null +++ b/server/test_data/obfs4.json @@ -0,0 +1 @@ +{"node-id":"f27b806cf27016b29cff6f4a7027cbe4b06e116c","private-key":"540bd146653b484bd84c5ec646f6ba8232ec1ec2d91320b4fd50ced640d9e139","public-key":"50ae30404985dd51bc1c9bd77da4ab1e18ae2bd93838ededb7fade96821c461b","drbg-seed":"b96054e02220e45ecd128a63ba64771c7b4a2bfb0aeda045","iat-mode":0} |