summaryrefslogtreecommitdiff
path: root/server/main.go
diff options
context:
space:
mode:
Diffstat (limited to 'server/main.go')
-rw-r--r--server/main.go245
1 files changed, 245 insertions, 0 deletions
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
+ }
+}