summaryrefslogtreecommitdiff
path: root/obfsproxy/main.go
diff options
context:
space:
mode:
Diffstat (limited to 'obfsproxy/main.go')
-rw-r--r--obfsproxy/main.go148
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
+ }
+ }()
+}