github.com/AntonOrnatskyi/goproxy@v0.0.0-20190205095733-4526a9fa18b4/utils/jumper/jumper.go (about)

     1  package jumper
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/base64"
     6  	"fmt"
     7  	"net"
     8  	"net/url"
     9  	"time"
    10  
    11  	"golang.org/x/net/proxy"
    12  )
    13  
    14  type Jumper struct {
    15  	proxyURL *url.URL
    16  	timeout  time.Duration
    17  }
    18  type socks5Dialer struct {
    19  	timeout time.Duration
    20  }
    21  
    22  func (s socks5Dialer) Dial(network, addr string) (net.Conn, error) {
    23  	return net.DialTimeout(network, addr, s.timeout)
    24  }
    25  
    26  func New(proxyURL string, timeout time.Duration) (j Jumper, err error) {
    27  	u, e := url.Parse(proxyURL)
    28  	if e != nil {
    29  		err = e
    30  		return
    31  	}
    32  	j = Jumper{
    33  		proxyURL: u,
    34  		timeout:  timeout,
    35  	}
    36  	return
    37  }
    38  func (j *Jumper) Dial(address string, timeout time.Duration) (net.Conn, error) {
    39  	switch j.proxyURL.Scheme {
    40  	case "https":
    41  		return j.dialHTTPS(address, timeout)
    42  	case "socks5":
    43  		return j.dialSOCKS5(address, timeout)
    44  	default:
    45  		return nil, fmt.Errorf("unkown scheme of %s", j.proxyURL.String())
    46  	}
    47  }
    48  func (j *Jumper) dialHTTPS(address string, timeout time.Duration) (conn net.Conn, err error) {
    49  	conn, err = net.DialTimeout("tcp", j.proxyURL.Host, timeout)
    50  	if err != nil {
    51  		return
    52  	}
    53  	pb := new(bytes.Buffer)
    54  	pb.Write([]byte(fmt.Sprintf("CONNECT %s HTTP/1.1\r\n", address)))
    55  	pb.WriteString(fmt.Sprintf("Host: %s\r\n", address))
    56  	pb.WriteString(fmt.Sprintf("Proxy-Host: %s\r\n", address))
    57  	pb.WriteString("Proxy-Connection: Keep-Alive\r\n")
    58  	pb.WriteString("Connection: Keep-Alive\r\n")
    59  	if j.proxyURL.User != nil {
    60  		p, _ := j.proxyURL.User.Password()
    61  		u := fmt.Sprintf("%s:%s", j.proxyURL.User.Username(), p)
    62  		pb.Write([]byte(fmt.Sprintf("Proxy-Authorization: Basic %s\r\n", base64.StdEncoding.EncodeToString([]byte(u)))))
    63  	}
    64  	pb.Write([]byte("\r\n"))
    65  	_, err = conn.Write(pb.Bytes())
    66  	if err != nil {
    67  		conn.Close()
    68  		conn = nil
    69  		err = fmt.Errorf("error connecting to proxy: %s", err)
    70  		return
    71  	}
    72  	reply := make([]byte, 1024)
    73  	conn.SetDeadline(time.Now().Add(timeout))
    74  	n, e := conn.Read(reply)
    75  	conn.SetDeadline(time.Time{})
    76  	if e != nil {
    77  		err = fmt.Errorf("error read reply from proxy: %s", e)
    78  		conn.Close()
    79  		conn = nil
    80  		return
    81  	}
    82  	if bytes.Index(reply[:n], []byte("200")) == -1 {
    83  		err = fmt.Errorf("error greeting to proxy, response: %s", string(reply[:n]))
    84  		conn.Close()
    85  		conn = nil
    86  		return
    87  	}
    88  	return
    89  }
    90  func (j *Jumper) dialSOCKS5(address string, timeout time.Duration) (conn net.Conn, err error) {
    91  	auth := &proxy.Auth{}
    92  	if j.proxyURL.User != nil {
    93  		auth.User = j.proxyURL.User.Username()
    94  		auth.Password, _ = j.proxyURL.User.Password()
    95  	} else {
    96  		auth = nil
    97  	}
    98  	dialSocksProxy, e := proxy.SOCKS5("tcp", j.proxyURL.Host, auth, socks5Dialer{timeout: timeout})
    99  	if e != nil {
   100  		err = fmt.Errorf("error connecting to proxy: %s", e)
   101  		return
   102  	}
   103  	return dialSocksProxy.Dial("tcp", address)
   104  }