-
Notifications
You must be signed in to change notification settings - Fork 95
/
Copy pathproxy_tcp.c
433 lines (373 loc) · 12.1 KB
/
proxy_tcp.c
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
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
// SPDX-License-Identifier: GPL-3.0-only
/*
* Copyright (c) 2023 Dengfeng Liu <liudf0716@gmail.com>
*/
#include <stdlib.h>
#include <stdio.h>
#include <assert.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <errno.h>
#include <syslog.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <event2/bufferevent.h>
#include <event2/buffer.h>
#include <event2/listener.h>
#include <event2/event.h>
#include "debug.h"
#include "uthash.h"
#include "common.h"
#include "proxy.h"
#include "config.h"
#include "tcpmux.h"
#include "control.h"
/** @brief Maximum buffer size for SOCKS5 protocol data */
#define SOCKS5_BUFFER_SIZE 2048
/**
* @brief Validates if a buffer contains a valid SOCKS5 protocol header
*
* Checks if the given buffer contains a valid SOCKS5 protocol header with:
* - Version: 0x05 (SOCKS5)
* - Command: 0x01 (CONNECT)
* - Reserved: 0x00
*
* @param buf Buffer containing the SOCKS5 header
* @param len Length of the buffer
* @return 1 if valid SOCKS5 header, 0 if invalid
*/
static int is_socks5(const uint8_t *buf, int len)
{
if (!buf || len < 3) {
return 0;
}
return (buf[0] == 0x05 && // SOCKS5 version
buf[1] == 0x01 && // CONNECT command
buf[2] == 0x00); // Reserved field
}
/**
* @brief Parse SOCKS5 address structure from ring buffer
*
* Parses a SOCKS5 address structure which can be one of:
* - IPv4 (type 0x01): 4 bytes address + 2 bytes port
* - IPv6 (type 0x04): 16 bytes address + 2 bytes port
* - Domain (type 0x03): 1 byte length + domain + 2 bytes port
*
* @param rb Ring buffer containing the SOCKS5 address data
* @param len Total length of data in ring buffer
* @param offset Returns number of bytes processed
* @param addr Output parameter for parsed address structure
* @return 1 on success, 0 on failure/invalid format
*/
static int parse_socks5_addr(struct ring_buffer *rb, int len, int *offset,
struct socks5_addr *addr)
{
assert(addr && rb && offset);
assert(len > 0);
// Initialize
memset(addr, 0, sizeof(struct socks5_addr));
uint8_t buf[256] = {0}; // Increased buffer size to handle domains
// Read address type
rx_ring_buffer_pop(rb, buf, 1);
*offset = 1;
addr->type = buf[0];
// Parse based on address type
switch(addr->type) {
case 0x01: // IPv4
if (len < 7) return 0;
rx_ring_buffer_pop(rb, buf+1, 6);
memcpy(addr->addr, buf+1, 4); // IPv4 address
memcpy(&addr->port, buf+5, 2); // Port
*offset = 7;
break;
case 0x04: // IPv6
if (len < 19) return 0;
rx_ring_buffer_pop(rb, buf+1, 18);
memcpy(addr->addr, buf+1, 16); // IPv6 address
memcpy(&addr->port, buf+17, 2); // Port
*offset = 19;
break;
case 0x03: // Domain name
if (len < 2) return 0;
rx_ring_buffer_pop(rb, buf+1, 1); // Domain length
uint8_t domain_len = buf[1];
if (len < domain_len + 4) return 0;
rx_ring_buffer_pop(rb, buf+2, domain_len + 2);
memcpy(addr->addr, buf+2, domain_len); // Domain
memcpy(&addr->port, buf+2+domain_len, 2); // Port
*offset = domain_len + 4;
break;
default:
return 0;
}
return 1;
}
/**
* @brief Establish a SOCKS5 proxy connection based on destination address
*
* This function creates and establishes a proxy connection to the target address
* specified in the SOCKS5 address structure. It supports:
* - IPv4 addresses (type 0x01)
* - Domain names (type 0x03)
* - IPv6 addresses (type 0x04)
*
* @param client Proxy client structure containing event base
* @param addr SOCKS5 address structure with connection details
* @return Configured bufferevent on success, NULL on failure
*/
static struct bufferevent *socks5_proxy_connect(struct proxy_client *client, struct socks5_addr *addr)
{
struct bufferevent *bev = NULL;
// Create new socket bufferevent
bev = bufferevent_socket_new(client->base, -1, BEV_OPT_CLOSE_ON_FREE);
if (!bev) {
debug(LOG_ERR, "Failed to create bufferevent for SOCKS5 proxy");
return NULL;
}
int connect_result = -1;
switch(addr->type) {
case 0x01: { // IPv4
struct sockaddr_in sin = {
.sin_family = AF_INET,
.sin_port = addr->port
};
memcpy(&sin.sin_addr, addr->addr, 4);
// Log connection details
char ip[INET_ADDRSTRLEN];
inet_ntop(AF_INET, addr->addr, ip, INET_ADDRSTRLEN);
debug(LOG_DEBUG, "SOCKS5 connecting to IPv4: %s:%d", ip, ntohs(addr->port));
connect_result = bufferevent_socket_connect(bev,
(struct sockaddr *)&sin, sizeof(sin));
break;
}
case 0x03: // Domain name
debug(LOG_DEBUG, "SOCKS5 connecting to domain: %s:%d",
addr->addr, ntohs(addr->port));
connect_result = bufferevent_socket_connect_hostname(bev,
get_main_control()->dnsbase, AF_INET,
(char *)addr->addr, ntohs(addr->port));
break;
case 0x04: { // IPv6
struct sockaddr_in6 sin6 = {
.sin6_family = AF_INET6,
.sin6_port = addr->port
};
memcpy(&sin6.sin6_addr, addr->addr, 16);
connect_result = bufferevent_socket_connect(bev,
(struct sockaddr *)&sin6, sizeof(sin6));
break;
}
default:
debug(LOG_ERR, "Invalid SOCKS5 address type: %d", addr->type);
bufferevent_free(bev);
return NULL;
}
if (connect_result < 0) {
debug(LOG_ERR, "Failed to connect SOCKS5 proxy (type %d)", addr->type);
bufferevent_free(bev);
return NULL;
}
// Setup callbacks and enable bufferevent
bufferevent_setcb(bev, tcp_proxy_c2s_cb, NULL, xfrp_proxy_event_cb, client);
bufferevent_enable(bev, EV_READ | EV_WRITE);
return bev;
}
/**
* @brief Legacy SOCKS5 protocol handler
*
* This function implements a simplified SOCKS5 protocol handler that supports:
* - Initial direct connection request (SOCKS5_INIT)
* - Data forwarding in established state (SOCKS5_ESTABLISHED)
*
* @param client The proxy client structure
* @param rb Ring buffer containing incoming data
* @param len Length of data in ring buffer
* @return Number of bytes processed, 0 on error
*
* @deprecated Use handle_socks5() instead which implements full SOCKS5 protocol
*/
uint32_t handle_ss5(struct proxy_client *client, struct ring_buffer *rb, int len)
{
uint32_t bytes_processed = 0;
// Handle established connection state
if (client->state == SOCKS5_ESTABLISHED) {
assert(client->local_proxy_bev);
tx_ring_buffer_write(client->local_proxy_bev, rb, len);
return len;
}
// Handle initial connection request
if (client->state == SOCKS5_INIT && len >= 7) {
debug(LOG_DEBUG, "Processing initial SOCKS5 connection request, len: %d", len);
// Parse destination address
int addr_len = 0;
if (!parse_socks5_addr(rb, len, &addr_len, &client->remote_addr)) {
debug(LOG_ERR, "Failed to parse SOCKS5 address");
return bytes_processed;
}
// Establish proxy connection
client->local_proxy_bev = socks5_proxy_connect(client, &client->remote_addr);
if (!client->local_proxy_bev) {
debug(LOG_ERR, "Failed to establish proxy connection");
return bytes_processed;
}
debug(LOG_DEBUG, "SOCKS5 proxy connection established (parsed %d of %d bytes)",
addr_len, len);
return addr_len;
}
return bytes_processed;
}
/**
* @brief Handles SOCKS5 protocol states and data forwarding
*
* This function implements the SOCKS5 protocol state machine and handles:
* - Initial handshake (SOCKS5_INIT)
* - Authentication negotiation (SOCKS5_HANDSHAKE)
* - Connection establishment (SOCKS5_CONNECT)
*
* @param client The proxy client structure
* @param rb Ring buffer containing incoming data
* @param len Length of data in ring buffer
* @return Number of bytes processed, 0 on error
*/
uint32_t handle_socks5(struct proxy_client *client, struct ring_buffer *rb, int len)
{
uint32_t nret = 0;
// Forward data in established connection state
if (client->state == SOCKS5_CONNECT) {
assert(client->local_proxy_bev);
tx_ring_buffer_write(client->local_proxy_bev, rb, len);
return len;
}
// Handle initial SOCKS5 handshake
if (client->state == SOCKS5_INIT && len >= 3) {
debug(LOG_DEBUG, "Processing SOCKS5 initial handshake, len: %d", len);
uint8_t buf[3] = {0};
rx_ring_buffer_pop(rb, buf, 3);
if (buf[0] != 0x5 || buf[1] != 0x1 || buf[2] != 0x0) {
debug(LOG_ERR, "Invalid SOCKS5 handshake");
return nret;
}
// Send handshake response
buf[1] = 0x0; // No authentication required
tmux_stream_write(client->ctl_bev, buf, 3, &client->stream);
client->state = SOCKS5_HANDSHAKE;
return 3;
}
// Handle connection request
if (client->state == SOCKS5_HANDSHAKE && len >= 10) {
debug(LOG_DEBUG, "Processing SOCKS5 connection request, len: %d", len);
uint8_t buf[3] = {0};
rx_ring_buffer_pop(rb, buf, 3);
if (!is_socks5(buf, 3)) {
debug(LOG_ERR, "Invalid SOCKS5 request format");
return nret;
}
int offset = 0;
if (!parse_socks5_addr(rb, len, &offset, &client->remote_addr)) {
debug(LOG_ERR, "Failed to parse SOCKS5 address");
return nret;
}
client->local_proxy_bev = socks5_proxy_connect(client, &client->remote_addr);
if (!client->local_proxy_bev) {
debug(LOG_ERR, "Failed to establish proxy connection");
return nret;
}
assert(len == offset + 3);
return len;
}
// Handle invalid protocol state
debug(LOG_ERR, "Invalid SOCKS5 protocol state");
if (client->local_proxy_bev) {
bufferevent_free(client->local_proxy_bev);
}
return nret;
}
/**
* @brief Callback function handling data transfer from client to server in TCP proxy
*
* This function processes data received from the client-side bufferevent and forwards
* it to the control connection. It supports both regular TCP proxy mode and TCP
* multiplexing mode.
*
* @param bev The bufferevent structure containing client data
* @param ctx Context pointer containing proxy client information
*
* Operation flow:
* 1. Validates client and control connection
* 2. Checks for available data in source buffer
* 3. If TCP multiplexing is disabled, directly forwards data to control connection
* 4. If TCP multiplexing is enabled, reads data into temporary buffer and writes
* to multiplexed stream
*
* @note In multiplexing mode, if partial write occurs, the read event is disabled
* to prevent buffer overflow
*/
void tcp_proxy_c2s_cb(struct bufferevent *bev, void *ctx)
{
struct proxy_client *client = (struct proxy_client *)ctx;
if (!client || !client->ctl_bev) {
debug(LOG_ERR, "Invalid client or control connection");
return;
}
struct evbuffer *src = bufferevent_get_input(bev);
size_t len = evbuffer_get_length(src);
if (len == 0) {
debug(LOG_DEBUG, "No data to read from client");
return;
}
struct common_conf *c_conf = get_common_config();
if (!c_conf->tcp_mux) {
struct evbuffer *dst = bufferevent_get_output(client->ctl_bev);
evbuffer_add_buffer(dst, src);
return;
}
uint8_t *buf = calloc(1, len);
if (!buf) {
debug(LOG_ERR, "Failed to allocate memory for buffer");
return;
}
size_t nr = bufferevent_read(bev, buf, len);
if (nr != len) {
debug(LOG_ERR, "Failed to read complete data: expected %zu, got %zu", len, nr);
free(buf);
return;
}
uint32_t written = tmux_stream_write(client->ctl_bev, buf, len, &client->stream);
if (written < len) {
debug(LOG_DEBUG, "Stream %d: Partial write %u/%zu bytes, disabling read",
client->stream.id, written, len);
bufferevent_disable(bev, EV_READ);
}
free(buf);
}
/**
* @brief Callback function for handling data transfer from server to client in TCP proxy
*
* This function is called when data is available to be read from the server's bufferevent
* and needs to be forwarded to the client.
*
* @param bev The bufferevent structure containing the server's buffer
* @param ctx The context pointer containing user-defined data (typically proxy session information)
*/
void tcp_proxy_s2c_cb(struct bufferevent *bev, void *ctx)
{
struct common_conf *c_conf = get_common_config();
struct proxy_client *client = (struct proxy_client *)ctx;
if (!client || !client->local_proxy_bev) {
debug(LOG_ERR, "Invalid client or local proxy connection");
return;
}
struct evbuffer *src = bufferevent_get_input(bev);
size_t len = evbuffer_get_length(src);
if (len == 0) {
debug(LOG_ERR, "No data to read from local service");
return;
}
if (!c_conf->tcp_mux) {
struct evbuffer *dst = bufferevent_get_output(client->local_proxy_bev);
evbuffer_add_buffer(dst, src);
return;
}
debug(LOG_ERR, "impossible to reach here");
}