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(16*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
64 static const char* auth_user
;
65 static const char* auth_pass
;
66 static sblist
* auth_ips
;
67 static pthread_rwlock_t auth_ips_lock
= PTHREAD_RWLOCK_INITIALIZER
;
68 static const struct server
* server
;
69 static union sockaddr_union bind_addr
= {.v4
.sin_family
= AF_UNSPEC
};
73 SS_2_NEED_AUTH
, /* skipped if NO_AUTH method supported */
86 EC_GENERAL_FAILURE
= 1,
88 EC_NET_UNREACHABLE
= 3,
89 EC_HOST_UNREACHABLE
= 4,
92 EC_COMMAND_NOT_SUPPORTED
= 7,
93 EC_ADDRESSTYPE_NOT_SUPPORTED
= 8,
99 enum socksstate state
;
107 /* we log to stderr because it's not using line buffering, i.e. malloc which would need
108 locking when called from different threads. for the same reason we use dprintf,
109 which writes directly to an fd. */
110 #define dolog(...) do { if(!quiet) dprintf(2, __VA_ARGS__); } while(0)
112 static void dolog(const char* fmt
, ...) { }
115 static struct addrinfo
* addr_choose(struct addrinfo
* list
, union sockaddr_union
* bindaddr
) {
116 int af
= SOCKADDR_UNION_AF(bindaddr
);
117 if(af
== AF_UNSPEC
) return list
;
119 for(p
=list
; p
; p
=p
->ai_next
)
120 if(p
->ai_family
== af
) return p
;
124 static int connect_socks_target(unsigned char *buf
, size_t n
, struct client
*client
) {
125 if(n
< 5) return -EC_GENERAL_FAILURE
;
126 if(buf
[0] != 5) return -EC_GENERAL_FAILURE
;
127 if(buf
[1] != 1) return -EC_COMMAND_NOT_SUPPORTED
; /* we support only CONNECT method */
128 if(buf
[2] != 0) return -EC_GENERAL_FAILURE
; /* malformed packet */
131 size_t minlen
= 4 + 4 + 2, l
;
133 struct addrinfo
* remote
;
141 if(n
< minlen
) return -EC_GENERAL_FAILURE
;
142 if(namebuf
!= inet_ntop(af
, buf
+4, namebuf
, sizeof namebuf
))
143 return -EC_GENERAL_FAILURE
; /* malformed or too long addr */
145 case 3: /* dns name */
147 minlen
= 4 + 2 + l
+ 1;
148 if(n
< 4 + 2 + l
+ 1) return -EC_GENERAL_FAILURE
;
149 memcpy(namebuf
, buf
+4+1, l
);
153 return -EC_ADDRESSTYPE_NOT_SUPPORTED
;
156 port
= (buf
[minlen
-2] << 8) | buf
[minlen
-1];
157 /* there's no suitable errorcode in rfc1928 for dns lookup failure */
158 if(resolve(namebuf
, port
, &remote
)) return -EC_GENERAL_FAILURE
;
159 struct addrinfo
* raddr
= addr_choose(remote
, &bind_addr
);
160 int fd
= socket(raddr
->ai_family
, SOCK_STREAM
, 0);
163 if(fd
!= -1) close(fd
);
164 freeaddrinfo(remote
);
167 return -EC_TTL_EXPIRED
;
169 case EPROTONOSUPPORT
:
171 return -EC_ADDRESSTYPE_NOT_SUPPORTED
;
173 return -EC_CONN_REFUSED
;
176 return -EC_NET_UNREACHABLE
;
178 return -EC_HOST_UNREACHABLE
;
181 perror("socket/connect");
182 return -EC_GENERAL_FAILURE
;
185 if(SOCKADDR_UNION_AF(&bind_addr
) == raddr
->ai_family
&&
186 bindtoip(fd
, &bind_addr
) == -1)
188 if(connect(fd
, raddr
->ai_addr
, raddr
->ai_addrlen
) == -1)
191 freeaddrinfo(remote
);
193 char clientname
[256];
194 af
= SOCKADDR_UNION_AF(&client
->addr
);
195 void *ipdata
= SOCKADDR_UNION_ADDRESS(&client
->addr
);
196 inet_ntop(af
, ipdata
, clientname
, sizeof clientname
);
197 dolog("client[%d] %s: connected to %s:%d\n", client
->fd
, clientname
, namebuf
, port
);
202 static int is_authed(union sockaddr_union
*client
, union sockaddr_union
*authedip
) {
203 int af
= SOCKADDR_UNION_AF(authedip
);
204 if(af
== SOCKADDR_UNION_AF(client
)) {
205 size_t cmpbytes
= af
== AF_INET
? 4 : 16;
206 void *cmp1
= SOCKADDR_UNION_ADDRESS(client
);
207 void *cmp2
= SOCKADDR_UNION_ADDRESS(authedip
);
208 if(!memcmp(cmp1
, cmp2
, cmpbytes
)) return 1;
213 static int is_in_authed_list(union sockaddr_union
*caddr
) {
215 for(i
=0;i
<sblist_getsize(auth_ips
);i
++)
216 if(is_authed(caddr
, sblist_get(auth_ips
, i
)))
221 static void add_auth_ip(union sockaddr_union
*caddr
) {
222 sblist_add(auth_ips
, caddr
);
225 static enum authmethod
check_auth_method(unsigned char *buf
, size_t n
, struct client
*client
) {
226 if(buf
[0] != 5) return AM_INVALID
;
228 if(idx
>= n
) return AM_INVALID
;
229 int n_methods
= buf
[idx
];
231 while(idx
< n
&& n_methods
> 0) {
232 if(buf
[idx
] == AM_NO_AUTH
) {
233 if(!auth_user
) return AM_NO_AUTH
;
236 if(pthread_rwlock_rdlock(&auth_ips_lock
) == 0) {
237 authed
= is_in_authed_list(&client
->addr
);
238 pthread_rwlock_unlock(&auth_ips_lock
);
240 if(authed
) return AM_NO_AUTH
;
242 } else if(buf
[idx
] == AM_USERNAME
) {
243 if(auth_user
) return AM_USERNAME
;
251 static void send_auth_response(int fd
, int version
, enum authmethod meth
) {
252 unsigned char buf
[2];
258 static void send_error(int fd
, enum errorcode ec
) {
259 /* position 4 contains ATYP, the address type, which is the same as used in the connect
260 request. we're lazy and return always IPV4 address type in errors. */
261 char buf
[10] = { 5, ec
, 0, 1 /*AT_IPV4*/, 0,0,0,0, 0,0 };
265 static void copyloop(int fd1
, int fd2
) {
266 struct pollfd fds
[2] = {
267 [0] = {.fd
= fd1
, .events
= POLLIN
},
268 [1] = {.fd
= fd2
, .events
= POLLIN
},
272 /* inactive connections are reaped after 15 min to free resources.
273 usually programs send keep-alive packets so this should only happen
274 when a connection is really unused. */
275 switch(poll(fds
, 2, 60*15*1000)) {
279 if(errno
== EINTR
|| errno
== EAGAIN
) continue;
283 int infd
= (fds
[0].revents
& POLLIN
) ? fd1
: fd2
;
284 int outfd
= infd
== fd2
? fd1
: fd2
;
286 ssize_t sent
= 0, n
= read(infd
, buf
, sizeof buf
);
289 ssize_t m
= write(outfd
, buf
+sent
, n
-sent
);
296 static enum errorcode
check_credentials(unsigned char* buf
, size_t n
) {
297 if(n
< 5) return EC_GENERAL_FAILURE
;
298 if(buf
[0] != 1) return EC_GENERAL_FAILURE
;
301 if(n
< 2 + ulen
+ 2) return EC_GENERAL_FAILURE
;
303 if(n
< 2 + ulen
+ 1 + plen
) return EC_GENERAL_FAILURE
;
304 char user
[256], pass
[256];
305 memcpy(user
, buf
+2, ulen
);
306 memcpy(pass
, buf
+2+ulen
+1, plen
);
309 if(!strcmp(user
, auth_user
) && !strcmp(pass
, auth_pass
)) return EC_SUCCESS
;
310 return EC_NOT_ALLOWED
;
313 static void* clientthread(void *data
) {
314 struct thread
*t
= data
;
315 t
->state
= SS_1_CONNECTED
;
316 unsigned char buf
[1024];
321 while((n
= recv(t
->client
.fd
, buf
, sizeof buf
, 0)) > 0) {
324 am
= check_auth_method(buf
, n
, &t
->client
);
325 if(am
== AM_NO_AUTH
) t
->state
= SS_3_AUTHED
;
326 else if (am
== AM_USERNAME
) t
->state
= SS_2_NEED_AUTH
;
327 send_auth_response(t
->client
.fd
, 5, am
);
328 if(am
== AM_INVALID
) goto breakloop
;
331 ret
= check_credentials(buf
, n
);
332 send_auth_response(t
->client
.fd
, 1, ret
);
333 if(ret
!= EC_SUCCESS
)
335 t
->state
= SS_3_AUTHED
;
336 if(auth_ips
&& !pthread_rwlock_wrlock(&auth_ips_lock
)) {
337 if(!is_in_authed_list(&t
->client
.addr
))
338 add_auth_ip(&t
->client
.addr
);
339 pthread_rwlock_unlock(&auth_ips_lock
);
343 ret
= connect_socks_target(buf
, n
, &t
->client
);
345 send_error(t
->client
.fd
, ret
*-1);
349 send_error(t
->client
.fd
, EC_SUCCESS
);
350 copyloop(t
->client
.fd
, remotefd
);
366 static void collect(sblist
*threads
) {
368 for(i
=0;i
<sblist_getsize(threads
);) {
369 struct thread
* thread
= *((struct thread
**)sblist_get(threads
, i
));
371 pthread_join(thread
->pt
, 0);
372 sblist_delete(threads
, i
);
379 static int usage(void) {
381 "MicroSocks SOCKS5 Server\n"
382 "------------------------\n"
383 "usage: microsocks -1 -q -i listenip -p port -u user -P password -b bindaddr\n"
384 "all arguments are optional.\n"
385 "by default listenip is 0.0.0.0 and port 1080.\n\n"
386 "option -q disables logging.\n"
387 "option -b specifies which ip outgoing connections are bound to\n"
388 "option -1 activates auth_once mode: once a specific ip address\n"
389 "authed successfully with user/pass, it is added to a whitelist\n"
390 "and may use the proxy without auth.\n"
391 "this is handy for programs like firefox that don't support\n"
392 "user/pass auth. for it to work you'd basically make one connection\n"
393 "with another program that supports it, and then you can use firefox too.\n"
398 /* prevent username and password from showing up in top. */
399 static void zero_arg(char *s
) {
400 size_t i
, l
= strlen(s
);
401 for(i
=0;i
<l
;i
++) s
[i
] = 0;
404 int main(int argc
, char** argv
) {
406 const char *listenip
= "0.0.0.0";
407 unsigned port
= 1080;
408 while((ch
= getopt(argc
, argv
, ":1qb:i:p:u:P:")) != -1) {
411 auth_ips
= sblist_new(sizeof(union sockaddr_union
), 8);
417 resolve_sa(optarg
, 0, &bind_addr
);
420 auth_user
= strdup(optarg
);
424 auth_pass
= strdup(optarg
);
434 dprintf(2, "error: option -%c requires an operand\n", optopt
);
440 if((auth_user
&& !auth_pass
) || (!auth_user
&& auth_pass
)) {
441 dprintf(2, "error: user and pass must be used together\n");
444 if(auth_ips
&& !auth_pass
) {
445 dprintf(2, "error: auth-once option must be used together with user/pass\n");
448 signal(SIGPIPE
, SIG_IGN
);
450 sblist
*threads
= sblist_new(sizeof (struct thread
*), 8);
451 if(server_setup(&s
, listenip
, port
)) {
452 perror("server_setup");
460 struct thread
*curr
= malloc(sizeof (struct thread
));
463 if(server_waitclient(&s
, &c
)) {
464 dolog("failed to accept connection\n");
466 usleep(FAILURE_TIMEOUT
);
470 if(!sblist_add(threads
, &curr
)) {
471 close(curr
->client
.fd
);
474 dolog("rejecting connection due to OOM\n");
475 usleep(FAILURE_TIMEOUT
); /* prevent 100% CPU usage in OOM situation */
478 pthread_attr_t
*a
= 0, attr
;
479 if(pthread_attr_init(&attr
) == 0) {
481 pthread_attr_setstacksize(a
, THREAD_STACK_SIZE
);
483 if(pthread_create(&curr
->pt
, a
, clientthread
, curr
) != 0)
484 dolog("pthread_create failed. OOM?\n");
485 if(a
) pthread_attr_destroy(&attr
);