diff options
Diffstat (limited to 'obfsproxy/main.go')
-rw-r--r-- | obfsproxy/main.go | 148 |
1 files changed, 148 insertions, 0 deletions
diff --git a/obfsproxy/main.go b/obfsproxy/main.go new file mode 100644 index 0000000..a35a26d --- /dev/null +++ b/obfsproxy/main.go @@ -0,0 +1,148 @@ +// The obfsproxy command creates a SOCKS5 obfuscating proxy. +package main + +import ( + "context" + "encoding/json" + "flag" + "io" + "log" + "net" + "os" + "os/signal" + + "0xacab.org/leap/obfsvpn" + + "git.torproject.org/pluggable-transports/goptlib.git" +) + +const transportName = "obfs4" + +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 + addr string = "[::1]:0" + vpnAddr string + cfgFile string + stateDir string + ) + 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") + } + + tcpVPNAddr, err := net.ResolveTCPAddr("tcp", vpnAddr) + if err != nil { + logger.Fatalf("error resolving VPN address: %v", err) + } + + fd, err := os.Open(cfgFile) + if err != nil { + logger.Fatalf("error opening config file: %v", err) + } + var cfg Config + err = json.NewDecoder(fd).Decode(&cfg) + if err != nil { + logger.Fatalf("error decoding config: %v", err) + } + + // Configure logging. + if verbose { + debug.SetOutput(os.Stderr) + } + + // Setup graceful shutdown. + ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt) + + listenConfig := obfsvpn.ListenConfig{ + NodeID: cfg.NodeID, + PrivateKey: cfg.PrivateKey, + Seed: cfg.DRBGSeed, + StateDir: stateDir, + } + 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, net.Dialer{}, logger, debug) + } +} + +func proxyConn(ctx context.Context, info *pt.ServerInfo, conn net.Conn, d net.Dialer, logger, debug *log.Logger) { + defer func() { + err := conn.Close() + if err != nil { + debug.Printf("error closing connection: %v", err) + } + }() + + // TODO: do we actually want to send the USERADDR/TRANSPORT ExtOrPort + // commands? I don't understand how this works, so I'm unsure. + remote, err := pt.DialOr(info, conn.RemoteAddr().String(), transportName) + if err != nil { + logger.Printf("error dialing remote: %v", err) + return + } + defer func() { + err := remote.Close() + if err != nil { + debug.Printf("error closing remote connection: %v", err) + } + }() + + go func() { + _, err := io.Copy(remote, conn) + if err != nil { + logger.Printf("error proxying client data to remote: %v", err) + return + } + }() + go func() { + _, err := io.Copy(conn, remote) + if err != nil { + logger.Printf("error proxying remote data to client: %v", err) + return + } + }() +} |