wsocks 0.0.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
wsocks-0.0.2/PKG-INFO ADDED
@@ -0,0 +1,157 @@
1
+ Metadata-Version: 2.1
2
+ Name: wsocks
3
+ Version: 0.0.2
4
+ Summary: UNKNOWN
5
+ Home-page: https://github.com/sazima/WSocks
6
+ License: UNKNOWN
7
+ Platform: UNKNOWN
8
+ Requires-Python: >=3.6
9
+ Description-Content-Type: text/markdown
10
+
11
+ # SOCKS5-WebSocket 代理
12
+
13
+ 基于 WebSocket 的 SOCKS5 代理工具,用于网络穿透和流量转发。
14
+
15
+ ## 原理
16
+
17
+ ```
18
+ 本地应用 <--SOCKS5--> 客户端 <--WebSocket--> 服务端 <--TCP--> 目标服务器
19
+ ```
20
+
21
+ - **客户端**:在本地启动 SOCKS5 服务器,将流量通过 WebSocket 转发到远程服务端
22
+ - **服务端**:接收 WebSocket 连接,代理访问目标服务器
23
+ - 使用 WebSocket 连接池, 提升并发性能
24
+ - 通过密码和消息签名保证连接安全
25
+
26
+
27
+
28
+ ## 安装
29
+
30
+ ```bash
31
+ pip install wsocks
32
+ ```
33
+
34
+
35
+ ## 使用方法
36
+
37
+ ### 1. 服务端配置
38
+
39
+ 编辑 `config_server.json`:
40
+
41
+ ```json
42
+ {
43
+ "server": {
44
+ "host": "0.0.0.0",
45
+ "port": 8888,
46
+ "path": "/ws",
47
+ "password": "your-password-here"
48
+ },
49
+ "log_level": "INFO"
50
+ }
51
+ ```
52
+
53
+ ### 2. 启动服务端
54
+
55
+ 在有公网 IP 的服务器上运行:
56
+
57
+ ```bash
58
+ wsocks_server -c config_server.json
59
+ ```
60
+
61
+ ### 3. 客户端配置
62
+
63
+ 编辑 `config_client.json`:
64
+
65
+ ```json
66
+ {
67
+ "server": {
68
+ "url": "ws://your-server.com:8888/ws",
69
+ "password": "your-password-here",
70
+ "compression": true,
71
+ "ws_pool_size": 8,
72
+ "heartbeat_enabled": true,
73
+ "heartbeat_min": 20,
74
+ "heartbeat_max": 50
75
+ },
76
+ "local": {
77
+ "host": "127.0.0.1",
78
+ "port": 1080
79
+ },
80
+ "log_level": "INFO"
81
+ }
82
+ ```
83
+
84
+ 推荐自行配置使用wss
85
+
86
+ ### 4. 启动客户端
87
+
88
+ ```bash
89
+ wsocks_client -c config_client.json
90
+ ```
91
+
92
+ ### 5. 配置代理
93
+
94
+ 在浏览器或应用中设置 SOCKS5 代理:
95
+ - 服务器:`127.0.0.1`
96
+ - 端口:`1080`
97
+
98
+ ## 配置参数说明
99
+
100
+ ### 服务端参数
101
+
102
+ | 参数 | 说明 | 默认值 |
103
+ |------|------|--------|
104
+ | host | 监听地址 | 0.0.0.0 |
105
+ | port | 监听端口 | 8888 |
106
+ | password | 连接密码 | - |
107
+ | timeout | 连接超时(秒) | 30 |
108
+ | max_connections | 最大并发连接数 | 1000 |
109
+
110
+ ### 客户端参数
111
+
112
+ | 参数 | 说明 | 默认值 |
113
+ |------|------|--------|
114
+ | server.url | 服务端地址 | - |
115
+ | server.password | 连接密码 | - |
116
+ | server.compression | 启用数据压缩 | true |
117
+ | server.ws_pool_size | WebSocket 连接池大小 | 8 |
118
+ | server.heartbeat_enabled | 启用应用层随机心跳 | true |
119
+ | server.heartbeat_min | 心跳最小间隔(秒) | 20 |
120
+ | server.heartbeat_max | 心跳最大间隔(秒) | 50 |
121
+ | local.port | 本地 SOCKS5 端口 | 1080 |
122
+
123
+
124
+ ## 对比
125
+ 同一服务器与v2ray对比
126
+ ![对比图](./speed_test/img.png)
127
+
128
+ ## 常见问题
129
+
130
+ **无法连接服务端**
131
+ - 检查服务端是否运行
132
+ - 确认防火墙端口已开放
133
+ - 验证密码是否一致
134
+
135
+ **上传速度慢**
136
+ - 增加 `ws_pool_size`(建议 8-32)
137
+
138
+ **连接频繁断开**
139
+ - 调整 `ping_interval` 和 `ping_timeout`
140
+ - 增加服务端 `timeout` 值
141
+ -
142
+
143
+ ## 安全建议
144
+
145
+ - 修改默认密码,使用强密码
146
+ - 生产环境使用 WSS(WebSocket over TLS)
147
+ - 配置防火墙限制访问 IP
148
+
149
+ ## 许可证
150
+
151
+ MIT License
152
+
153
+ ## 注意事项
154
+
155
+ 本工具仅供学习和合法用途使用,请遵守当地法律法规。
156
+
157
+
wsocks-0.0.2/README.md ADDED
@@ -0,0 +1,145 @@
1
+ # SOCKS5-WebSocket 代理
2
+
3
+ 基于 WebSocket 的 SOCKS5 代理工具,用于网络穿透和流量转发。
4
+
5
+ ## 原理
6
+
7
+ ```
8
+ 本地应用 <--SOCKS5--> 客户端 <--WebSocket--> 服务端 <--TCP--> 目标服务器
9
+ ```
10
+
11
+ - **客户端**:在本地启动 SOCKS5 服务器,将流量通过 WebSocket 转发到远程服务端
12
+ - **服务端**:接收 WebSocket 连接,代理访问目标服务器
13
+ - 使用 WebSocket 连接池, 提升并发性能
14
+ - 通过密码和消息签名保证连接安全
15
+
16
+
17
+
18
+ ## 安装
19
+
20
+ ```bash
21
+ pip install wsocks
22
+ ```
23
+
24
+
25
+ ## 使用方法
26
+
27
+ ### 1. 服务端配置
28
+
29
+ 编辑 `config_server.json`:
30
+
31
+ ```json
32
+ {
33
+ "server": {
34
+ "host": "0.0.0.0",
35
+ "port": 8888,
36
+ "path": "/ws",
37
+ "password": "your-password-here"
38
+ },
39
+ "log_level": "INFO"
40
+ }
41
+ ```
42
+
43
+ ### 2. 启动服务端
44
+
45
+ 在有公网 IP 的服务器上运行:
46
+
47
+ ```bash
48
+ wsocks_server -c config_server.json
49
+ ```
50
+
51
+ ### 3. 客户端配置
52
+
53
+ 编辑 `config_client.json`:
54
+
55
+ ```json
56
+ {
57
+ "server": {
58
+ "url": "ws://your-server.com:8888/ws",
59
+ "password": "your-password-here",
60
+ "compression": true,
61
+ "ws_pool_size": 8,
62
+ "heartbeat_enabled": true,
63
+ "heartbeat_min": 20,
64
+ "heartbeat_max": 50
65
+ },
66
+ "local": {
67
+ "host": "127.0.0.1",
68
+ "port": 1080
69
+ },
70
+ "log_level": "INFO"
71
+ }
72
+ ```
73
+
74
+ 推荐自行配置使用wss
75
+
76
+ ### 4. 启动客户端
77
+
78
+ ```bash
79
+ wsocks_client -c config_client.json
80
+ ```
81
+
82
+ ### 5. 配置代理
83
+
84
+ 在浏览器或应用中设置 SOCKS5 代理:
85
+ - 服务器:`127.0.0.1`
86
+ - 端口:`1080`
87
+
88
+ ## 配置参数说明
89
+
90
+ ### 服务端参数
91
+
92
+ | 参数 | 说明 | 默认值 |
93
+ |------|------|--------|
94
+ | host | 监听地址 | 0.0.0.0 |
95
+ | port | 监听端口 | 8888 |
96
+ | password | 连接密码 | - |
97
+ | timeout | 连接超时(秒) | 30 |
98
+ | max_connections | 最大并发连接数 | 1000 |
99
+
100
+ ### 客户端参数
101
+
102
+ | 参数 | 说明 | 默认值 |
103
+ |------|------|--------|
104
+ | server.url | 服务端地址 | - |
105
+ | server.password | 连接密码 | - |
106
+ | server.compression | 启用数据压缩 | true |
107
+ | server.ws_pool_size | WebSocket 连接池大小 | 8 |
108
+ | server.heartbeat_enabled | 启用应用层随机心跳 | true |
109
+ | server.heartbeat_min | 心跳最小间隔(秒) | 20 |
110
+ | server.heartbeat_max | 心跳最大间隔(秒) | 50 |
111
+ | local.port | 本地 SOCKS5 端口 | 1080 |
112
+
113
+
114
+ ## 对比
115
+ 同一服务器与v2ray对比
116
+ ![对比图](./speed_test/img.png)
117
+
118
+ ## 常见问题
119
+
120
+ **无法连接服务端**
121
+ - 检查服务端是否运行
122
+ - 确认防火墙端口已开放
123
+ - 验证密码是否一致
124
+
125
+ **上传速度慢**
126
+ - 增加 `ws_pool_size`(建议 8-32)
127
+
128
+ **连接频繁断开**
129
+ - 调整 `ping_interval` 和 `ping_timeout`
130
+ - 增加服务端 `timeout` 值
131
+ -
132
+
133
+ ## 安全建议
134
+
135
+ - 修改默认密码,使用强密码
136
+ - 生产环境使用 WSS(WebSocket over TLS)
137
+ - 配置防火墙限制访问 IP
138
+
139
+ ## 许可证
140
+
141
+ MIT License
142
+
143
+ ## 注意事项
144
+
145
+ 本工具仅供学习和合法用途使用,请遵守当地法律法规。
wsocks-0.0.2/setup.cfg ADDED
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
wsocks-0.0.2/setup.py ADDED
@@ -0,0 +1,38 @@
1
+ import setuptools
2
+ version = '0.0.2'
3
+
4
+ with open("README.md", "r", encoding="utf8") as f:
5
+ readme = f.read()
6
+
7
+ setuptools.setup(
8
+ name='wsocks',
9
+ version=version,
10
+
11
+ # 自动查找所有包
12
+ packages=setuptools.find_packages(),
13
+
14
+ # 包含所有包数据
15
+ include_package_data=True,
16
+
17
+ # 元数据
18
+ long_description=readme,
19
+ long_description_content_type="text/markdown",
20
+ url='https://github.com/sazima/WSocks',
21
+
22
+ # 入口点
23
+ entry_points={
24
+ 'console_scripts': [
25
+ 'wsocks_client=wsocks.run_client:main',
26
+ 'wsocks_server=wsocks.run_server:main',
27
+ ],
28
+ },
29
+
30
+ # 依赖
31
+ install_requires=['tornado', 'websockets'],
32
+
33
+ # 使用 wheel 格式而不是 egg
34
+ zip_safe=False,
35
+
36
+ # Python 版本要求
37
+ python_requires='>=3.6',
38
+ )
File without changes
@@ -0,0 +1 @@
1
+ # Client modules for SOCKS5-WS Proxy
@@ -0,0 +1,276 @@
1
+ import socket
2
+ import struct
3
+ import asyncio
4
+ import os
5
+ from typing import Dict, Optional
6
+ from wsocks.common.logger import setup_logger
7
+ from wsocks.common.protocol import Protocol, MSG_TYPE_CONNECT, MSG_TYPE_DATA, MSG_TYPE_CLOSE
8
+
9
+ logger = setup_logger()
10
+
11
+ class SOCKS5Connection:
12
+ """SOCKS5 连接"""
13
+ def __init__(self, client_socket: socket.socket, conn_id: bytes, ws_client):
14
+ self.client_socket = client_socket
15
+ self.conn_id = conn_id
16
+ self.ws_client = ws_client
17
+ self.running = True
18
+ self.connect_event = asyncio.Event()
19
+ self.connect_success = False
20
+ self.connect_error = None
21
+
22
+ async def handle(self):
23
+ """处理连接"""
24
+ try:
25
+ # SOCKS5 握手
26
+ await self.socks5_handshake()
27
+
28
+ # SOCKS5 连接请求(读取目标地址,但暂不回复)
29
+ target_addr, target_port = await self.socks5_connect_request_parse()
30
+
31
+ logger.info(f"[{self.conn_id.hex()}] Connecting to {target_addr}:{target_port}")
32
+
33
+ # 发送连接请求到服务端
34
+ connect_data = {
35
+ 'host': target_addr,
36
+ 'port': target_port
37
+ }
38
+ await self.ws_client.send_message(
39
+ MSG_TYPE_CONNECT,
40
+ self.conn_id,
41
+ str(connect_data).encode()
42
+ )
43
+
44
+ # 等待服务端连接响应(最多30秒)
45
+ try:
46
+ await asyncio.wait_for(self.connect_event.wait(), timeout=30.0)
47
+ except asyncio.TimeoutError:
48
+ logger.error(f"[{self.conn_id.hex()}] Connect timeout (30s)")
49
+ await self.socks5_connect_response(success=False, error_msg="Connection timeout")
50
+ return
51
+
52
+ # 检查连接结果
53
+ if self.connect_success:
54
+ # 连接成功,回复 SOCKS5 客户端
55
+ await self.socks5_connect_response(success=True)
56
+ # 开始转发数据
57
+ await self.forward_data()
58
+ else:
59
+ # 连接失败,回复错误
60
+ error_msg = self.connect_error or "Connection failed"
61
+ logger.error(f"[{self.conn_id.hex()}] Connect failed: {error_msg}")
62
+ await self.socks5_connect_response(success=False, error_msg=error_msg)
63
+
64
+ except Exception as e:
65
+ logger.error(f"[{self.conn_id.hex()}] Error: {e}")
66
+ finally:
67
+ await self.close()
68
+
69
+ async def socks5_handshake(self):
70
+ """SOCKS5 握手"""
71
+ # 读取客户端握手请求
72
+ data = await asyncio.get_event_loop().sock_recv(self.client_socket, 2)
73
+ version, nmethods = struct.unpack('!BB', data)
74
+
75
+ if version != 5:
76
+ raise Exception(f"Unsupported SOCKS version: {version}")
77
+
78
+ # 读取认证方法
79
+ methods = await asyncio.get_event_loop().sock_recv(self.client_socket, nmethods)
80
+
81
+ # 回复:无需认证
82
+ response = struct.pack('!BB', 5, 0)
83
+ await asyncio.get_event_loop().sock_sendall(self.client_socket, response)
84
+
85
+ async def socks5_connect_request_parse(self):
86
+ """解析 SOCKS5 连接请求(不回复)"""
87
+ # 读取请求头
88
+ data = await asyncio.get_event_loop().sock_recv(self.client_socket, 4)
89
+ version, cmd, _, atyp = struct.unpack('!BBBB', data)
90
+
91
+ if version != 5:
92
+ raise Exception(f"Unsupported SOCKS version: {version}")
93
+
94
+ if cmd != 1: # 只支持 CONNECT
95
+ raise Exception(f"Unsupported command: {cmd}")
96
+
97
+ # 读取目标地址
98
+ if atyp == 1: # IPv4
99
+ addr_data = await asyncio.get_event_loop().sock_recv(self.client_socket, 4)
100
+ target_addr = socket.inet_ntoa(addr_data)
101
+ elif atyp == 3: # 域名
102
+ addr_len = await asyncio.get_event_loop().sock_recv(self.client_socket, 1)
103
+ addr_len = struct.unpack('!B', addr_len)[0]
104
+ addr_data = await asyncio.get_event_loop().sock_recv(self.client_socket, addr_len)
105
+ target_addr = addr_data.decode('utf-8')
106
+ elif atyp == 4: # IPv6
107
+ addr_data = await asyncio.get_event_loop().sock_recv(self.client_socket, 16)
108
+ target_addr = socket.inet_ntop(socket.AF_INET6, addr_data)
109
+ else:
110
+ raise Exception(f"Unsupported address type: {atyp}")
111
+
112
+ # 读取目标端口
113
+ port_data = await asyncio.get_event_loop().sock_recv(self.client_socket, 2)
114
+ target_port = struct.unpack('!H', port_data)[0]
115
+
116
+ return target_addr, target_port
117
+
118
+ async def socks5_connect_response(self, success: bool, error_msg: str = ""):
119
+ """回复 SOCKS5 连接结果"""
120
+ if success:
121
+ # 回复连接成功
122
+ response = struct.pack('!BBBB', 5, 0, 0, 1) # 成功
123
+ response += socket.inet_aton('0.0.0.0') # 绑定地址
124
+ response += struct.pack('!H', 0) # 绑定端口
125
+ else:
126
+ # 回复连接失败(错误码 1 = 一般性 SOCKS 服务器故障)
127
+ response = struct.pack('!BBBB', 5, 1, 0, 1) # 失败
128
+ response += socket.inet_aton('0.0.0.0') # 绑定地址
129
+ response += struct.pack('!H', 0) # 绑定端口
130
+
131
+ try:
132
+ await asyncio.get_event_loop().sock_sendall(self.client_socket, response)
133
+ except Exception as e:
134
+ logger.error(f"[{self.conn_id.hex()}] Failed to send SOCKS5 response: {e}")
135
+
136
+ async def forward_data(self):
137
+ """转发数据"""
138
+ loop = asyncio.get_event_loop()
139
+
140
+ while self.running:
141
+ try:
142
+ # 从本地客户端读取数据(使用 64KB 缓冲区,移除超时)
143
+ data = await loop.sock_recv(self.client_socket, 65536)
144
+
145
+ if not data:
146
+ logger.info(f"[{self.conn_id.hex()}] Client closed")
147
+ break
148
+
149
+ # 发送到服务端
150
+ await self.ws_client.send_message(
151
+ MSG_TYPE_DATA,
152
+ self.conn_id,
153
+ data
154
+ )
155
+
156
+ except asyncio.CancelledError:
157
+ logger.debug(f"[{self.conn_id.hex()}] Forward task cancelled")
158
+ break
159
+ except OSError as e:
160
+ # Socket 已关闭
161
+ if e.errno == 9: # Bad file descriptor
162
+ logger.debug(f"[{self.conn_id.hex()}] Socket already closed")
163
+ else:
164
+ logger.error(f"[{self.conn_id.hex()}] Forward error: {e}")
165
+ break
166
+ except Exception as e:
167
+ logger.error(f"[{self.conn_id.hex()}] Forward error: {e}")
168
+ break
169
+
170
+ async def send_to_client(self, data: bytes):
171
+ """发送数据到本地客户端"""
172
+ try:
173
+ await asyncio.get_event_loop().sock_sendall(self.client_socket, data)
174
+ except Exception as e:
175
+ logger.error(f"[{self.conn_id.hex()}] Send to client error: {e}")
176
+ await self.close()
177
+
178
+ def on_connect_success(self):
179
+ """连接成功回调"""
180
+ logger.info(f"[{self.conn_id.hex()}] Server connected successfully")
181
+ self.connect_success = True
182
+ self.connect_event.set()
183
+
184
+ def on_connect_failed(self, reason: str):
185
+ """连接失败回调"""
186
+ logger.warning(f"[{self.conn_id.hex()}] Server connect failed: {reason}")
187
+ self.connect_success = False
188
+ self.connect_error = reason
189
+ self.connect_event.set()
190
+
191
+ async def close(self, notify_server: bool = True):
192
+ """关闭连接
193
+
194
+ Args:
195
+ notify_server: 是否通知服务端关闭(如果是服务端主动关闭则不需要)
196
+ """
197
+ if not self.running:
198
+ return
199
+
200
+ self.running = False
201
+ logger.info(f"[{self.conn_id.hex()}] Closing connection")
202
+
203
+ if notify_server:
204
+ try:
205
+ # 通知服务端关闭
206
+ await self.ws_client.send_message(MSG_TYPE_CLOSE, self.conn_id, b'')
207
+ except Exception as e:
208
+ logger.debug(f"[{self.conn_id.hex()}] Failed to send close message: {e}")
209
+
210
+ # 先关闭 socket 的发送端,让正在读取的操作能够正常结束
211
+ try:
212
+ self.client_socket.shutdown(socket.SHUT_RDWR)
213
+ except Exception:
214
+ pass
215
+
216
+ # 等待一小段时间,让 forward_data 中的读取操作正常结束
217
+ await asyncio.sleep(0.05)
218
+
219
+ try:
220
+ self.client_socket.close()
221
+ except Exception as e:
222
+ logger.debug(f"[{self.conn_id.hex()}] Failed to close socket: {e}")
223
+
224
+
225
+ class SOCKS5Server:
226
+ """SOCKS5 服务器"""
227
+ def __init__(self, host: str, port: int, ws_client):
228
+ self.host = host
229
+ self.port = port
230
+ self.ws_client = ws_client
231
+ self.connections: Dict[bytes, SOCKS5Connection] = {}
232
+
233
+ async def start(self):
234
+ """启动服务器"""
235
+ server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
236
+ server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
237
+ server_socket.bind((self.host, self.port))
238
+ server_socket.listen(100)
239
+ server_socket.setblocking(False)
240
+
241
+ logger.info(f"SOCKS5 server listening on {self.host}:{self.port}")
242
+
243
+ loop = asyncio.get_event_loop()
244
+
245
+ while True:
246
+ client_socket, addr = await loop.sock_accept(server_socket)
247
+ logger.info(f"New connection from {addr}")
248
+
249
+ # 生成连接 ID
250
+ conn_id = os.urandom(4)
251
+
252
+ # 创建连接处理
253
+ connection = SOCKS5Connection(client_socket, conn_id, self.ws_client)
254
+ self.connections[conn_id] = connection
255
+
256
+ # 异步处理连接
257
+ asyncio.ensure_future(self._handle_connection(connection))
258
+
259
+ async def _handle_connection(self, connection: SOCKS5Connection):
260
+ """处理连接"""
261
+ try:
262
+ await connection.handle()
263
+ except asyncio.CancelledError:
264
+ logger.debug(f"[{connection.conn_id.hex()}] Task cancelled")
265
+ raise
266
+ except Exception as e:
267
+ logger.error(f"[{connection.conn_id.hex()}] Unexpected error: {e}")
268
+ finally:
269
+ # 延迟一点删除连接,给服务端发送关闭消息的时间
270
+ await asyncio.sleep(0.1)
271
+ if connection.conn_id in self.connections:
272
+ del self.connections[connection.conn_id]
273
+
274
+ def get_connection(self, conn_id: bytes) -> Optional[SOCKS5Connection]:
275
+ """获取连接"""
276
+ return self.connections.get(conn_id)