-
Notifications
You must be signed in to change notification settings - Fork 0
/
socks.go
155 lines (132 loc) · 3.07 KB
/
socks.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
150
151
152
153
154
155
package socks
import (
"fmt"
"io"
"log"
"net"
"time"
)
const (
SOCKS5Version = 0x05
ReservedField = 0x00
)
type Socks interface {
Run() error
}
type Server struct {
IP string
Port string
Config *Config
}
type Config struct {
AuthMethod Method
PasswordChecker func(username, password string) bool
}
func (s *Server) initConf() error {
if s.Config.AuthMethod == MethodPassword && s.Config.PasswordChecker == nil {
return ErrPasswordCheckerNotSet
}
return nil
}
func (s *Server) Run() error {
if err := s.initConf(); err != nil {
return err
}
addr := net.JoinHostPort(s.IP, s.Port)
lis, err := net.Listen("tcp", addr)
if err != nil {
return err
}
for {
conn, err := lis.Accept()
if err != nil {
log.Printf("connection failure from [%s]: %+v", conn.RemoteAddr(), err)
continue
}
go func() {
defer conn.Close()
err := handleConn(conn, s.Config)
if err != nil {
log.Printf("handle connection failure from [%s]: %+v", conn.RemoteAddr(), err)
}
}()
}
}
func handleConn(conn net.Conn, conf *Config) error {
// auth
if err := auth(conn, conf); err != nil {
return err
}
// request
target, err := request(conn)
if err != nil {
return err
}
// forward
return forward(conn, target)
}
func auth(conn io.ReadWriter, conf *Config) error {
// Read client auth message
msg, err := NewClientAuthMsg(conn)
if err != nil {
return err
}
// Check if the auth method is supported
if !msg.ContainsMethod(conf.AuthMethod) {
NewServerAuthMsg(conn, MethodNoAcceptable)
return fmt.Errorf("method %v not supported", conf.AuthMethod)
}
err = NewServerAuthMsg(conn, conf.AuthMethod)
if err != nil {
return err
}
switch conf.AuthMethod {
case MethodPassword:
msg, err := NewClientPasswordMsg(conn)
if err != nil {
return err
}
if !conf.PasswordChecker(msg.Username, msg.Password) {
WriteSrvPasswordMsg(conn, PasswordAuthFailure)
return ErrPasswordAuthFailure
}
err = WriteSrvPasswordMsg(conn, PasswordAuthSuccess)
if err != nil {
return err
}
case MethodNoAuth:
break
}
return nil
}
func request(conn io.ReadWriter) (io.ReadWriteCloser, error) {
msg, err := NewClientRequestMsg(conn)
if err != nil {
return nil, err
}
// Check if the command is supported
if msg.Command != CmdConnect {
// no supported
return nil, WriteReqFailureMsg(conn, ReplyCommandNotSupported)
}
// Check if the address type is supported
if msg.AddrType == IPv6Addr {
return nil, WriteReqFailureMsg(conn, ReplyAddressTypeNotSupported)
}
// Access target tcp server
address := net.JoinHostPort(msg.Address, fmt.Sprintf("%d", msg.Port))
targetConn, err := net.DialTimeout("tcp", address, 5*time.Second)
if err != nil {
return nil, WriteReqFailureMsg(conn, ReplyConnectionRefused)
}
// Send success message
addrVal := targetConn.LocalAddr()
addr := addrVal.(*net.TCPAddr)
return targetConn, WriteReqSuccessMsg(conn, addr.IP, uint16(addr.Port))
}
func forward(server io.ReadWriter, target io.ReadWriteCloser) error {
defer target.Close()
go io.Copy(target, server)
_, err := io.Copy(server, target)
return err
}