Skip to content
Snippets Groups Projects
Verified Commit 6498e0ce authored by Sam Whited's avatar Sam Whited
Browse files

obfsproxy: add initial proxy implementation


Fixes #3

Signed-off-by: default avatarSam Whited <sam@samwhited.com>
parent 790d6f76
No related branches found
No related tags found
Loading
Pipeline #84593 failed
......@@ -33,7 +33,7 @@ validate:
staticcheck -checks inherit,ST1000,ST1016,ST1020,ST1021,ST1022,ST1023 ./...
# gosec does not handle modules correctly.
# See: https://github.com/securego/gosec/issues/622
gosec -exclude-dir=obfsproxy ./...
gosec ./...
go mod tidy
git diff --exit-code -- go.mod go.sum
......@@ -5,6 +5,7 @@ go 1.17
require (
git.torproject.org/pluggable-transports/goptlib.git v1.0.0
gitlab.com/yawning/obfs4.git v0.0.0-20220204003609-77af0cba934d
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110
)
require (
......
......@@ -10,6 +10,7 @@ gitlab.com/yawning/obfs4.git v0.0.0-20220204003609-77af0cba934d h1:tJ8F7ABaQ3p3w
gitlab.com/yawning/obfs4.git v0.0.0-20220204003609-77af0cba934d/go.mod h1:9GcM8QNU9/wXtEEH2q8bVOnPI7FtIF6VVLzZ1l6Hgf8=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 h1:/UOmuWzQfxxo9UtlXMwuQU8CMgg1eZXqTRwkSQJWKOI=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 h1:qWPm9rbaAMKs8Bq/9LRpbMqxWRVUAQwMI9fVrssnTfw=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 h1:SrN+KX8Art/Sf4HNj6Zcz06G7VEz+7w9tdXTPOZ7+l4=
......
GW=37.218.241.98
certs:
curl -k https://black.riseup.net/ca.crt > /tmp/ca.crt
curl -k https://api.black.riseup.net/3/cert > /tmp/cert.pem
proxy: certs
#./obfsproxy -proxy=${GW}:4430 -addr=127.0.0.1:4430
GW=${GW} ./leap-vpn.sh
check:
curl https://wtfismyip.com/json
stop:
pkill -9 shape
obfsproxy:
go build
// 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
}
}()
}
package main
import (
"context"
"flag"
"net"
"os"
"os/exec"
"testing"
"time"
"golang.org/x/net/proxy"
)
type testWriter struct {
t *testing.T
prefix string
}
func (w testWriter) Write(p []byte) (int, error) {
w.t.Logf("%s%s", w.prefix, p)
return len(p), nil
}
func TestMain(m *testing.M) {
runProxy := flag.Bool("runproxy", false, "Start the command instead of running the tests")
flag.Parse()
if *runProxy {
os.Args = append(os.Args[0:1], flag.Args()...)
main()
return
}
os.Exit(m.Run())
}
func TestRoundTrip(t *testing.T) {
// Setup and exec the proxy:
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// Instead of passing a listener to the command or doing IPC to get the
// address of the listener created by the command back out, which would all
// require changes to the actual binary for something that has no use outside
// of tests and is just another potential source of errors, just start and
// stop a listener to get a random port and then pass that in (for the command
// to re-open) as a string. It's not ideal, but it's simple.
ln, err := net.Listen("tcp", "[::1]:0")
if err != nil {
t.Fatalf("error listening: %v", err)
}
addr := ln.Addr()
err = ln.Close()
if err != nil {
t.Fatalf("error closing listener: %v", err)
}
cmd := exec.CommandContext(ctx, os.Args[0], "-runproxy", "--", "-addr", addr.String(), "-proxy", "37.218.241.98:4430")
cmd.Stdout = testWriter{prefix: "stdout ", t: t}
cmd.Stderr = testWriter{prefix: "stderr ", t: t}
t.Logf("running proxy command %v", cmd.Args)
err = cmd.Start()
if err != nil {
t.Fatalf("error starting proxy: %v", err)
}
// Once the proxy is running, try to connect:
ln, err = net.Listen("tcp", "[::1]:0")
if err != nil {
t.Fatalf("error listening for connection: %v", err)
}
go func() {
conn, err := ln.Accept()
if err != nil {
t.Logf("error accepting connection: %v", err)
}
t.Logf("got conn: %v", conn)
}()
dialer, err := proxy.SOCKS5("tcp", addr.String(), nil, proxy.Direct)
if err != nil {
t.Fatalf("error creating socks dialer: %v", err)
}
// TODO: this is slow, flakey, and generally jank. Can we watch /proc for a
// new file descriptor or just poll until the listener is open?
t.Logf("waiting 3 seconds for command to start…")
time.Sleep(3 * time.Second)
_, err = dialer.Dial("tcp", ln.Addr().String())
if err != nil {
t.Fatalf("error dialing: %v", err)
}
select {}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment