2 MicroSocks - multithreaded, small, efficient SOCKS5 server.
4 Copyright (C) 2017 rofl0r.
6 This is the successor of "rocksocks5", and it was written with
7 different goals in mind:
9 - prefer usage of standard libc functions over homegrown ones
10 - no artificial limits
11 - do not aim for minimal binary size, but for minimal source code size,
12 and maximal readability, reusability, and extensibility.
14 as a result of that, ipv4, dns, and ipv6 is supported out of the box
15 and can use the same code, while rocksocks5 has several compile time
16 defines to bring down the size of the resulting binary to extreme values
17 like 10 KB static linked when only ipv4 support is enabled.
19 still, if optimized for size, *this* program when static linked against musl
20 libc is not even 50 KB. that's easily usable even on the cheapest routers.
26 #define _POSIX_C_SOURCE 200809L
33 #include <arpa/inet.h>
39 /* timeout in microseconds on resource exhaustion to prevent excessive
41 #ifndef FAILURE_TIMEOUT
42 #define FAILURE_TIMEOUT 64
46 #define MAX(x, y) ((x) > (y) ? (x) : (y))
49 #ifdef PTHREAD_STACK_MIN
50 #define THREAD_STACK_SIZE MAX(8*1024, PTHREAD_STACK_MIN)
52 #define THREAD_STACK_SIZE 64*1024
55 #if defined(__APPLE__)
56 #undef THREAD_STACK_SIZE
57 #define THREAD_STACK_SIZE 64*1024
58 #elif defined(__GLIBC__) || defined(__FreeBSD__) || defined(__sun__)
59 #undef THREAD_STACK_SIZE
60 #define THREAD_STACK_SIZE 32*1024
63 static const char* auth_user
;
64 static const char* auth_pass
;
65 static sblist
* auth_ips
;
66 static pthread_rwlock_t auth_ips_lock
= PTHREAD_RWLOCK_INITIALIZER
;
67 static const struct server
* server
;
68 static union sockaddr_union bind_addr
= {.v4
.sin_family
= AF_UNSPEC
};
72 SS_2_NEED_AUTH
, /* skipped if NO_AUTH method supported */
85 EC_GENERAL_FAILURE
= 1,
87 EC_NET_UNREACHABLE
= 3,
88 EC_HOST_UNREACHABLE
= 4,
91 EC_COMMAND_NOT_SUPPORTED
= 7,
92 EC_ADDRESSTYPE_NOT_SUPPORTED
= 8,
98 enum socksstate state
;
106 /* we log to stderr because it's not using line buffering, i.e. malloc which would need
107 locking when called from different threads. for the same reason we use dprintf,
108 which writes directly to an fd. */
109 #define dolog(...) dprintf(2, __VA_ARGS__)
111 static void dolog(const char* fmt
, ...) { }
114 static struct addrinfo
* addr_choose(struct addrinfo
* list
, union sockaddr_union
* bind_addr
) {
115 int af
= SOCKADDR_UNION_AF(bind_addr
);
116 if(af
== AF_UNSPEC
) return list
;
118 for(p
=list
; p
; p
=p
->ai_next
)
119 if(p
->ai_family
== af
) return p
;
123 static int connect_socks_target(unsigned char *buf
, size_t n
, struct client
*client
) {
124 if(n
< 5) return -EC_GENERAL_FAILURE
;
125 if(buf
[0] != 5) return -EC_GENERAL_FAILURE
;
126 if(buf
[1] != 1) return -EC_COMMAND_NOT_SUPPORTED
; /* we support only CONNECT method */
127 if(buf
[2] != 0) return -EC_GENERAL_FAILURE
; /* malformed packet */
130 size_t minlen
= 4 + 4 + 2, l
;
132 struct addrinfo
* remote
;
140 if(n
< minlen
) return -EC_GENERAL_FAILURE
;
141 if(namebuf
!= inet_ntop(af
, buf
+4, namebuf
, sizeof namebuf
))
142 return -EC_GENERAL_FAILURE
; /* malformed or too long addr */
144 case 3: /* dns name */
146 minlen
= 4 + 2 + l
+ 1;
147 if(n
< 4 + 2 + l
+ 1) return -EC_GENERAL_FAILURE
;
148 memcpy(namebuf
, buf
+4+1, l
);
152 return -EC_ADDRESSTYPE_NOT_SUPPORTED
;
155 port
= (buf
[minlen
-2] << 8) | buf
[minlen
-1];
156 /* there's no suitable errorcode in rfc1928 for dns lookup failure */
157 if(resolve(namebuf
, port
, &remote
)) return -EC_GENERAL_FAILURE
;
158 struct addrinfo
* raddr
= addr_choose(remote
, &bind_addr
);
159 int fd
= socket(raddr
->ai_family
, SOCK_STREAM
, 0);
162 if(fd
!= -1) close(fd
);
163 freeaddrinfo(remote
);
166 return -EC_TTL_EXPIRED
;
168 case EPROTONOSUPPORT
:
170 return -EC_ADDRESSTYPE_NOT_SUPPORTED
;
172 return -EC_CONN_REFUSED
;
175 return -EC_NET_UNREACHABLE
;
177 return -EC_HOST_UNREACHABLE
;
180 perror("socket/connect");
181 return -EC_GENERAL_FAILURE
;
184 if(SOCKADDR_UNION_AF(&bind_addr
) == raddr
->ai_family
&&
185 bindtoip(fd
, &bind_addr
) == -1)
187 if(connect(fd
, raddr
->ai_addr
, raddr
->ai_addrlen
) == -1)
190 freeaddrinfo(remote
);
192 char clientname
[256];
193 af
= SOCKADDR_UNION_AF(&client
->addr
);
194 void *ipdata
= SOCKADDR_UNION_ADDRESS(&client
->addr
);
195 inet_ntop(af
, ipdata
, clientname
, sizeof clientname
);
196 dolog("client[%d] %s: connected to %s:%d\n", client
->fd
, clientname
, namebuf
, port
);
201 static int is_authed(union sockaddr_union
*client
, union sockaddr_union
*authedip
) {
202 int af
= SOCKADDR_UNION_AF(authedip
);
203 if(af
== SOCKADDR_UNION_AF(client
)) {
204 size_t cmpbytes
= af
== AF_INET
? 4 : 16;
205 void *cmp1
= SOCKADDR_UNION_ADDRESS(client
);
206 void *cmp2
= SOCKADDR_UNION_ADDRESS(authedip
);
207 if(!memcmp(cmp1
, cmp2
, cmpbytes
)) return 1;
212 static int is_in_authed_list(union sockaddr_union
*caddr
) {
214 for(i
=0;i
<sblist_getsize(auth_ips
);i
++)
215 if(is_authed(caddr
, sblist_get(auth_ips
, i
)))
220 static void add_auth_ip(union sockaddr_union
*caddr
) {
221 sblist_add(auth_ips
, caddr
);
224 static enum authmethod
check_auth_method(unsigned char *buf
, size_t n
, struct client
*client
) {
225 if(buf
[0] != 5) return AM_INVALID
;
227 if(idx
>= n
) return AM_INVALID
;
228 int n_methods
= buf
[idx
];
230 while(idx
< n
&& n_methods
> 0) {
231 if(buf
[idx
] == AM_NO_AUTH
) {
232 if(!auth_user
) return AM_NO_AUTH
;
235 if(pthread_rwlock_rdlock(&auth_ips_lock
) == 0) {
236 authed
= is_in_authed_list(&client
->addr
);
237 pthread_rwlock_unlock(&auth_ips_lock
);
239 if(authed
) return AM_NO_AUTH
;
241 } else if(buf
[idx
] == AM_USERNAME
) {
242 if(auth_user
) return AM_USERNAME
;
250 static void send_auth_response(int fd
, int version
, enum authmethod meth
) {
251 unsigned char buf
[2];
257 static void send_error(int fd
, enum errorcode ec
) {
258 /* position 4 contains ATYP, the address type, which is the same as used in the connect
259 request. we're lazy and return always IPV4 address type in errors. */
260 char buf
[10] = { 5, ec
, 0, 1 /*AT_IPV4*/, 0,0,0,0, 0,0 };
264 static void copyloop(int fd1
, int fd2
) {
265 struct pollfd fds
[2] = {
266 [0] = {.fd
= fd1
, .events
= POLLIN
},
267 [1] = {.fd
= fd2
, .events
= POLLIN
},
271 /* inactive connections are reaped after 15 min to free resources.
272 usually programs send keep-alive packets so this should only happen
273 when a connection is really unused. */
274 switch(poll(fds
, 2, 60*15*1000)) {
278 if(errno
== EINTR
|| errno
== EAGAIN
) continue;
282 int infd
= (fds
[0].revents
& POLLIN
) ? fd1
: fd2
;
283 int outfd
= infd
== fd2
? fd1
: fd2
;
285 ssize_t sent
= 0, n
= read(infd
, buf
, sizeof buf
);
288 ssize_t m
= write(outfd
, buf
+sent
, n
-sent
);
295 static enum errorcode
check_credentials(unsigned char* buf
, size_t n
) {
296 if(n
< 5) return EC_GENERAL_FAILURE
;
297 if(buf
[0] != 1) return EC_GENERAL_FAILURE
;
300 if(n
< 2 + ulen
+ 2) return EC_GENERAL_FAILURE
;
302 if(n
< 2 + ulen
+ 1 + plen
) return EC_GENERAL_FAILURE
;
303 char user
[256], pass
[256];
304 memcpy(user
, buf
+2, ulen
);
305 memcpy(pass
, buf
+2+ulen
+1, plen
);
308 if(!strcmp(user
, auth_user
) && !strcmp(pass
, auth_pass
)) return EC_SUCCESS
;
309 return EC_NOT_ALLOWED
;
312 static void* clientthread(void *data
) {
313 struct thread
*t
= data
;
314 t
->state
= SS_1_CONNECTED
;
315 unsigned char buf
[1024];
320 while((n
= recv(t
->client
.fd
, buf
, sizeof buf
, 0)) > 0) {
323 am
= check_auth_method(buf
, n
, &t
->client
);
324 if(am
== AM_NO_AUTH
) t
->state
= SS_3_AUTHED
;
325 else if (am
== AM_USERNAME
) t
->state
= SS_2_NEED_AUTH
;
326 send_auth_response(t
->client
.fd
, 5, am
);
327 if(am
== AM_INVALID
) goto breakloop
;
330 ret
= check_credentials(buf
, n
);
331 send_auth_response(t
->client
.fd
, 1, ret
);
332 if(ret
!= EC_SUCCESS
)
334 t
->state
= SS_3_AUTHED
;
335 if(auth_ips
&& !pthread_rwlock_wrlock(&auth_ips_lock
)) {
336 if(!is_in_authed_list(&t
->client
.addr
))
337 add_auth_ip(&t
->client
.addr
);
338 pthread_rwlock_unlock(&auth_ips_lock
);
342 ret
= connect_socks_target(buf
, n
, &t
->client
);
344 send_error(t
->client
.fd
, ret
*-1);
348 send_error(t
->client
.fd
, EC_SUCCESS
);
349 copyloop(t
->client
.fd
, remotefd
);
365 static void collect(sblist
*threads
) {
367 for(i
=0;i
<sblist_getsize(threads
);) {
368 struct thread
* thread
= *((struct thread
**)sblist_get(threads
, i
));
370 pthread_join(thread
->pt
, 0);
371 sblist_delete(threads
, i
);
378 static int usage(void) {
380 "MicroSocks SOCKS5 Server\n"
381 "------------------------\n"
382 "usage: microsocks -1 -i listenip -p port -u user -P password -b bindaddr\n"
383 "all arguments are optional.\n"
384 "by default listenip is 0.0.0.0 and port 1080.\n\n"
385 "option -b specifies which ip outgoing connections are bound to\n"
386 "option -1 activates auth_once mode: once a specific ip address\n"
387 "authed successfully with user/pass, it is added to a whitelist\n"
388 "and may use the proxy without auth.\n"
389 "this is handy for programs like firefox that don't support\n"
390 "user/pass auth. for it to work you'd basically make one connection\n"
391 "with another program that supports it, and then you can use firefox too.\n"
396 /* prevent username and password from showing up in top. */
397 static void zero_arg(char *s
) {
398 size_t i
, l
= strlen(s
);
399 for(i
=0;i
<l
;i
++) s
[i
] = 0;
402 int main(int argc
, char** argv
) {
404 const char *listenip
= "0.0.0.0";
405 unsigned port
= 1080;
406 while((ch
= getopt(argc
, argv
, ":1b:i:p:u:P:")) != -1) {
409 auth_ips
= sblist_new(sizeof(union sockaddr_union
), 8);
412 resolve_sa(optarg
, 0, &bind_addr
);
415 auth_user
= strdup(optarg
);
419 auth_pass
= strdup(optarg
);
429 dprintf(2, "error: option -%c requires an operand\n", optopt
);
435 if((auth_user
&& !auth_pass
) || (!auth_user
&& auth_pass
)) {
436 dprintf(2, "error: user and pass must be used together\n");
439 if(auth_ips
&& !auth_pass
) {
440 dprintf(2, "error: auth-once option must be used together with user/pass\n");
443 signal(SIGPIPE
, SIG_IGN
);
445 sblist
*threads
= sblist_new(sizeof (struct thread
*), 8);
446 if(server_setup(&s
, listenip
, port
)) {
447 perror("server_setup");
455 struct thread
*curr
= malloc(sizeof (struct thread
));
458 if(server_waitclient(&s
, &c
)) {
459 dolog("failed to accept connection\n");
461 usleep(FAILURE_TIMEOUT
);
465 if(!sblist_add(threads
, &curr
)) {
466 close(curr
->client
.fd
);
469 dolog("rejecting connection due to OOM\n");
470 usleep(FAILURE_TIMEOUT
); /* prevent 100% CPU usage in OOM situation */
473 pthread_attr_t
*a
= 0, attr
;
474 if(pthread_attr_init(&attr
) == 0) {
476 pthread_attr_setstacksize(a
, THREAD_STACK_SIZE
);
478 if(pthread_create(&curr
->pt
, a
, clientthread
, curr
) != 0)
479 dolog("pthread_create failed. OOM?\n");
480 if(a
) pthread_attr_destroy(&attr
);