-
Notifications
You must be signed in to change notification settings - Fork 4
/
socks4.go
149 lines (123 loc) · 3.79 KB
/
socks4.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
149
// Package socks4 implements socks4 and socks4a support for net/proxy
// Just import `_ "github.com/bdandy/go-socks4"` to add `socks4` support
package socks4 // import _ "github.com/bdandy/go-socks4"
import (
"io"
"net"
"net/url"
"strconv"
errors "github.com/bdandy/go-errors"
"golang.org/x/net/proxy"
)
const (
socksVersion = 0x04
socksConnect = 0x01
// nolint
socksBind = 0x02
accessGranted = 0x5a
accessRejected = 0x5b
accessIdentRequired = 0x5c
accessIdentFailed = 0x5d
minRequestLen = 8
)
const (
ErrWrongNetwork = errors.String("network should be tcp or tcp4") // socks4 protocol supports only tcp/ip v4 connections
ErrDialFailed = errors.String("socks4 dial") // connection to socks4 server failed
ErrWrongAddr = errors.String("wrong addr: %s") // provided addr should be in format host:port
ErrConnRejected = errors.String("connection to remote host was rejected") // connection was rejected by proxy
ErrIdentRequired = errors.String("valid ident required") // proxy requires valid ident. Check Ident variable
ErrIO = errors.String("i\\o error") // some i\o error happened
ErrHostUnknown = errors.String("unable to find IP address of host %s") // host dns resolving failed
ErrInvalidResponse = errors.String("unknown socks4 server response %v") // proxy reply contains invalid data
ErrBuffer = errors.String("unable write into buffer")
)
var Ident = "[email protected]"
func init() {
proxy.RegisterDialerType("socks4", func(u *url.URL, d proxy.Dialer) (proxy.Dialer, error) {
return socks4{url: u, dialer: d}, nil
})
proxy.RegisterDialerType("socks4a", func(u *url.URL, d proxy.Dialer) (proxy.Dialer, error) {
return socks4{url: u, dialer: d}, nil
})
}
type Error = errors.Error
type socks4 struct {
url *url.URL
dialer proxy.Dialer
}
// Dial implements proxy.Dialer interface
func (s socks4) Dial(network, addr string) (c net.Conn, err error) {
if network != "tcp" && network != "tcp4" {
return nil, ErrWrongNetwork
}
c, err = s.dialer.Dial(network, s.url.Host)
if err != nil {
return nil, ErrDialFailed.New().Wrap(err)
}
// close connection later if we got an error
defer func() {
if err != nil && c != nil {
_ = c.Close()
}
}()
host, port, err := s.parseAddr(addr)
if err != nil {
return nil, ErrWrongAddr.New(addr).Wrap(err)
}
ip := net.IPv4(0, 0, 0, 1)
if !s.isSocks4a() {
if ip, err = s.lookupAddr(host); err != nil {
return nil, ErrHostUnknown.New(host).Wrap(err)
}
}
req, err := request{Host: host, Port: port, IP: ip, Is4a: s.isSocks4a()}.Bytes()
if err != nil {
return nil, ErrBuffer.New().Wrap(err)
}
var i int
i, err = c.Write(req)
if err != nil {
return c, ErrIO.New().Wrap(err)
} else if i < minRequestLen {
return c, ErrIO.New().Wrap(io.ErrShortWrite)
}
var resp [8]byte
i, err = c.Read(resp[:])
if err != nil && err != io.EOF {
return c, ErrIO.New().Wrap(err)
} else if i != 8 {
return c, ErrIO.New().Wrap(io.ErrUnexpectedEOF)
}
switch resp[1] {
case accessGranted:
return c, nil
case accessIdentRequired, accessIdentFailed:
return c, ErrIdentRequired
case accessRejected:
return c, ErrConnRejected
default:
return c, ErrInvalidResponse.New(resp[1])
}
}
func (s socks4) lookupAddr(host string) (net.IP, error) {
ip, err := net.ResolveIPAddr("ip4", host)
if err != nil {
return net.IP{}, err
}
return ip.IP.To4(), nil
}
func (s socks4) isSocks4a() bool {
return s.url.Scheme == "socks4a"
}
func (s socks4) parseAddr(addr string) (host string, iport int, err error) {
var port string
host, port, err = net.SplitHostPort(addr)
if err != nil {
return "", 0, err
}
iport, err = strconv.Atoi(port)
if err != nil {
return "", 0, err
}
return
}