// 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) //#nosec G304 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? err := left.Close() if err != nil { fmt.Fprintln(os.Stderr, "error closing left connection: ", err.Error()) } err = right.Close() if err != nil { fmt.Fprintln(os.Stderr, "error closing right connection: ", err.Error()) } 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 } }