github.com/psiphon-labs/psiphon-tunnel-core@v2.0.28+incompatible/psiphon/upstreamproxy/proxy_socks4.go (about)

     1  /*
     2   * Copyright (c) 2015, Psiphon Inc.
     3   * All rights reserved.
     4   *
     5   * This program is free software: you can redistribute it and/or modify
     6   * it under the terms of the GNU General Public License as published by
     7   * the Free Software Foundation, either version 3 of the License, or
     8   * (at your option) any later version.
     9   *
    10   * This program is distributed in the hope that it will be useful,
    11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
    12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13   * GNU General Public License for more details.
    14   *
    15   * You should have received a copy of the GNU General Public License
    16   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    17   *
    18   */
    19  /*
    20   * Copyright (c) 2014, Yawning Angel <yawning at torproject dot org>
    21   * All rights reserved.
    22   *
    23   * Redistribution and use in source and binary forms, with or without
    24   * modification, are permitted provided that the following conditions are met:
    25   *
    26   *  * Redistributions of source code must retain the above copyright notice,
    27   *    this list of conditions and the following disclaimer.
    28   *
    29   *  * Redistributions in binary form must reproduce the above copyright notice,
    30   *    this list of conditions and the following disclaimer in the documentation
    31   *    and/or other materials provided with the distribution.
    32   *
    33   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
    34   * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
    35   * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
    36   * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
    37   * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
    38   * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
    39   * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
    40   * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
    41   * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
    42   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
    43   * POSSIBILITY OF SUCH DAMAGE.
    44   *
    45   * This is inspired by go.net/proxy/socks5.go:
    46   *
    47   * Copyright 2011 The Go Authors. All rights reserved.
    48   * Use of this source code is governed by a BSD-style
    49   * license that can be found in the LICENSE file.
    50   */
    51  
    52  package upstreamproxy
    53  
    54  import (
    55  	"fmt"
    56  	"io"
    57  	"net"
    58  	"net/url"
    59  	"strconv"
    60  
    61  	"golang.org/x/net/proxy"
    62  )
    63  
    64  // socks4Proxy is a SOCKS4 proxy.
    65  type socks4Proxy struct {
    66  	hostPort string
    67  	username string
    68  	forward  proxy.Dialer
    69  }
    70  
    71  const (
    72  	socks4Version        = 0x04
    73  	socks4CommandConnect = 0x01
    74  	socks4Null           = 0x00
    75  	socks4ReplyVersion   = 0x00
    76  
    77  	socks4Granted                = 0x5a
    78  	socks4Rejected               = 0x5b
    79  	socks4RejectedIdentdFailed   = 0x5c
    80  	socks4RejectedIdentdMismatch = 0x5d
    81  )
    82  
    83  func newSOCKS4(uri *url.URL, forward proxy.Dialer) (proxy.Dialer, error) {
    84  	s := new(socks4Proxy)
    85  	s.hostPort = uri.Host
    86  	s.forward = forward
    87  	if uri.User != nil {
    88  		s.username = uri.User.Username()
    89  	}
    90  	return s, nil
    91  }
    92  
    93  func (s *socks4Proxy) Dial(network, addr string) (net.Conn, error) {
    94  	if network != "tcp" && network != "tcp4" {
    95  		return nil, proxyError(fmt.Errorf("invalid network type"))
    96  	}
    97  
    98  	// Deal with the destination address/string.
    99  	hostStr, portStr, err := net.SplitHostPort(addr)
   100  	domainDest := ""
   101  	if err != nil {
   102  		return nil, proxyError(fmt.Errorf("parsing destination address: %v", err))
   103  	}
   104  	ip := net.ParseIP(hostStr)
   105  	if ip == nil {
   106  		// hostStr is not representing an IP, probably a domain name
   107  		// try to put an invalid IP into DSTIP field and
   108  		// append domain name terminated by '\x00' at the end of request
   109  		ip = net.IPv4(0, 0, 0, 1)
   110  		domainDest = hostStr
   111  	}
   112  	ip4 := ip.To4()
   113  	if ip4 == nil {
   114  		return nil, proxyError(fmt.Errorf("destination address is not IPv4"))
   115  	}
   116  	port, err := strconv.ParseUint(portStr, 10, 16)
   117  	if err != nil {
   118  		return nil, proxyError(fmt.Errorf("failed to parse destination port: %v", err))
   119  	}
   120  
   121  	// Connect to the proxy.
   122  	c, err := s.forward.Dial("tcp", s.hostPort)
   123  	if err != nil {
   124  		return nil, proxyError(fmt.Errorf("failed to dial SOCKS4a proxy: %v", err))
   125  	}
   126  
   127  	// Make/write the request:
   128  	//  +----+----+----+----+----+----+----+----+----+----+....+----+
   129  	//  | VN | CD | DSTPORT |      DSTIP        | USERID       |NULL|
   130  	//  +----+----+----+----+----+----+----+----+----+----+....+----+
   131  
   132  	req := make([]byte, 0, 9+len(s.username))
   133  	req = append(req, socks4Version)
   134  	req = append(req, socks4CommandConnect)
   135  	req = append(req, byte(port>>8), byte(port))
   136  	req = append(req, ip4...)
   137  	if s.username != "" {
   138  		req = append(req, s.username...)
   139  	}
   140  	req = append(req, socks4Null)
   141  	if domainDest != "" {
   142  		req = append(req, domainDest...)
   143  		req = append(req, socks4Null)
   144  	}
   145  	_, err = c.Write(req)
   146  	if err != nil {
   147  		c.Close()
   148  		return nil, proxyError(fmt.Errorf("failed to write to SOCKS4a proxy: %v", err))
   149  	}
   150  
   151  	// Read the response:
   152  	// +----+----+----+----+----+----+----+----+
   153  	// | VN | CD | DSTPORT |      DSTIP        |
   154  	// +----+----+----+----+----+----+----+----+
   155  
   156  	var resp [8]byte
   157  	_, err = io.ReadFull(c, resp[:])
   158  	if err != nil {
   159  		c.Close()
   160  		return nil, proxyError(fmt.Errorf("failed to read SOCKS4a proxy response: %v", err))
   161  	}
   162  	if resp[0] != socks4ReplyVersion {
   163  		c.Close()
   164  		return nil, proxyError(fmt.Errorf("proxy returned invalid SOCKS4 version"))
   165  	}
   166  	if resp[1] != socks4Granted {
   167  		c.Close()
   168  		return nil, proxyError(fmt.Errorf("proxy error: %s", socks4ErrorToString(resp[1])))
   169  	}
   170  
   171  	return c, nil
   172  }
   173  
   174  func socks4ErrorToString(code byte) string {
   175  	switch code {
   176  	case socks4Rejected:
   177  		return "request rejected or failed"
   178  	case socks4RejectedIdentdFailed:
   179  		return "request rejected because SOCKS server cannot connect to identd on the client"
   180  	case socks4RejectedIdentdMismatch:
   181  		return "request rejected because the client program and identd report different user-ids"
   182  	default:
   183  		return fmt.Sprintf("unknown failure code %x", code)
   184  	}
   185  }
   186  
   187  func init() {
   188  	proxy.RegisterDialerType("socks4a", newSOCKS4)
   189  }