From 2219d4e32557a41bacadb04ce35e57fe067bf7f7 Mon Sep 17 00:00:00 2001 From: Ruben Pollan Date: Thu, 18 Jul 2019 13:19:27 +0200 Subject: [feat] first implementation of the library --- README.md | 22 +++++++++++ shapeshifter.go | 116 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 138 insertions(+) create mode 100644 README.md create mode 100644 shapeshifter.go diff --git a/README.md b/README.md new file mode 100644 index 0000000..ae9bf8f --- /dev/null +++ b/README.md @@ -0,0 +1,22 @@ +ShapeShifter library +-------------------- + +Heavily based on the shapeshifter-dispatcher: +https://github.com/OperatorFoundation/shapeshifter-dispatcher/ + + +To use it: +```go + ss := ShapeShifter{ + Cert: "cert", + Target: "ip:port", + SocksAddr: "127.0.0.1:4430", + } + err := ss.Open() + if err != nil { + return err + } + defer ss.Close() +``` + +And now you can tunnel your protocol into `127.0.0.1:4430`. diff --git a/shapeshifter.go b/shapeshifter.go new file mode 100644 index 0000000..8fc49dd --- /dev/null +++ b/shapeshifter.go @@ -0,0 +1,116 @@ +package shapeshifter + +import ( + "fmt" + "io" + "log" + "net" + "sync" + + "github.com/OperatorFoundation/shapeshifter-transports/transports/obfs4" +) + +type ShapeShifter struct { + Cert string + IatMode int + Target string // remote ip:port obfs4 server + SocksAddr string // -proxylistenaddr in shapeshifter-dispatcher + ln net.Listener +} + +func (ss *ShapeShifter) Open() error { + err := ss.checkOptions() + if err != nil { + return err + } + + ss.ln, err = net.Listen("tcp", ss.SocksAddr) + if err != nil { + return fmt.Errorf("failed to listen: %s", err.Error()) + } + + go ss.clientAcceptLoop() + return nil +} + +func (ss *ShapeShifter) Close() error { + return ss.ln.Close() +} + +func (ss ShapeShifter) clientAcceptLoop() error { + for { + conn, err := ss.ln.Accept() + if err != nil { + if e, ok := err.(net.Error); ok && !e.Temporary() { + return err + } + continue + } + go ss.clientHandler(conn) + } +} + +func (ss ShapeShifter) clientHandler(conn net.Conn) { + defer conn.Close() + + transport := obfs4.NewObfs4Client(ss.Cert, ss.IatMode) + remote := transport.Dial(ss.Target) + if remote == nil { + log.Printf("outgoing connection failed %s", ss.Target) + return + } + defer remote.Close() + + err := copyLoop(conn, remote) + if err != nil { + log.Printf("%s - closed connection: %v", ss.Target, err) + } else { + log.Printf("%s - closed connection", ss.Target) + } + + return +} + +func copyLoop(a net.Conn, b net.Conn) error { + // Note: b is always the pt connection. a is the SOCKS/ORPort connection. + errChan := make(chan error, 2) + + var wg sync.WaitGroup + wg.Add(2) + + go func() { + defer wg.Done() + defer b.Close() + defer a.Close() + _, err := io.Copy(b, a) + errChan <- err + }() + go func() { + defer wg.Done() + defer a.Close() + defer b.Close() + _, err := io.Copy(a, b) + errChan <- err + }() + + // Wait for both upstream and downstream to close. Since one side + // terminating closes the other, the second error in the channel will be + // something like EINVAL (though io.Copy() will swallow EOF), so only the + // first error is returned. + wg.Wait() + if len(errChan) > 0 { + return <-errChan + } + + return nil +} + +func (ss *ShapeShifter) checkOptions() error { + if ss.SocksAddr == "" { + ss.SocksAddr = "127.0.0.1:0" + } + if ss.Cert == "" { + return fmt.Errorf("obfs4 transport missing cert argument") + } + return nil +} -- cgit v1.2.3