From a809112a20b31b9a4adca31ae564d943a41e9023 Mon Sep 17 00:00:00 2001 From: atanarjuat Date: Sat, 21 May 2022 00:43:41 +0200 Subject: functional transparent proxy --- obfsproxy/Makefile | 18 ++++++ obfsproxy/leap-vpn.sh | 13 +++++ obfsproxy/main.go | 154 +++++++++++++++++++++++++++++++++++++++---------- obfsproxy/main_test.go | 8 +-- 4 files changed, 155 insertions(+), 38 deletions(-) create mode 100644 obfsproxy/Makefile create mode 100755 obfsproxy/leap-vpn.sh (limited to 'obfsproxy') diff --git a/obfsproxy/Makefile b/obfsproxy/Makefile new file mode 100644 index 0000000..8694349 --- /dev/null +++ b/obfsproxy/Makefile @@ -0,0 +1,18 @@ +RHOST=163.172.126.44:443 +LHOST="10.0.0.209:443" + +run: + sudo ./obfsproxy -addr ${LHOST} -vpn ${RHOST} -state test_data -c test_data/obfs4.json + +certs: + curl -k https://black.riseup.net/ca.crt > /tmp/ca.crt + curl -k https://api.black.riseup.net/3/cert > /tmp/cert.pem + +check: + curl https://wtfismyip.com/json + +stop: + pkill -9 obfsproxy + +obfsproxy: + go build diff --git a/obfsproxy/leap-vpn.sh b/obfsproxy/leap-vpn.sh new file mode 100755 index 0000000..4fb09c3 --- /dev/null +++ b/obfsproxy/leap-vpn.sh @@ -0,0 +1,13 @@ +#!/bin/sh +sudo openvpn \ + --verb 3 \ + --tls-cipher DHE-RSA-AES128-SHA \ + --cipher AES-128-CBC \ + --dev tun --client --tls-client \ + --remote-cert-tls server --tls-version-min 1.2 \ + --ca /tmp/ca.crt --cert /tmp/cert.pem --key /tmp/cert.pem \ + --proto tcp4 \ + --remote localhost 4430 \ + --socks-proxy localhost 4430 \ + --route $GW 255.255.255.255 net_gateway \ + --persist-tun diff --git a/obfsproxy/main.go b/obfsproxy/main.go index a35a26d..5e56789 100644 --- a/obfsproxy/main.go +++ b/obfsproxy/main.go @@ -4,7 +4,9 @@ package main import ( "context" "encoding/json" + "errors" "flag" + "fmt" "io" "log" "net" @@ -12,12 +14,19 @@ import ( "os/signal" "0xacab.org/leap/obfsvpn" - "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) @@ -48,15 +57,17 @@ func main() { } 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) } - var cfg Config err = json.NewDecoder(fd).Decode(&cfg) if err != nil { logger.Fatalf("error decoding config: %v", err) @@ -67,15 +78,16 @@ func main() { debug.SetOutput(os.Stderr) } + log.Println("config:", cfg) + // 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, + 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) } + log.Println("DEBUG:", listenConfig) ln, err := listenConfig.Listen(ctx, "tcp", addr) if err != nil { logger.Fatalf("error binding to %s: %v", addr, err) @@ -105,44 +117,122 @@ func main() { return } debug.Printf("accepted connection %v…", conn) - go proxyConn(ctx, info, conn, net.Dialer{}, logger, debug) + go proxyConn(ctx, info, conn, logger, debug) } } -func proxyConn(ctx context.Context, info *pt.ServerInfo, conn net.Conn, d net.Dialer, logger, debug *log.Logger) { +// obfsConn 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 := conn.Close() + err := obfsConn.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) + // 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 } - 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 + if err = CopyLoop(clearConn, obfsConn); err != nil { + debug.Printf("%s - closed connection: %s", "obfsvpn", err.Error()) + } else { + debug.Printf("%s - closed connection", "obfsvpn") + } +} + +// a stock copy loop. let's not dwell too much on 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/obfsproxy/main_test.go b/obfsproxy/main_test.go index d22d3af..cdead61 100644 --- a/obfsproxy/main_test.go +++ b/obfsproxy/main_test.go @@ -1,15 +1,9 @@ package main import ( - "context" "flag" - "net" "os" - "os/exec" "testing" - "time" - - "golang.org/x/net/proxy" ) type testWriter struct { @@ -33,6 +27,7 @@ func TestMain(m *testing.M) { os.Exit(m.Run()) } +/* func TestRoundTrip(t *testing.T) { // Setup and exec the proxy: ctx, cancel := context.WithCancel(context.Background()) @@ -90,3 +85,4 @@ func TestRoundTrip(t *testing.T) { select {} } +*/ -- cgit v1.2.3