/
jumper.go
148 lines (140 loc) · 3.63 KB
/
jumper.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
package gproxy
import (
"bytes"
"crypto/tls"
"encoding/base64"
"encoding/json"
"fmt"
"golang.org/x/net/proxy"
"net"
"net/url"
"strings"
"time"
)
type Jumper struct {
proxyURL *url.URL
timeout time.Duration
tls bool
cipher interface{}
}
func (j *Jumper) MarshalJSON() ([]byte, error) {
return json.Marshal(j.MarshalMap())
}
func (j *Jumper) MarshalMap() map[string]interface{} {
d := map[string]interface{}{
"proxy_url": j.proxyURL.String(),
"timeout": j.timeout / 1e6,
}
return d
}
type socks5Dialer struct {
timeout time.Duration
tls bool
}
func (s socks5Dialer) Dial(_, addr string) (net.Conn, error) {
return dialTCP(addr, s.tls, s.timeout)
}
func NewJumper(proxyURL string, timeout time.Duration) (j *Jumper, err error) {
if !strings.Contains(proxyURL, "://") {
proxyURL = "http://" + proxyURL
}
u, e := url.Parse(proxyURL)
if e != nil {
err = e
return
}
j = &Jumper{
proxyURL: u,
timeout: timeout,
}
switch j.proxyURL.Scheme {
case "http", "socks5":
case "https", "socks5s":
j.tls = true
default:
err = fmt.Errorf("unknown scheme of %s", j.proxyURL.String())
}
return
}
func (j *Jumper) Dial(address string) (net.Conn, error) {
return j.DialTimeout(address, j.timeout)
}
func (j *Jumper) DialTimeout(address string, timeout time.Duration) (net.Conn, error) {
switch j.proxyURL.Scheme {
case "http", "https":
return j.dialHTTPS(address, timeout)
case "socks5", "socks5s":
return j.dialSOCKS5(address, timeout)
}
return nil, fmt.Errorf("unknown jumper scheme %s", j.proxyURL.Scheme)
}
func (j *Jumper) dialHTTPS(address string, timeout time.Duration) (conn net.Conn, err error) {
conn, err = dialTCP(j.proxyURL.Host, j.tls, timeout)
if err != nil {
return
}
pb := new(bytes.Buffer)
pb.Write([]byte(fmt.Sprintf("CONNECT %s HTTP/1.1\r\n", address)))
pb.WriteString(fmt.Sprintf("Host: %s\r\n", address))
pb.WriteString(fmt.Sprintf("Proxy-Host: %s\r\n", address))
pb.WriteString("Proxy-Connection: Keep-Alive\r\n")
pb.WriteString("Connection: Keep-Alive\r\n")
if j.proxyURL.User != nil {
p, _ := j.proxyURL.User.Password()
u := fmt.Sprintf("%s:%s", url.QueryEscape(j.proxyURL.User.Username()), url.QueryEscape(p))
pb.Write([]byte(fmt.Sprintf("Proxy-Authorization: Basic %s\r\n", base64.StdEncoding.EncodeToString([]byte(u)))))
}
pb.Write([]byte("\r\n"))
_, err = conn.Write(pb.Bytes())
if err != nil {
conn.Close()
conn = nil
err = fmt.Errorf("error connecting to proxy: %s", err)
return
}
reply := make([]byte, 1024)
conn.SetDeadline(time.Now().Add(timeout))
n, e := conn.Read(reply)
conn.SetDeadline(time.Time{})
if e != nil {
err = fmt.Errorf("error read reply from proxy: %s", e)
conn.Close()
conn = nil
return
}
if bytes.Index(reply[:n], []byte("200")) == -1 {
err = fmt.Errorf("error greeting to proxy, response: %s", string(reply[:n]))
conn.Close()
conn = nil
return
}
return
}
func (j *Jumper) dialSOCKS5(address string, timeout time.Duration) (conn net.Conn, err error) {
auth := &proxy.Auth{}
if j.proxyURL.User != nil {
auth.User = j.proxyURL.User.Username()
auth.Password, _ = j.proxyURL.User.Password()
} else {
auth = nil
}
dialSocksProxy, e := proxy.SOCKS5("tcp", j.proxyURL.Host, auth, socks5Dialer{timeout: timeout, tls: j.tls})
if e != nil {
err = fmt.Errorf("error connecting to proxy: %s", e)
return
}
return dialSocksProxy.Dial("tcp", address)
}
func dialTCP(target string, isTLS bool, timeout time.Duration) (conn net.Conn, err error) {
conn, err = net.DialTimeout("tcp", target, timeout)
if err != nil {
return
}
if isTLS {
tlscfg := &tls.Config{
InsecureSkipVerify: true,
}
return tls.Client(conn, tlscfg), nil
}
return
}