Explorar o código

added basic upstream socks4 and http from obfs4proxy project

Eugene Fryntov %!s(int64=11) %!d(string=hai) anos
pai
achega
9d731191da
Modificáronse 2 ficheiros con 358 adicións e 0 borrados
  1. 176 0
      psiphon/upstreamproxy/proxy_http.go
  2. 182 0
      psiphon/upstreamproxy/proxy_socks4.go

+ 176 - 0
psiphon/upstreamproxy/proxy_http.go

@@ -0,0 +1,176 @@
+/*
+ * Copyright (c) 2015, Psiphon Inc.
+ * All rights reserved.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+/*
+ * 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 upstreamproxy
+
+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)
+}

+ 182 - 0
psiphon/upstreamproxy/proxy_socks4.go

@@ -0,0 +1,182 @@
+/*
+ * Copyright (c) 2015, Psiphon Inc.
+ * All rights reserved.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+/*
+ * 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 upstreamproxy
+
+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)
+}