├── .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 --------------------------------------------------------------------------------