diff options
Diffstat (limited to 'obfs4proxy')
-rw-r--r-- | obfs4proxy/obfs4proxy.go | 354 | ||||
-rw-r--r-- | obfs4proxy/proxy_http.go | 158 | ||||
-rw-r--r-- | obfs4proxy/proxy_socks4.go | 164 | ||||
-rw-r--r-- | obfs4proxy/pt_extras.go | 166 | ||||
-rw-r--r-- | obfs4proxy/termmon.go | 135 | ||||
-rw-r--r-- | obfs4proxy/termmon_linux.go | 49 |
6 files changed, 91 insertions, 935 deletions
diff --git a/obfs4proxy/obfs4proxy.go b/obfs4proxy/obfs4proxy.go index f66f7c0..92a34c0 100644 --- a/obfs4proxy/obfs4proxy.go +++ b/obfs4proxy/obfs4proxy.go @@ -32,22 +32,21 @@ package main import ( "flag" "fmt" - "io" golog "log" "net" - "net/url" "os" "path" - "sync" "syscall" - "golang.org/x/net/proxy" - "git.torproject.org/pluggable-transports/goptlib.git" - "git.torproject.org/pluggable-transports/obfs4.git/common/log" - "git.torproject.org/pluggable-transports/obfs4.git/common/socks5" - "git.torproject.org/pluggable-transports/obfs4.git/transports" - "git.torproject.org/pluggable-transports/obfs4.git/transports/base" + "github.com/OperatorFoundation/obfs4/common/log" + "github.com/OperatorFoundation/obfs4/common/termmon" + "github.com/OperatorFoundation/obfs4/common/pt_extras" + "github.com/OperatorFoundation/obfs4/transports" + + "github.com/OperatorFoundation/obfs4/proxies/proxy_socks5" + "github.com/OperatorFoundation/obfs4/proxies/proxy_transparent" + "github.com/OperatorFoundation/obfs4/proxies/proxy_transparent_udp" ) const ( @@ -57,249 +56,7 @@ const ( ) var stateDir string -var termMon *termMonitor - -func clientSetup() (launched bool, listeners []net.Listener) { - ptClientInfo, err := pt.ClientSetup(transports.Transports()) - if err != nil { - golog.Fatal(err) - } - - ptClientProxy, err := ptGetProxy() - if err != nil { - golog.Fatal(err) - } else if ptClientProxy != nil { - ptProxyDone() - } - - // Launch each of the client listeners. - for _, name := range ptClientInfo.MethodNames { - t := transports.Get(name) - if t == nil { - pt.CmethodError(name, "no such transport is supported") - continue - } - - f, err := t.ClientFactory(stateDir) - if err != nil { - pt.CmethodError(name, "failed to get ClientFactory") - continue - } - - ln, err := net.Listen("tcp", socksAddr) - if err != nil { - pt.CmethodError(name, err.Error()) - continue - } - - go clientAcceptLoop(f, ln, ptClientProxy) - pt.Cmethod(name, socks5.Version(), ln.Addr()) - - log.Infof("%s - registered listener: %s", name, ln.Addr()) - - listeners = append(listeners, ln) - launched = true - } - pt.CmethodsDone() - - return -} - -func clientAcceptLoop(f base.ClientFactory, ln net.Listener, proxyURI *url.URL) error { - defer ln.Close() - for { - conn, err := ln.Accept() - if err != nil { - if e, ok := err.(net.Error); ok && !e.Temporary() { - return err - } - continue - } - go clientHandler(f, conn, proxyURI) - } -} - -func clientHandler(f base.ClientFactory, conn net.Conn, proxyURI *url.URL) { - defer conn.Close() - termMon.onHandlerStart() - defer termMon.onHandlerFinish() - - name := f.Transport().Name() - - // Read the client's SOCKS handshake. - socksReq, err := socks5.Handshake(conn) - if err != nil { - log.Errorf("%s - client failed socks handshake: %s", name, err) - return - } - addrStr := log.ElideAddr(socksReq.Target) - - // Deal with arguments. - args, err := f.ParseArgs(&socksReq.Args) - if err != nil { - log.Errorf("%s(%s) - invalid arguments: %s", name, addrStr, err) - socksReq.Reply(socks5.ReplyGeneralFailure) - return - } - - // Obtain the proxy dialer if any, and create the outgoing TCP connection. - dialFn := proxy.Direct.Dial - if proxyURI != nil { - dialer, err := proxy.FromURL(proxyURI, proxy.Direct) - if err != nil { - // This should basically never happen, since config protocol - // verifies this. - log.Errorf("%s(%s) - failed to obtain proxy dialer: %s", name, addrStr, log.ElideError(err)) - socksReq.Reply(socks5.ReplyGeneralFailure) - return - } - dialFn = dialer.Dial - } - remote, err := f.Dial("tcp", socksReq.Target, dialFn, args) - if err != nil { - log.Errorf("%s(%s) - outgoing connection failed: %s", name, addrStr, log.ElideError(err)) - socksReq.Reply(socks5.ErrorToReplyCode(err)) - return - } - defer remote.Close() - err = socksReq.Reply(socks5.ReplySucceeded) - if err != nil { - log.Errorf("%s(%s) - SOCKS reply failed: %s", name, addrStr, log.ElideError(err)) - return - } - - if err = copyLoop(conn, remote); err != nil { - log.Warnf("%s(%s) - closed connection: %s", name, addrStr, log.ElideError(err)) - } else { - log.Infof("%s(%s) - closed connection", name, addrStr) - } - - return -} - -func serverSetup() (launched bool, listeners []net.Listener) { - ptServerInfo, err := pt.ServerSetup(transports.Transports()) - if err != nil { - golog.Fatal(err) - } - - for _, bindaddr := range ptServerInfo.Bindaddrs { - name := bindaddr.MethodName - t := transports.Get(name) - if t == nil { - pt.SmethodError(name, "no such transport is supported") - continue - } - - f, err := t.ServerFactory(stateDir, &bindaddr.Options) - if err != nil { - pt.SmethodError(name, err.Error()) - continue - } - - ln, err := net.ListenTCP("tcp", bindaddr.Addr) - if err != nil { - pt.SmethodError(name, err.Error()) - continue - } - - go serverAcceptLoop(f, ln, &ptServerInfo) - if args := f.Args(); args != nil { - pt.SmethodArgs(name, ln.Addr(), *args) - } else { - pt.SmethodArgs(name, ln.Addr(), nil) - } - - log.Infof("%s - registered listener: %s", name, log.ElideAddr(ln.Addr().String())) - - listeners = append(listeners, ln) - launched = true - } - pt.SmethodsDone() - - return -} - -func serverAcceptLoop(f base.ServerFactory, ln net.Listener, info *pt.ServerInfo) error { - defer ln.Close() - for { - conn, err := ln.Accept() - if err != nil { - if e, ok := err.(net.Error); ok && !e.Temporary() { - return err - } - continue - } - go serverHandler(f, conn, info) - } -} - -func serverHandler(f base.ServerFactory, conn net.Conn, info *pt.ServerInfo) { - defer conn.Close() - termMon.onHandlerStart() - defer termMon.onHandlerFinish() - - name := f.Transport().Name() - addrStr := log.ElideAddr(conn.RemoteAddr().String()) - log.Infof("%s(%s) - new connection", name, addrStr) - - // Instantiate the server transport method and handshake. - remote, err := f.WrapConn(conn) - if err != nil { - log.Warnf("%s(%s) - handshake failed: %s", name, addrStr, log.ElideError(err)) - return - } - - // Connect to the orport. - orConn, err := pt.DialOr(info, conn.RemoteAddr().String(), name) - if err != nil { - log.Errorf("%s(%s) - failed to connect to ORPort: %s", name, addrStr, log.ElideError(err)) - return - } - defer orConn.Close() - - if err = copyLoop(orConn, remote); err != nil { - log.Warnf("%s(%s) - closed connection: %s", name, addrStr, log.ElideError(err)) - } else { - log.Infof("%s(%s) - closed connection", name, addrStr) - } - - 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 -} +var termMon *termmon.TermMonitor func getVersion() string { return fmt.Sprintf("obfs4proxy-%s", obfs4proxyVersion) @@ -307,7 +64,7 @@ func getVersion() string { func main() { // Initialize the termination state monitor as soon as possible. - termMon = newTermMonitor() + termMon = termmon.NewTermMonitor() // Handle the command line arguments. _, execName := path.Split(os.Args[0]) @@ -315,6 +72,13 @@ func main() { logLevelStr := flag.String("logLevel", "ERROR", "Log level (ERROR/WARN/INFO/DEBUG)") enableLogging := flag.Bool("enableLogging", false, "Log to TOR_PT_STATE_LOCATION/"+obfs4proxyLogFile) unsafeLogging := flag.Bool("unsafeLogging", false, "Disable the address scrubber") + transparent := flag.Bool("transparent", false, "Enable transparent proxy mode") + udp := flag.Bool("udp", false, "Enable UDP proxy mode") + target := flag.String("target", "", "Specify transport server destination address") + clientMode := flag.Bool("client", false, "Enable client mode") + serverMode := flag.Bool("server", false, "Enable server mode") + statePath := flag.String("state", "", "Specify transport server destination address") + bindAddr := flag.String("bindaddr", "", "Specify the bind address for transparent server") flag.Parse() if *showVer { @@ -328,11 +92,11 @@ func main() { // Determine if this is a client or server, initialize the common state. var ptListeners []net.Listener launched := false - isClient, err := ptIsClient() + isClient, err := checkIsClient(*clientMode, *serverMode) if err != nil { golog.Fatalf("[ERROR]: %s - must be run as a managed transport", execName) } - if stateDir, err = pt.MakeStateDir(); err != nil { + if stateDir, err = makeStateDir(*statePath); err != nil { golog.Fatalf("[ERROR]: %s - No state directory: %s", execName, err) } if err = log.Init(*enableLogging, path.Join(stateDir, obfs4proxyLogFile), *unsafeLogging); err != nil { @@ -345,14 +109,58 @@ func main() { log.Noticef("%s - launched", getVersion()) - // Do the managed pluggable transport protocol configuration. - if isClient { - log.Infof("%s - initializing client transport listeners", execName) - launched, ptListeners = clientSetup() + if *transparent { + // Do the transparent proxy configuration. + log.Infof("%s - initializing transparent proxy", execName) + if *udp { + log.Infof("%s - initializing UDP transparent proxy", execName) + if isClient { + log.Infof("%s - initializing client transport listeners", execName) + if *target == "" { + log.Errorf("%s - transparent mode requires a target", execName) + } else { + launched = proxy_transparent_udp.ClientSetup(termMon, *target) + } + } else { + log.Infof("%s - initializing server transport listeners", execName) + if *bindAddr == "" { + fmt.Println("%s - transparent mode requires a bindaddr", execName) + } else { + launched = proxy_transparent_udp.ServerSetup(termMon, *bindAddr) + fmt.Println("launched", launched, ptListeners) + } + } + } else { + log.Infof("%s - initializing TCP transparent proxy", execName) + if isClient { + log.Infof("%s - initializing client transport listeners", execName) + if *target == "" { + log.Errorf("%s - transparent mode requires a target", execName) + } else { + launched, ptListeners = proxy_transparent.ClientSetup(termMon, *target) + } + } else { + log.Infof("%s - initializing server transport listeners", execName) + if *bindAddr == "" { + fmt.Println("%s - transparent mode requires a bindaddr", execName) + } else { + launched, ptListeners = proxy_transparent.ServerSetup(termMon, *bindAddr) + fmt.Println("launched", launched, ptListeners) + } + } + } } else { - log.Infof("%s - initializing server transport listeners", execName) - launched, ptListeners = serverSetup() + // Do the managed pluggable transport protocol configuration. + log.Infof("%s - initializing PT 1.0 proxy", execName) + if isClient { + log.Infof("%s - initializing client transport listeners", execName) + launched, ptListeners = proxy_socks5.ClientSetup(termMon) + } else { + log.Infof("%s - initializing server transport listeners", execName) + launched, ptListeners = proxy_socks5.ServerSetup(termMon) + } } + if !launched { // Initialization failed, the client or server setup routines should // have logged, so just exit here. @@ -368,7 +176,7 @@ func main() { // connections will be processed. Wait till the parent dies // (immediate exit), a SIGTERM is received (immediate exit), // or a SIGINT is received. - if sig := termMon.wait(false); sig == syscall.SIGTERM { + if sig := termMon.Wait(false); sig == syscall.SIGTERM { return } @@ -378,5 +186,25 @@ func main() { for _, ln := range ptListeners { ln.Close() } - termMon.wait(true) + + termMon.Wait(true) +} + +func checkIsClient(client bool, server bool) (bool, error) { + if client { + return true, nil + } else if server { + return false, nil + } else { + return pt_extras.PtIsClient() + } +} + +func makeStateDir(statePath string) (string, error) { + if statePath != "" { + err := os.MkdirAll(statePath, 0700) + return statePath, err + } else { + return pt.MakeStateDir() + } } diff --git a/obfs4proxy/proxy_http.go b/obfs4proxy/proxy_http.go deleted file mode 100644 index 6f11790..0000000 --- a/obfs4proxy/proxy_http.go +++ /dev/null @@ -1,158 +0,0 @@ -/* - * Copyright (c) 2014, Yawning Angel <yawning at torproject dot org> - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * * Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - -package main - -import ( - "bufio" - "fmt" - "net" - "net/http" - "net/http/httputil" - "net/url" - "time" - - "golang.org/x/net/proxy" -) - -// httpProxy is a HTTP connect proxy. -type httpProxy struct { - hostPort string - haveAuth bool - username string - password string - forward proxy.Dialer -} - -func newHTTP(uri *url.URL, forward proxy.Dialer) (proxy.Dialer, error) { - s := new(httpProxy) - s.hostPort = uri.Host - s.forward = forward - if uri.User != nil { - s.haveAuth = true - s.username = uri.User.Username() - s.password, _ = uri.User.Password() - } - - return s, nil -} - -func (s *httpProxy) Dial(network, addr string) (net.Conn, error) { - // Dial and create the http client connection. - c, err := s.forward.Dial("tcp", s.hostPort) - if err != nil { - return nil, err - } - conn := new(httpConn) - conn.httpConn = httputil.NewClientConn(c, nil) - conn.remoteAddr, err = net.ResolveTCPAddr(network, addr) - if err != nil { - conn.httpConn.Close() - return nil, err - } - - // HACK HACK HACK HACK. http.ReadRequest also does this. - reqURL, err := url.Parse("http://" + addr) - if err != nil { - conn.httpConn.Close() - return nil, err - } - reqURL.Scheme = "" - - req, err := http.NewRequest("CONNECT", reqURL.String(), nil) - if err != nil { - conn.httpConn.Close() - return nil, err - } - req.Close = false - if s.haveAuth { - req.SetBasicAuth(s.username, s.password) - } - req.Header.Set("User-Agent", "") - - resp, err := conn.httpConn.Do(req) - if err != nil && err != httputil.ErrPersistEOF { - conn.httpConn.Close() - return nil, err - } - if resp.StatusCode != 200 { - conn.httpConn.Close() - return nil, fmt.Errorf("proxy error: %s", resp.Status) - } - - conn.hijackedConn, conn.staleReader = conn.httpConn.Hijack() - return conn, nil -} - -type httpConn struct { - remoteAddr *net.TCPAddr - httpConn *httputil.ClientConn - hijackedConn net.Conn - staleReader *bufio.Reader -} - -func (c *httpConn) Read(b []byte) (int, error) { - if c.staleReader != nil { - if c.staleReader.Buffered() > 0 { - return c.staleReader.Read(b) - } - c.staleReader = nil - } - return c.hijackedConn.Read(b) -} - -func (c *httpConn) Write(b []byte) (int, error) { - return c.hijackedConn.Write(b) -} - -func (c *httpConn) Close() error { - return c.hijackedConn.Close() -} - -func (c *httpConn) LocalAddr() net.Addr { - return c.hijackedConn.LocalAddr() -} - -func (c *httpConn) RemoteAddr() net.Addr { - return c.remoteAddr -} - -func (c *httpConn) SetDeadline(t time.Time) error { - return c.hijackedConn.SetDeadline(t) -} - -func (c *httpConn) SetReadDeadline(t time.Time) error { - return c.hijackedConn.SetReadDeadline(t) -} - -func (c *httpConn) SetWriteDeadline(t time.Time) error { - return c.hijackedConn.SetWriteDeadline(t) -} - -func init() { - proxy.RegisterDialerType("http", newHTTP) -} diff --git a/obfs4proxy/proxy_socks4.go b/obfs4proxy/proxy_socks4.go deleted file mode 100644 index 536dd96..0000000 --- a/obfs4proxy/proxy_socks4.go +++ /dev/null @@ -1,164 +0,0 @@ -/* - * Copyright (c) 2014, Yawning Angel <yawning at torproject dot org> - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * * Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - * - * This is inspired by go.net/proxy/socks5.go: - * - * Copyright 2011 The Go Authors. All rights reserved. - * Use of this source code is governed by a BSD-style - * license that can be found in the LICENSE file. - */ - -package main - -import ( - "errors" - "fmt" - "io" - "net" - "net/url" - "strconv" - - "golang.org/x/net/proxy" -) - -// socks4Proxy is a SOCKS4 proxy. -type socks4Proxy struct { - hostPort string - username string - forward proxy.Dialer -} - -const ( - socks4Version = 0x04 - socks4CommandConnect = 0x01 - socks4Null = 0x00 - socks4ReplyVersion = 0x00 - - socks4Granted = 0x5a - socks4Rejected = 0x5b - socks4RejectedIdentdFailed = 0x5c - socks4RejectedIdentdMismatch = 0x5d -) - -func newSOCKS4(uri *url.URL, forward proxy.Dialer) (proxy.Dialer, error) { - s := new(socks4Proxy) - s.hostPort = uri.Host - s.forward = forward - if uri.User != nil { - s.username = uri.User.Username() - } - return s, nil -} - -func (s *socks4Proxy) Dial(network, addr string) (net.Conn, error) { - if network != "tcp" && network != "tcp4" { - return nil, errors.New("invalid network type") - } - - // Deal with the destination address/string. - ipStr, portStr, err := net.SplitHostPort(addr) - if err != nil { - return nil, err - } - ip := net.ParseIP(ipStr) - if ip == nil { - return nil, errors.New("failed to parse destination IP") - } - ip4 := ip.To4() - if ip4 == nil { - return nil, errors.New("destination address is not IPv4") - } - port, err := strconv.ParseUint(portStr, 10, 16) - if err != nil { - return nil, err - } - - // Connect to the proxy. - c, err := s.forward.Dial("tcp", s.hostPort) - if err != nil { - return nil, err - } - - // Make/write the request: - // +----+----+----+----+----+----+----+----+----+----+....+----+ - // | VN | CD | DSTPORT | DSTIP | USERID |NULL| - // +----+----+----+----+----+----+----+----+----+----+....+----+ - - req := make([]byte, 0, 9+len(s.username)) - req = append(req, socks4Version) - req = append(req, socks4CommandConnect) - req = append(req, byte(port>>8), byte(port)) - req = append(req, ip4...) - if s.username != "" { - req = append(req, s.username...) - } - req = append(req, socks4Null) - _, err = c.Write(req) - if err != nil { - c.Close() - return nil, err - } - - // Read the response: - // +----+----+----+----+----+----+----+----+ - // | VN | CD | DSTPORT | DSTIP | - // +----+----+----+----+----+----+----+----+ - - var resp [8]byte - _, err = io.ReadFull(c, resp[:]) - if err != nil { - c.Close() - return nil, err - } - if resp[0] != socks4ReplyVersion { - c.Close() - return nil, errors.New("proxy returned invalid SOCKS4 version") - } - if resp[1] != socks4Granted { - c.Close() - return nil, fmt.Errorf("proxy error: %s", socks4ErrorToString(resp[1])) - } - - return c, nil -} - -func socks4ErrorToString(code byte) string { - switch code { - case socks4Rejected: - return "request rejected or failed" - case socks4RejectedIdentdFailed: - return "request rejected becasue SOCKS server cannot connect to identd on the client" - case socks4RejectedIdentdMismatch: - return "request rejected because the client program and identd report different user-ids" - default: - return fmt.Sprintf("unknown failure code %x", code) - } -} - -func init() { - // Despite the scheme name, this really is SOCKS4. - proxy.RegisterDialerType("socks4a", newSOCKS4) -} diff --git a/obfs4proxy/pt_extras.go b/obfs4proxy/pt_extras.go deleted file mode 100644 index f490fbc..0000000 --- a/obfs4proxy/pt_extras.go +++ /dev/null @@ -1,166 +0,0 @@ -/* - * Copyright (c) 2014, Yawning Angel <yawning at torproject dot org> - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * * Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - -package main - -import ( - "errors" - "fmt" - "net" - "net/url" - "os" - "strconv" - - "git.torproject.org/pluggable-transports/goptlib.git" -) - -// This file contains things that probably should be in goptlib but are not -// yet or are not finalized. - -func ptEnvError(msg string) error { - line := []byte(fmt.Sprintf("ENV-ERROR %s\n", msg)) - pt.Stdout.Write(line) - return errors.New(msg) -} - -func ptProxyError(msg string) error { - line := []byte(fmt.Sprintf("PROXY-ERROR %s\n", msg)) - pt.Stdout.Write(line) - return errors.New(msg) -} - -func ptProxyDone() { - line := []byte("PROXY DONE\n") - pt.Stdout.Write(line) -} - -func ptIsClient() (bool, error) { - clientEnv := os.Getenv("TOR_PT_CLIENT_TRANSPORTS") - serverEnv := os.Getenv("TOR_PT_SERVER_TRANSPORTS") - if clientEnv != "" && serverEnv != "" { - return false, ptEnvError("TOR_PT_[CLIENT,SERVER]_TRANSPORTS both set") - } else if clientEnv != "" { - return true, nil - } else if serverEnv != "" { - return false, nil - } - return false, errors.New("not launched as a managed transport") -} - -func ptGetProxy() (*url.URL, error) { - specString := os.Getenv("TOR_PT_PROXY") - if specString == "" { - return nil, nil - } - spec, err := url.Parse(specString) - if err != nil { - return nil, ptProxyError(fmt.Sprintf("failed to parse proxy config: %s", err)) - } - - // Validate the TOR_PT_PROXY uri. - if !spec.IsAbs() { - return nil, ptProxyError("proxy URI is relative, must be absolute") - } - if spec.Path != "" { - return nil, ptProxyError("proxy URI has a path defined") - } - if spec.RawQuery != "" { - return nil, ptProxyError("proxy URI has a query defined") - } - if spec.Fragment != "" { - return nil, ptProxyError("proxy URI has a fragment defined") - } - - switch spec.Scheme { - case "http": - // The most forgiving of proxies. - - case "socks4a": - if spec.User != nil { - _, isSet := spec.User.Password() - if isSet { - return nil, ptProxyError("proxy URI specified SOCKS4a and a password") - } - } - - case "socks5": - if spec.User != nil { - // UNAME/PASSWD both must be between 1 and 255 bytes long. (RFC1929) - user := spec.User.Username() - passwd, isSet := spec.User.Password() - if len(user) < 1 || len(user) > 255 { - return nil, ptProxyError("proxy URI specified a invalid SOCKS5 username") - } - if !isSet || len(passwd) < 1 || len(passwd) > 255 { - return nil, ptProxyError("proxy URI specified a invalid SOCKS5 password") - } - } - - default: - return nil, ptProxyError(fmt.Sprintf("proxy URI has invalid scheme: %s", spec.Scheme)) - } - - _, err = resolveAddrStr(spec.Host) - if err != nil { - return nil, ptProxyError(fmt.Sprintf("proxy URI has invalid host: %s", err)) - } - - return spec, nil -} - -// Sigh, pt.resolveAddr() isn't exported. Include our own getto version that -// doesn't work around #7011, because we don't work with pre-0.2.5.x tor, and -// all we care about is validation anyway. -func resolveAddrStr(addrStr string) (*net.TCPAddr, error) { - ipStr, portStr, err := net.SplitHostPort(addrStr) - if err != nil { - return nil, err - } - - if ipStr == "" { - return nil, net.InvalidAddrError(fmt.Sprintf("address string %q lacks a host part", addrStr)) - } - if portStr == "" { - return nil, net.InvalidAddrError(fmt.Sprintf("address string %q lacks a port part", addrStr)) - } - ip := net.ParseIP(ipStr) - if ip == nil { - return nil, net.InvalidAddrError(fmt.Sprintf("not an IP string: %q", ipStr)) - } - port, err := strconv.ParseUint(portStr, 10, 16) - if err != nil { - return nil, net.InvalidAddrError(fmt.Sprintf("not a Port string: %q", portStr)) - } - - return &net.TCPAddr{IP: ip, Port: int(port), Zone: ""}, nil -} - -// Feature #15435 adds a new env var for determining if Tor keeps stdin -// open for use in termination detection. -func ptShouldExitOnStdinClose() bool { - return os.Getenv("TOR_PT_EXIT_ON_STDIN_CLOSE") == "1" -} diff --git a/obfs4proxy/termmon.go b/obfs4proxy/termmon.go deleted file mode 100644 index 86db190..0000000 --- a/obfs4proxy/termmon.go +++ /dev/null @@ -1,135 +0,0 @@ -/* - * Copyright (c) 2015, Yawning Angel <yawning at torproject dot org> - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * * Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - -package main - -import ( - "io" - "io/ioutil" - "os" - "os/signal" - "runtime" - "syscall" - "time" - - "git.torproject.org/pluggable-transports/obfs4.git/common/log" -) - -var termMonitorOSInit func(*termMonitor) error - -type termMonitor struct { - sigChan chan os.Signal - handlerChan chan int - numHandlers int -} - -func (m *termMonitor) onHandlerStart() { - m.handlerChan <- 1 -} - -func (m *termMonitor) onHandlerFinish() { - m.handlerChan <- -1 -} - -func (m *termMonitor) wait(termOnNoHandlers bool) os.Signal { - // Block until a signal has been received, or (optionally) the - // number of pending handlers has hit 0. In the case of the - // latter, treat it as if a SIGTERM has been received. - for { - select { - case n := <-m.handlerChan: - m.numHandlers += n - case sig := <-m.sigChan: - return sig - } - if termOnNoHandlers && m.numHandlers == 0 { - return syscall.SIGTERM - } - } -} - -func (m *termMonitor) termOnStdinClose() { - _, err := io.Copy(ioutil.Discard, os.Stdin) - - // io.Copy() will return a nil on EOF, since reaching EOF is - // expected behavior. No matter what, if this unblocks, assume - // that stdin is closed, and treat that as having received a - // SIGTERM. - log.Noticef("Stdin is closed or unreadable: %v", err) - m.sigChan <- syscall.SIGTERM -} - -func (m *termMonitor) termOnPPIDChange(ppid int) { - // Under most if not all U*IX systems, the parent PID will change - // to that of init once the parent dies. There are several notable - // exceptions (Slowlaris/Android), but the parent PID changes - // under those platforms as well. - // - // Naturally we lose if the parent has died by the time when the - // Getppid() call was issued in our parent, but, this is better - // than nothing. - const ppidPollInterval = 1 * time.Second - for ppid == os.Getppid() { - time.Sleep(ppidPollInterval) - } - - // Treat the parent PID changing as the same as having received - // a SIGTERM. - log.Noticef("Parent pid changed: %d (was %d)", os.Getppid(), ppid) - m.sigChan <- syscall.SIGTERM -} - -func newTermMonitor() (m *termMonitor) { - ppid := os.Getppid() - m = new(termMonitor) - m.sigChan = make(chan os.Signal) - m.handlerChan = make(chan int) - signal.Notify(m.sigChan, syscall.SIGINT, syscall.SIGTERM) - - // If tor supports feature #15435, we can use Stdin being closed as an - // indication that tor has died, or wants the PT to shutdown for any - // reason. - if ptShouldExitOnStdinClose() { - go m.termOnStdinClose() - } else { - // Instead of feature #15435, use various kludges and hacks: - // * Linux - Platform specific code that should always work. - // * Other U*IX - Somewhat generic code, that works unless the - // parent dies before the monitor is initialized. - if termMonitorOSInit != nil { - // Errors here are non-fatal, since it might still be - // possible to fall back to a generic implementation. - if err := termMonitorOSInit(m); err == nil { - return - } - } - if runtime.GOOS != "windows" { - go m.termOnPPIDChange(ppid) - } - } - return -} diff --git a/obfs4proxy/termmon_linux.go b/obfs4proxy/termmon_linux.go deleted file mode 100644 index 9711cfc..0000000 --- a/obfs4proxy/termmon_linux.go +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (c) 2015, Yawning Angel <yawning at torproject dot org> - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * * Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - -package main - -import ( - "fmt" - "syscall" -) - -func termMonitorInitLinux(m *termMonitor) error { - // Use prctl() to have the kernel deliver a SIGTERM if the parent - // process dies. This beats anything else that can be done before - // #15435 is implemented. - _, _, errno := syscall.Syscall(syscall.SYS_PRCTL, syscall.PR_SET_PDEATHSIG, uintptr(syscall.SIGTERM), 0) - if errno != 0 { - var err error = errno - return fmt.Errorf("prctl(PR_SET_PDEATHSIG, SIGTERM) returned: %s", err) - } - return nil -} - -func init() { - termMonitorOSInit = termMonitorInitLinux -} |