├── .gitignore
├── Makefile
├── UNLICENSE
├── log.h
├── main.c
├── readme.md
├── s5tunnel.c
├── s5tunnel.h
└── socks5.h
/.gitignore:
--------------------------------------------------------------------------------
1 | *.o
2 | .vscode
3 | s5tunnel
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | CFLAGS=-O2 -Wall -Wextra
2 | TARGETS=s5tunnel
3 | OBJS=main.o s5tunnel.o
4 | CC=cc
5 |
6 | .PHONY: all clean
7 | all: $(TARGETS)
8 |
9 | s5tunnel: $(OBJS)
10 | $(CC) -o s5tunnel $(OBJS) -lpthread
11 |
12 | %.o: %.c
13 | $(CC) -c -o $@ $< $(CFLAGS)
14 |
15 | clean:
16 | rm -f $(TARGETS) $(OBJS)
--------------------------------------------------------------------------------
/UNLICENSE:
--------------------------------------------------------------------------------
1 | This is free and unencumbered software released into the public domain.
2 |
3 | Anyone is free to copy, modify, publish, use, compile, sell, or
4 | distribute this software, either in source code form or as a compiled
5 | binary, for any purpose, commercial or non-commercial, and by any
6 | means.
7 |
8 | In jurisdictions that recognize copyright laws, the author or authors
9 | of this software dedicate any and all copyright interest in the
10 | software to the public domain. We make this dedication for the benefit
11 | of the public at large and to the detriment of our heirs and
12 | successors. We intend this dedication to be an overt act of
13 | relinquishment in perpetuity of all present and future rights to this
14 | software under copyright law.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22 | OTHER DEALINGS IN THE SOFTWARE.
23 |
24 | For more information, please refer to
25 |
--------------------------------------------------------------------------------
/log.h:
--------------------------------------------------------------------------------
1 | #ifndef S5TUNNEL_LOG
2 | #define S5TUNNEL_LOG
3 | #include
4 | #define __log(log_level, fmt, ...) fprintf(stderr, "[" log_level "] %s:%d %s: " fmt, __FILE__, __LINE__, __FUNCTION__, ## __VA_ARGS__)
5 | #define log_info(fmt, ...) __log("INFO ", fmt, ## __VA_ARGS__)
6 | #define log_warn(fmt, ...) __log("WARN ", fmt, ## __VA_ARGS__)
7 | #define log_error(fmt, ...) __log("ERROR", fmt, ## __VA_ARGS__)
8 | #define log_fatal(fmt, ...) __log("FATAL", fmt, ## __VA_ARGS__)
9 |
10 | #ifdef S5TUNNEL_DEBUG
11 | #define log_debug(fmt, ...) __log("DEBUG", fmt, ## __VA_ARGS__)
12 | #else
13 | #define log_debug(fmt, ...)
14 | #endif
15 |
16 | #endif // S5TUNNEL_LOG
--------------------------------------------------------------------------------
/main.c:
--------------------------------------------------------------------------------
1 | #include "s5tunnel.h"
2 | #include "socks5.h"
3 | #include "log.h"
4 | #include
5 | #include
6 | #include
7 | #include
8 | #include
9 |
10 | bool parse_tunnel(const char *s, s5_remote_t **remote, int proto) {
11 | char *str = strdup(s);
12 |
13 | if (!str) {
14 | log_error("strdup(): %s\n", strerror(errno));
15 | return false;
16 | }
17 |
18 | char *tkn, *lhost, *lport, *atyp, *rhost, *rport;
19 | tkn = lhost = lport = atyp = rhost = rport = NULL;
20 | tkn = strtok(str, ",");
21 | bool ok = false;
22 | uint8_t atyp_i = 0xff;
23 |
24 | int pos = 0;
25 | while (tkn) {
26 | /**/ if (pos == 0) lhost = strdup(tkn);
27 | else if (pos == 1) lport = strdup(tkn);
28 | else if (pos == 2) atyp = strdup(tkn);
29 | else if (pos == 3) rhost = strdup(tkn);
30 | else if (pos == 4) rport = strdup(tkn);
31 | else {
32 | log_error("too many options for tunnel specification: %s\n", s);
33 | goto parse_end;
34 | }
35 | tkn = strtok(NULL, ",");
36 | pos++;
37 | }
38 |
39 | if (pos != 5) {
40 | log_error("not enough options for tunnel specification: %s\n", s);
41 | goto parse_end;
42 | }
43 |
44 | if (strcmp(atyp, "ip") == 0) atyp_i = ATYP_IP;
45 | if (strcmp(atyp, "ip6") == 0) atyp_i = ATYP_IP6;
46 | if (strcmp(atyp, "fqdn") == 0) atyp_i = ATYP_FQDN;
47 | if (atyp_i == 0xff) {
48 | log_error("bad remote address type: %s\n", atyp);
49 | goto parse_end;
50 | }
51 |
52 | *remote = (s5_remote_t *) malloc(sizeof(s5_remote_t));
53 | ssize_t sz = mks5addr(atyp_i, rhost, htons(atoi(rport)), &((*remote)->remote)); // FIXME: atoi
54 | if (sz < 0) {
55 | log_error("error creating remote address.\n");
56 | free(*remote);
57 | goto parse_end;
58 | }
59 |
60 | (*remote)->local_host = lhost;
61 | (*remote)->local_port = lport;
62 | (*remote)->remote_type = atyp_i;
63 | (*remote)->remote_len = sz;
64 | (*remote)->protocol = proto;
65 | (*remote)->next = NULL;
66 | ok = true;
67 |
68 | parse_end:
69 | if (str) free(str);
70 | if (!ok && lhost) free(lhost);
71 | if (!ok && lport) free(lport);
72 | if (atyp) free(atyp);
73 | if (rhost) free(rhost);
74 | if (rport) free(rport);
75 | return ok;
76 | }
77 |
78 | void help() {
79 | fprintf(stderr, "usage: s5tunnel -s SERVER_HOST -p SERVER_PORT [-U USER -P PASS] TSPEC [TSPEC...]\n");
80 | fprintf(stderr, "where: TSPEC := PROTO LADDR,LPORT,RTYPE,RADDR,RPORT\n");
81 | fprintf(stderr, " PROTO := { -t | -u }\n");
82 | fprintf(stderr, " RTYPE := { ip | ip6 | fqdn }\n");
83 | }
84 |
85 | void freeconfig(s5_config_t *cfg) {
86 | if (cfg->server_host) free(cfg->server_host);
87 | if (cfg->server_port) free(cfg->server_port);
88 | if (cfg->user) free(cfg->user);
89 | if (cfg->passwd) free(cfg->passwd);
90 |
91 | for (s5_remote_t *r = cfg->remotes; r != NULL; ) {
92 | if (r->local_host) free(r->local_host);
93 | if (r->local_port) free(r->local_port);
94 | if (r->remote) free(r->remote);
95 | s5_remote_t *lst = r;
96 | r = r->next;
97 | free(lst);
98 | }
99 | }
100 |
101 | int main (int argc, char **argv) {
102 | s5_config_t cfg;
103 | memset(&cfg, 0, sizeof(s5_config_t));
104 | s5_remote_t *r = cfg.remotes;
105 |
106 | int c;
107 |
108 | while ((c = getopt(argc, argv, "s:p:U:P:t:u:")) != -1) {
109 | switch (c) {
110 | case 's': cfg.server_host = strdup(optarg); break;
111 | case 'p': cfg.server_port = strdup(optarg); break;
112 | case 'U': cfg.auth_enabled = true; cfg.user = strdup(optarg); break;
113 | case 'P': cfg.auth_enabled = true; cfg.passwd = strdup(optarg); break;
114 | case 't':
115 | case 'u': {
116 | int proto = c == 'u' ? IPPROTO_UDP : IPPROTO_TCP;
117 | s5_remote_t *_r;
118 | if (!parse_tunnel(optarg, &_r, proto)) {
119 | help();
120 | goto err;
121 | }
122 | if (r == NULL) r = cfg.remotes = _r;
123 | else {
124 | r->next = _r;
125 | r = _r;
126 | }
127 | }
128 | }
129 | }
130 |
131 | if (cfg.server_host == NULL || cfg.server_port == NULL || (cfg.auth_enabled && (cfg.passwd == NULL || cfg.user == NULL)) || cfg.remotes == NULL) {
132 | help();
133 | goto err;
134 | }
135 |
136 | s5_run(&cfg);
137 |
138 | err:
139 | freeconfig(&cfg);
140 | return 1;
141 | }
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | s5tunnel
2 | ---
3 |
4 | `s5tunnel` is a simple tool that tunnels local TCP/UDP ports to remote servers through a SOCKS5 proxy.
5 |
6 | ### Usage
7 |
8 | ```
9 | usage: s5tunnel -s SERVER_HOST -p SERVER_PORT [-U USER -P PASS] TSPEC [TSPEC...]
10 | where: TSPEC := PROTO LADDR,LPORT,RTYPE,RADDR,RPORT
11 | PROTO := { -t | -u }
12 | RTYPE := { ip | ip6 | fqdn }
13 | ```
14 |
15 | For example, the following command:
16 |
17 | ```
18 | $ ./s5tunnel -s 127.0.0.1 -p 1080 \
19 | -t 0.0.0.0,9090,ip,202.5.31.222,80 \
20 | -t 0.0.0.0,9091,ip6,2602:feda:4::3,80 \
21 | -t ::,2200,ip,202.5.31.222,22 \
22 | -t 0.0.0.0,9092,fqdn,nat.moe,80 \
23 | -u 0.0.0.0,9053,ip,202.5.31.222,53
24 | ```
25 |
26 | creates five tunnels through SOCKS5 server on `127.0.0.1:1080`:
27 |
28 | local|remote
29 | --|--
30 | `0.0.0.0:9090/tcp`|`202.5.31.222:80/tcp`
31 | `0.0.0.0:9091/tcp`|`[2602:feda:4::3]:80/tcp`
32 | `[::]:2200/tcp`|`202.5.31.222:22/tcp`
33 | `0.0.0.0:9092/tcp`|`nat.moe:80/tcp`
34 | `0.0.0.0:9053/udp`|`202.5.31.222:43/udp`
35 |
36 | ### Installation
37 |
38 | ```
39 | $ git clone https://github.com/nat-lab/s5tunnel
40 | $ cd s5tunnel
41 | $ make
42 | ```
43 |
44 | ### License
45 |
46 | UNLICENSE
--------------------------------------------------------------------------------
/s5tunnel.c:
--------------------------------------------------------------------------------
1 | #include "s5tunnel.h"
2 | #include "socks5.h"
3 | #include "log.h"
4 | #include
5 | #include
6 | #include
7 | #include
8 | #include
9 | #include
10 | #include
11 | #include
12 |
13 | void sockaddr_to_str(const struct sockaddr *sa, char *s, size_t len) {
14 | if (sa->sa_family == AF_INET) inet_ntop(AF_INET, &(((struct sockaddr_in *)sa)->sin_addr), s, len);
15 | else if (sa->sa_family == AF_INET6) inet_ntop(AF_INET6, &(((struct sockaddr_in *)sa)->sin_addr), s, len);
16 | else strncpy(s, "(unknow)", len);
17 | }
18 |
19 | int gai_connect(const char *host, const char *port, int socktype, int protocol) {
20 | log_debug("connecting to %s:%s...\n", host, port);
21 |
22 | struct addrinfo hints, *result;
23 | memset(&hints, 0, sizeof(struct addrinfo));
24 | hints.ai_family = PF_UNSPEC;
25 | hints.ai_flags = AI_V4MAPPED | AI_ADDRCONFIG;
26 | hints.ai_socktype = socktype;
27 | hints.ai_protocol = protocol;
28 | char remote_address_str[INET6_ADDRSTRLEN];
29 |
30 | int s = 0;
31 | if ((s = getaddrinfo(host, port, &hints, &result)) != 0) {
32 | log_fatal("getaddrinfo(): %s\n", gai_strerror(s));
33 | return -1;
34 | }
35 |
36 | for (const struct addrinfo *cur = result; cur != NULL; cur = cur->ai_next) {
37 | sockaddr_to_str(cur->ai_addr, remote_address_str, INET6_ADDRSTRLEN);
38 | log_debug("trying %s...\n", remote_address_str);
39 |
40 | int fd = socket(cur->ai_family, cur->ai_socktype, cur->ai_protocol);
41 | if (fd < 0) {
42 | log_error("socket(): %s\n", strerror(errno));
43 | continue;
44 | }
45 |
46 | if (connect(fd, cur->ai_addr, cur->ai_addrlen) < 0) {
47 | log_error("connect(): %s\n", strerror(errno));
48 | close(fd);
49 | continue;
50 | }
51 |
52 | log_debug("connected to %s.\n", remote_address_str);
53 | freeaddrinfo(result);
54 | return fd;
55 | }
56 |
57 | freeaddrinfo(result);
58 | log_fatal("failed to connect to %s:%s.\n", host, port);
59 | return -1;
60 | }
61 |
62 | int gai_bind(const char *host, const char *port, int socktype, int protocol) {
63 | log_debug("binding on %s:%s...\n", host, port);
64 |
65 | struct addrinfo hints, *result;
66 | memset(&hints, 0, sizeof(struct addrinfo));
67 | hints.ai_family = PF_UNSPEC;
68 | hints.ai_flags = AI_V4MAPPED | AI_ADDRCONFIG | AI_PASSIVE;
69 | hints.ai_socktype = socktype;
70 | hints.ai_protocol = protocol;
71 | char remote_address_str[INET6_ADDRSTRLEN];
72 |
73 | int s = 0;
74 | if ((s = getaddrinfo(host, port, &hints, &result)) != 0) {
75 | log_fatal("getaddrinfo(): %s\n", gai_strerror(s));
76 | return -1;
77 | }
78 |
79 | for (const struct addrinfo *cur = result; cur != NULL; cur = cur->ai_next) {
80 | sockaddr_to_str(cur->ai_addr, remote_address_str, INET6_ADDRSTRLEN);
81 | log_debug("trying %s...\n", remote_address_str);
82 |
83 | int fd = socket(cur->ai_family, cur->ai_socktype, cur->ai_protocol);
84 | if (fd < 0) {
85 | log_error("socket(): %s\n", strerror(errno));
86 | continue;
87 | }
88 |
89 | if (bind(fd, cur->ai_addr, cur->ai_addrlen) < 0) {
90 | log_error("bind(): %s\n", strerror(errno));
91 | close(fd);
92 | continue;
93 | }
94 |
95 | log_debug("binded on %s.\n", remote_address_str);
96 | freeaddrinfo(result);
97 | return fd;
98 | }
99 |
100 | freeaddrinfo(result);
101 | log_fatal("failed to bind on %s:%s.\n", host, port);
102 | return -1;
103 | }
104 |
105 | int s5_new_connection(const s5_config_t *config, const s5_remote_t *remote) {
106 | int fd = gai_connect(config->server_host, config->server_port, SOCK_STREAM, IPPROTO_TCP);
107 | if (fd < 0) {
108 | log_error("can't connect to socks5 server.\n");
109 | return -1;
110 | }
111 |
112 | s5_method_request_t mreq;
113 | mreq.ver = SOCK_VER;
114 | mreq.nmethods = 1;
115 | mreq.methods = config->auth_enabled ? S5_AUTH_USER_PASSWD : S5_AUTH_NONE;
116 | ssize_t sz = write(fd, &mreq, sizeof(s5_method_request_t));
117 | if (sz < 0) {
118 | log_fatal("write(): %s\n", strerror(errno));
119 | goto err_new_conn;
120 | }
121 |
122 | int state = METHOD_SENT;
123 | uint8_t buffer[1024];
124 | uint8_t send_buffer[1024];
125 |
126 | while (1) {
127 | ssize_t len = read(fd, buffer, sizeof(buffer));
128 | if (len < 0) {
129 | log_fatal("read(): %s\n", strerror(errno));
130 | goto err_new_conn;
131 | }
132 |
133 | if (state == METHOD_SENT) {
134 | if (len != sizeof(s5_method_reply_t)) {
135 | log_fatal("bad s5_method_reply message.\n");
136 | goto err_new_conn;
137 | }
138 |
139 | s5_method_reply_t *reply = (s5_method_reply_t *) buffer;
140 |
141 | if (reply->ver != SOCK_VER) {
142 | log_fatal("bad remote server version: %d.\n", reply->ver);
143 | goto err_new_conn;
144 | }
145 |
146 | if (reply->method == S5_AUTH_BAD) {
147 | log_fatal("remote: bad auth.\n");
148 | goto err_new_conn;
149 | }
150 |
151 | if (reply->method != S5_AUTH_NONE && reply->method != S5_AUTH_USER_PASSWD) {
152 | log_fatal("unknow server auth method: %d.\n", reply->method);
153 | goto err_new_conn;
154 | }
155 |
156 | if (reply->method == S5_AUTH_NONE) {
157 | if (config->auth_enabled) {
158 | log_fatal("remote server does not support auth.\n");
159 | goto err_new_conn;
160 | }
161 | state = AUTH_SENT;
162 | }
163 |
164 | if (reply->method == S5_AUTH_USER_PASSWD) {
165 | if (!config->auth_enabled) {
166 | log_fatal("remote server need auth.\n");
167 | goto err_new_conn;
168 | }
169 | size_t ulen = strnlen(config->user, 255);
170 | size_t plen = strnlen(config->passwd, 255);
171 | size_t pkt_len = ulen + plen + 3;
172 | send_buffer[0] = 1;
173 | send_buffer[1] = ulen;
174 | memcpy(send_buffer + 2, config->user, ulen);
175 | send_buffer[ulen + 2] = plen;
176 | memcpy(send_buffer + ulen + 3, config->passwd, plen);
177 | write(fd, send_buffer, pkt_len);
178 | state = AUTH_SENT;
179 | }
180 | }
181 |
182 | if (state == AUTH_SENT) {
183 | if (config->auth_enabled) {
184 | if (len != 2) {
185 | log_fatal("bad auth reply message from server: invalid len (%zu).\n", len);
186 | goto err_new_conn;
187 | }
188 |
189 | if (buffer[0] != 1) {
190 | log_fatal("bad auth reply message from server: bad version (%d).\n", buffer[0]);
191 | goto err_new_conn;
192 | }
193 |
194 | if (buffer[1] != 0) {
195 | log_fatal("auth failed.\n");
196 | goto err_new_conn;
197 | }
198 | }
199 |
200 | s5_request_hdr_t *request = (s5_request_hdr_t *) send_buffer;
201 | request->ver = 5;
202 | request->cmd = remote->protocol == IPPROTO_UDP ? CMD_UDP_ASSOC : CMD_CONNECT;
203 | request->rsv = 0;
204 | request->atyp = remote->remote_type;
205 |
206 | uint8_t *buf_ptr = send_buffer + sizeof(s5_request_hdr_t);
207 | size_t pkt_len = remote->remote_len + sizeof(s5_request_hdr_t);
208 |
209 | if (pkt_len > sizeof(send_buffer)) {
210 | log_fatal("internal error: send buffer overflow.\n");
211 | goto err_new_conn;
212 | }
213 |
214 | memcpy(buf_ptr, remote->remote, remote->remote_len);
215 | write(fd, send_buffer, pkt_len);
216 | state = REQUEST_SENT;
217 | continue;
218 | }
219 |
220 | if (state == REQUEST_SENT) {
221 | s5_reply_hdr_t *reply = (s5_reply_hdr_t *) buffer;
222 |
223 | if (reply->ver != SOCK_VER) {
224 | log_fatal("bad remote server version: %d.\n", reply->ver);
225 | goto err_new_conn;
226 | }
227 |
228 | if (reply->rep != REP_OK) {
229 | log_fatal("remote request rejected: %d.\n", reply->rep);
230 | goto err_new_conn;
231 | }
232 |
233 | if (remote->protocol == IPPROTO_UDP) {
234 | /** todo **/
235 | log_fatal("udp not yet implemented.\n");
236 | goto err_new_conn;
237 | }
238 |
239 | return fd;
240 | }
241 | }
242 |
243 | err_new_conn:
244 | close(fd);
245 | return -1;
246 | }
247 |
248 | void fdbridge(int a, int b) {
249 | struct pollfd fds[2];
250 | fds[0].fd = a;
251 | fds[1].fd = b;
252 | fds[0].events = fds[1].events = POLLIN;
253 | uint8_t buffer[65536];
254 | bool ain = false, bin = false;
255 |
256 | while (1) {
257 | if (poll(fds, 2, -1) < 0) {
258 | log_fatal("poll(): %s\n", strerror(errno));
259 | goto br_err;
260 | }
261 |
262 | if (fds[0].revents & POLLIN) {
263 | ain = true;
264 | ssize_t rsz = read(fds[0].fd, buffer, sizeof(buffer));
265 | if (rsz < 0) {
266 | log_error("read(): %s\n", strerror(errno));
267 | goto br_err;
268 | }
269 | if (rsz == 0) {
270 | log_debug("read(): fd closed.\n");
271 | goto br_done;
272 | }
273 | ssize_t wsz = write(fds[1].fd, buffer, (size_t) rsz);
274 | if (wsz < 0) {
275 | log_error("write(): %s\n", strerror(errno));
276 | goto br_err;
277 | }
278 | if (wsz != rsz) {
279 | log_warn("inconsistent write size.\n");
280 | }
281 | }
282 |
283 | if (fds[1].revents & POLLIN) {
284 | bin = true;
285 | ssize_t rsz = read(fds[1].fd, buffer, sizeof(buffer));
286 | if (rsz < 0) {
287 | log_error("read(): %s\n", strerror(errno));
288 | return;
289 | }
290 | if (rsz == 0) return;
291 | ssize_t wsz = write(fds[0].fd, buffer, (size_t) rsz);
292 | if (wsz < 0) {
293 | log_error("write(): %s\n", strerror(errno));
294 | goto br_err;
295 | }
296 | if (wsz != rsz) {
297 | log_warn("inconsistent write size.\n");
298 | }
299 | }
300 |
301 | if (!ain && !bin) {
302 | log_warn("poll() returned but nothing ready.\n");
303 | }
304 |
305 | ain = bin = false;
306 | }
307 |
308 | br_err:
309 | log_error("broken pipe.\n");
310 |
311 | br_done:
312 | return;
313 | }
314 |
315 | void* s5_worker_tcp_conn(void *p) {
316 | s5_fdpair_t *fds = (s5_fdpair_t *) p;
317 | fdbridge(fds->local, fds->remote);
318 | close(fds->remote);
319 | close(fds->local);
320 | log_debug("connection closed.\n");
321 | free(p);
322 |
323 | return NULL;
324 | }
325 |
326 | void* s5_worker_tcp(void *ctx) {
327 | s5_context_t *context = (s5_context_t *) ctx;
328 | int fd = gai_bind(context->remote->local_host, context->remote->local_port, SOCK_STREAM, IPPROTO_TCP);
329 | if (fd < 0) return NULL;
330 |
331 | if (listen(fd, 5) < 0) {
332 | log_fatal("listen(): %s\n", strerror(errno));
333 | return NULL;
334 | }
335 |
336 | s5_thread_t *threads, *cur;
337 | threads = (s5_thread_t *) malloc(sizeof(s5_thread_t));
338 | cur = threads;
339 |
340 | struct sockaddr_storage client_addr;
341 | socklen_t client_addr_len = sizeof(struct sockaddr_storage);
342 | char client_addr_str[INET6_ADDRSTRLEN];
343 |
344 | while (1) {
345 | int lfd = accept(fd, (struct sockaddr *) &client_addr, &client_addr_len);
346 | if (lfd < 0) {
347 | log_fatal("accept(): %s\n", strerror(errno));
348 | goto err_tcp;
349 | }
350 | sockaddr_to_str((struct sockaddr *) &client_addr, client_addr_str, INET6_ADDRSTRLEN);
351 | log_debug("new tcp client: %s -> %s:%s\n", client_addr_str, context->remote->local_host, context->remote->local_port);
352 | int rfd = s5_new_connection(context->config, context->remote);
353 | if (rfd < 0) {
354 | log_error("can't create new socks5 connection.\n");
355 | close(lfd);
356 | continue;
357 | }
358 | s5_fdpair_t *p = (s5_fdpair_t *) malloc(sizeof(s5_fdpair_t));
359 | p->local = lfd;
360 | p->remote = rfd;
361 |
362 | int s = pthread_create(&(cur->thread), NULL, s5_worker_tcp_conn, p);
363 | if (s != 0) {
364 | close(lfd);
365 | close(rfd);
366 | log_fatal("failed to start connection worker: %s\n", strerror(s));
367 | continue;
368 | }
369 |
370 | cur->next = (s5_thread_t *) malloc(sizeof(s5_thread_t));
371 | cur = cur->next;
372 | }
373 |
374 | err_tcp:
375 | close(fd);
376 | return NULL;
377 | }
378 |
379 | void* s5_worker_udp(void *ctx) {
380 | s5_context_t *context = (s5_context_t *) ctx;
381 | // todo
382 | log_fatal("udp not yet implemented.\n");
383 | goto err_udp;
384 |
385 | err_udp:
386 | return NULL;
387 | }
388 |
389 | void s5_run(const s5_config_t *config) {
390 | if (config->remotes == NULL) {
391 | log_error("no tunnel configured.\n");
392 | return;
393 | }
394 |
395 | s5_thread_t *threads, *cur;
396 | threads = (s5_thread_t *) malloc(sizeof(s5_thread_t));
397 | cur = threads;
398 | for (const s5_remote_t *r = config->remotes; r != NULL; r = r->next) {
399 | cur->ctx.config = config;
400 | cur->ctx.remote = r;
401 |
402 | int s = pthread_create(&(cur->thread), NULL, r->protocol == IPPROTO_UDP ? s5_worker_udp : s5_worker_tcp, &(cur->ctx));
403 | if (s != 0) {
404 | log_fatal("failed to start worker: %s\n", strerror(s));
405 | goto err_run;
406 | }
407 | cur->next = (s5_thread_t *) malloc(sizeof(s5_thread_t));
408 | cur = cur->next;
409 | cur->thread = 0;
410 | cur->next = NULL;
411 | }
412 |
413 | for (cur = threads; cur != NULL; cur = cur->next) {
414 | if (cur->thread != 0) {
415 | pthread_join(cur->thread, NULL);
416 | log_warn("worker exited.\n");
417 | }
418 | }
419 |
420 | log_warn("all workers exited.\n");
421 | goto err_run;
422 | err_run:
423 | return; // FIXME: stop workers, free structs
424 | }
425 |
426 | ssize_t mks5addr(uint8_t atyp, const char *host, in_port_t port, uint8_t **rslt) {
427 | if (atyp == ATYP_IP) {
428 | struct {
429 | struct in_addr addr;
430 | in_port_t port;
431 | } __attribute__((packed)) addr;
432 |
433 | int s = inet_pton(AF_INET, host, &(addr.addr));
434 | addr.port = port;
435 | if (s <= 0) {
436 | if (s == 0) log_fatal("inet_pton(): invalid ipv4 address.\n");
437 | if (s < 0) log_fatal("inet_pton(): %s\n", strerror(errno));
438 | }
439 |
440 | *rslt = malloc(sizeof(addr));
441 | memcpy(*rslt, &addr, sizeof(addr));
442 | return sizeof(addr);
443 | } else if (atyp == ATYP_IP6) {
444 | struct {
445 | struct in6_addr addr;
446 | in_port_t port;
447 | } __attribute__((packed)) addr;
448 |
449 | int s = inet_pton(AF_INET6, host, &(addr.addr));
450 | addr.port = port;
451 | if (s <= 0) {
452 | if (s == 0) log_fatal("inet_pton(): invalid ipv4 address.\n");
453 | if (s < 0) log_fatal("inet_pton(): %s\n", strerror(errno));
454 | }
455 |
456 | *rslt = malloc(sizeof(addr));
457 | memcpy(*rslt, &addr, sizeof(addr));
458 | return sizeof(addr);
459 | } else if (atyp == ATYP_FQDN) {
460 | size_t dlen = strlen(host);
461 | size_t buf_sz = 1 + dlen + sizeof(in_port_t); // 1: length field
462 | *rslt = malloc(buf_sz);
463 | *rslt[0] = dlen;
464 | memcpy((*rslt) + 1, host, dlen);
465 | (*(uint16_t *) (*rslt + dlen + 1)) = port;
466 | return buf_sz;
467 | } else {
468 | log_error("bad address type %d.\n", atyp);
469 | return -1;
470 | }
471 |
472 | log_fatal("unreached.\n");
473 | return -1;
474 | }
--------------------------------------------------------------------------------
/s5tunnel.h:
--------------------------------------------------------------------------------
1 | #ifndef S5TUNNEL_H
2 | #define S5TUNNEL_H
3 | #include
4 | #include
5 | #include
6 | #include
7 | #include
8 |
9 | typedef struct s5_remote {
10 | int protocol;
11 | char* local_host;
12 | char* local_port;
13 | uint8_t remote_type;
14 | size_t remote_len;
15 | uint8_t* remote;
16 | struct s5_remote *next;
17 | } s5_remote_t;
18 |
19 | typedef struct s5_config {
20 | char* server_host;
21 | char* server_port;
22 |
23 | bool auth_enabled;
24 | char* user;
25 | char* passwd;
26 |
27 | s5_remote_t *remotes;
28 | } s5_config_t;
29 |
30 | typedef struct s5_context {
31 | const s5_config_t *config;
32 | const s5_remote_t *remote;
33 | } s5_context_t;
34 |
35 | typedef struct s5_thread {
36 | pthread_t thread;
37 | s5_context_t ctx;
38 | struct s5_thread *next;
39 | } s5_thread_t;
40 |
41 | typedef struct s5_fdpair {
42 | int local;
43 | int remote;
44 | } s5_fdpair_t;
45 |
46 | enum s5_states {
47 | IDLE, METHOD_SENT, AUTH_SENT, REQUEST_SENT
48 | };
49 |
50 | void s5_run(const s5_config_t *config);
51 | ssize_t mks5addr(uint8_t atyp, const char *host, in_port_t port, uint8_t **rslt);
52 |
53 | #endif // S5TUNNEL_H
--------------------------------------------------------------------------------
/socks5.h:
--------------------------------------------------------------------------------
1 | #ifndef S5_SOCKS5_H
2 | #define S5_SOCKS5_H
3 | #include
4 |
5 | #define SOCK_VER 0x05
6 |
7 | #define S5_AUTH_NONE 0x00
8 | #define S5_AUTH_USER_PASSWD 0x02
9 | #define S5_AUTH_BAD 0xff
10 |
11 | typedef struct s5_method_request {
12 | uint8_t ver;
13 | uint8_t nmethods; // always 1 in our case
14 | uint8_t methods; // S5_AUTH_NONE or S5_AUTH_USER_PASSWD
15 | } __attribute__((packed)) s5_method_request_t;
16 |
17 | typedef struct s5_method_reply {
18 | uint8_t ver;
19 | uint8_t method;
20 | } __attribute__((packed)) s5_method_reply_t;
21 |
22 | #define CMD_CONNECT 0x01
23 | #define CMD_BIND 0x02
24 | #define CMD_UDP_ASSOC 0x03
25 |
26 | #define ATYP_IP 0x01
27 | #define ATYP_FQDN 0x03
28 | #define ATYP_IP6 0x04
29 |
30 | typedef struct s5_request_hdr {
31 | uint8_t ver;
32 | uint8_t cmd;
33 | uint8_t rsv;
34 | uint8_t atyp;
35 | } __attribute__((packed)) s5_request_hdr_t;
36 |
37 | #define REP_OK 0x00
38 | #define REP_GENERAL 0x01
39 | #define REP_DENY 0x02
40 | #define REP_NET_UNREACH 0x03
41 | #define REP_HOST_UNREACH 0x04
42 | #define REP_CONN_REFUSED 0x05
43 | #define REP_TTL_EXPIRED 0x06
44 | #define REP_BAD_COMMAND 0x07
45 | #define REP_BAD_ATYP 0x08
46 |
47 | typedef struct s5_reply_hdr {
48 | uint8_t ver;
49 | uint8_t rep;
50 | uint8_t rsv;
51 | uint8_t atyp;
52 | } __attribute__((packed)) s5_reply_hdr_t;
53 |
54 | typedef struct s5_udp_payload_hdr {
55 | uint16_t rsv;
56 | uint8_t frag;
57 | uint8_t atyp;
58 | } __attribute__((packed)) s5_udp_payload_hdr_t;
59 |
60 | #endif // S5_SOCKS5_H
--------------------------------------------------------------------------------