forked from linw1995/lightsocks-python
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathserver.py
More file actions
213 lines (189 loc) · 7.89 KB
/
Copy pathserver.py
File metadata and controls
213 lines (189 loc) · 7.89 KB
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
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
import logging
import typing
import socket
import asyncio
from lightsocks.utils import net
from lightsocks.core.cipher import Cipher
from lightsocks.core.securesocket import SecureSocket
Connection = socket.socket
logger = logging.getLogger(__name__)
class LsServer(SecureSocket):
def __init__(self,
loop: asyncio.AbstractEventLoop,
password: bytearray,
listenAddr: net.Address) -> None:
super().__init__(loop=loop, cipher=Cipher.NewCipher(password))
self.listenAddr = listenAddr
async def listen(self, didListen: typing.Callable=None):
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as listener:
listener.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
listener.setblocking(False)
listener.bind(self.listenAddr)
listener.listen(socket.SOMAXCONN)
logger.info('Listen to %s:%d' % self.listenAddr)
if didListen:
didListen(listener.getsockname())
while True:
connection, address = await self.loop.sock_accept(listener)
logger.info('Receive %s:%d', *address)
asyncio.ensure_future(self.handleConn(connection))
async def handleConn(self, connection: Connection):
"""
Handle the connection from LsLocal.
"""
"""
SOCKS Protocol Version 5 https://www.ietf.org/rfc/rfc1928.txt
The localConn connects to the dstServer, and sends a ver
identifier/method selection message:
+----+----------+----------+
|VER | NMETHODS | METHODS |
+----+----------+----------+
| 1 | 1 | 1 to 255 |
+----+----------+----------+
The VER field is set to X'05' for this ver of the protocol. The
NMETHODS field contains the number of method identifier octets that
appear in the METHODS field.
"""
buf = await self.decodeRead(connection)
if not buf or buf[0] != 0x05:
connection.close()
return
"""
The dstServer selects from one of the methods given in METHODS, and
sends a METHOD selection message:
+----+--------+
|VER | METHOD |
+----+--------+
| 1 | 1 |
+----+--------+
If the selected METHOD is X'FF', none of the methods listed by the
client are acceptable, and the client MUST close the connection.
The values currently defined for METHOD are:
o X'00' NO AUTHENTICATION REQUIRED
o X'01' GSSAPI
o X'02' USERNAME/PASSWORD
o X'03' to X'7F' IANA ASSIGNED
o X'80' to X'FE' RESERVED FOR PRIVATE METHODS
o X'FF' NO ACCEPTABLE METHODS
The client and server then enter a method-specific sub-negotiation.
"""
await self.encodeWrite(connection, bytearray((0x05, 0x00)))
"""
The SOCKS request is formed as follows:
+----+-----+-------+------+----------+----------+
|VER | CMD | RSV | ATYP | DST.ADDR | DST.PORT |
+----+-----+-------+------+----------+----------+
| 1 | 1 | X'00' | 1 | Variable | 2 |
+----+-----+-------+------+----------+----------+
Where:
o VER protocol version: X'05'
o CMD
o CONNECT X'01'
o BIND X'02'
o UDP ASSOCIATE X'03'
o RSV RESERVED
o ATYP address type of following address
o IP V4 address: X'01'
o DOMAINNAME: X'03'
o IP V6 address: X'04'
o DST.ADDR desired destination address
o DST.PORT desired destination port in network octet
order
"""
buf = await self.decodeRead(connection)
if len(buf) < 7:
connection.close()
return
if buf[1] != 0x01:
connection.close()
return
dstIP = None
dstPort = buf[-2:]
dstPort = int(dstPort.hex(), 16)
dstFamily = None
if buf[3] == 0x01:
# ipv4
dstIP = socket.inet_ntop(socket.AF_INET, buf[4:4 + 4])
dstAddress = net.Address(ip=dstIP, port=dstPort)
dstFamily = socket.AF_INET
elif buf[3] == 0x03:
# domain
dstIP = buf[5:-2].decode()
dstAddress = net.Address(ip=dstIP, port=dstPort)
elif buf[3] == 0x04:
# ipv6
dstIP = socket.inet_ntop(socket.AF_INET6, buf[4:4 + 16])
dstAddress = (dstIP, dstPort, 0, 0)
dstFamily = socket.AF_INET6
else:
connection.close()
return
dstServer = None
if dstFamily:
try:
dstServer = socket.socket(
family=dstFamily, type=socket.SOCK_STREAM)
dstServer.setblocking(False)
await self.loop.sock_connect(dstServer, dstAddress)
except OSError:
if dstServer is not None:
dstServer.close()
dstServer = None
else:
host, port = dstAddress
for res in await self.loop.getaddrinfo(host, port):
dstFamily, socktype, proto, _, dstAddress = res
try:
dstServer = socket.socket(dstFamily, socktype, proto)
dstServer.setblocking(False)
await self.loop.sock_connect(dstServer, dstAddress)
break
except OSError:
if dstServer is not None:
dstServer.close()
dstServer = None
if dstFamily is None:
return
"""
The SOCKS request information is sent by the client as soon as it has
established a connection to the SOCKS server, and completed the
authentication negotiations. The server evaluates the request, and
returns a reply formed as follows:
+----+-----+-------+------+----------+----------+
|VER | REP | RSV | ATYP | BND.ADDR | BND.PORT |
+----+-----+-------+------+----------+----------+
| 1 | 1 | X'00' | 1 | Variable | 2 |
+----+-----+-------+------+----------+----------+
Where:
o VER protocol version: X'05'
o REP Reply field:
o X'00' succeeded
o X'01' general SOCKS server failure
o X'02' connection not allowed by ruleset
o X'03' Network unreachable
o X'04' Host unreachable
o X'05' Connection refused
o X'06' TTL expired
o X'07' Command not supported
o X'08' Address type not supported
o X'09' to X'FF' unassigned
o RSV RESERVED
o ATYP address type of following address
"""
await self.encodeWrite(connection,
bytearray((0x05, 0x00, 0x00, 0x01, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00)))
def cleanUp(task):
"""
Close the socket when they succeeded or had an exception.
"""
dstServer.close()
connection.close()
conn2dst = asyncio.ensure_future(
self.decodeCopy(dstServer, connection))
dst2conn = asyncio.ensure_future(
self.encodeCopy(connection, dstServer))
task = asyncio.ensure_future(
asyncio.gather(
conn2dst, dst2conn, loop=self.loop, return_exceptions=True))
task.add_done_callback(cleanUp)