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
32 #include <sys/select.h>
33 #include <arpa/inet.h>
40 #define MAX(x, y) ((x) > (y) ? (x) : (y))
43 #if !defined(PTHREAD_STACK_MIN) || defined(__APPLE__)
44 /* MAC says its min is 8KB, but then crashes in our face. thx hunkOLard */
45 #define PTHREAD_STACK_MIN 64*1024
48 static const char* auth_user
;
49 static const char* auth_pass
;
50 static sblist
* auth_ips
;
51 static pthread_mutex_t auth_ips_mutex
= PTHREAD_MUTEX_INITIALIZER
;
52 static const struct server
* server
;
57 SS_2_NEED_AUTH
, /* skipped if NO_AUTH method supported */
70 EC_GENERAL_FAILURE
= 1,
72 EC_NET_UNREACHABLE
= 3,
73 EC_HOST_UNREACHABLE
= 4,
76 EC_COMMAND_NOT_SUPPORTED
= 7,
77 EC_ADDRESSTYPE_NOT_SUPPORTED
= 8,
83 enum socksstate state
;
91 /* we log to stderr because it's not using line buffering, i.e. malloc which would need
92 locking when called from different threads. for the same reason we use dprintf,
93 which writes directly to an fd. */
94 #define dolog(...) dprintf(2, __VA_ARGS__)
96 static void dolog(const char* fmt
, ...) { }
99 static int connect_socks_target(unsigned char *buf
, size_t n
, struct client
*client
) {
100 if(n
< 5) return -EC_GENERAL_FAILURE
;
101 if(buf
[0] != 5) return -EC_GENERAL_FAILURE
;
102 if(buf
[1] != 1) return -EC_COMMAND_NOT_SUPPORTED
; /* we support only CONNECT method */
103 if(buf
[2] != 0) return -EC_GENERAL_FAILURE
; /* malformed packet */
106 size_t minlen
= 4 + 4 + 2, l
;
108 struct addrinfo
* remote
;
116 if(n
< minlen
) return -EC_GENERAL_FAILURE
;
117 if(namebuf
!= inet_ntop(af
, buf
+4, namebuf
, sizeof namebuf
))
118 return -EC_GENERAL_FAILURE
; /* malformed or too long addr */
120 case 3: /* dns name */
122 minlen
= 4 + 2 + l
+ 1;
123 if(n
< 4 + 2 + l
+ 1) return -EC_GENERAL_FAILURE
;
124 memcpy(namebuf
, buf
+4+1, l
);
128 return -EC_ADDRESSTYPE_NOT_SUPPORTED
;
131 port
= (buf
[minlen
-2] << 8) | buf
[minlen
-1];
132 if(resolve(namebuf
, port
, &remote
)) return -9;
133 int fd
= socket(remote
->ai_addr
->sa_family
, SOCK_STREAM
, 0);
136 freeaddrinfo(remote
);
139 case EPROTONOSUPPORT
:
141 return -EC_ADDRESSTYPE_NOT_SUPPORTED
;
143 return -EC_CONN_REFUSED
;
146 return -EC_NET_UNREACHABLE
;
148 return -EC_HOST_UNREACHABLE
;
151 perror("socket/connect");
152 return -EC_GENERAL_FAILURE
;
155 if(bind_mode
&& server_bindtoip(server
, fd
) == -1)
157 if(connect(fd
, remote
->ai_addr
, remote
->ai_addrlen
) == -1)
160 freeaddrinfo(remote
);
162 char clientname
[256];
163 af
= client
->addr
.v4
.sin_family
;
164 void *ipdata
= af
== AF_INET
? (void*)&client
->addr
.v4
.sin_addr
: (void*)&client
->addr
.v6
.sin6_addr
;
165 inet_ntop(af
, ipdata
, clientname
, sizeof clientname
);
166 dolog("client[%d] %s: connected to %s:%d\n", client
->fd
, clientname
, namebuf
, port
);
171 static int is_authed(union sockaddr_union
*client
, union sockaddr_union
*authedip
) {
172 if(authedip
->v4
.sin_family
== client
->v4
.sin_family
) {
173 int af
= authedip
->v4
.sin_family
;
174 size_t cmpbytes
= af
== AF_INET
? 4 : 16;
175 void *cmp1
= af
== AF_INET
? (void*)&client
->v4
.sin_addr
: (void*)&client
->v6
.sin6_addr
;
176 void *cmp2
= af
== AF_INET
? (void*)&authedip
->v4
.sin_addr
: (void*)&authedip
->v6
.sin6_addr
;
177 if(!memcmp(cmp1
, cmp2
, cmpbytes
)) return 1;
182 static enum authmethod
check_auth_method(unsigned char *buf
, size_t n
, struct client
*client
) {
183 if(buf
[0] != 5) return AM_INVALID
;
185 if(idx
>= n
) return AM_INVALID
;
186 int n_methods
= buf
[idx
];
188 while(idx
< n
&& n_methods
> 0) {
189 if(buf
[idx
] == AM_NO_AUTH
) {
190 if(!auth_user
) return AM_NO_AUTH
;
194 pthread_mutex_lock(&auth_ips_mutex
);
195 for(i
=0;i
<sblist_getsize(auth_ips
);i
++) {
196 if((authed
= is_authed(&client
->addr
, sblist_get(auth_ips
, i
))))
199 pthread_mutex_unlock(&auth_ips_mutex
);
200 if(authed
) return AM_NO_AUTH
;
202 } else if(buf
[idx
] == AM_USERNAME
) {
203 if(auth_user
) return AM_USERNAME
;
211 static void add_auth_ip(struct client
*client
) {
212 pthread_mutex_lock(&auth_ips_mutex
);
213 sblist_add(auth_ips
, &client
->addr
);
214 pthread_mutex_unlock(&auth_ips_mutex
);
217 static void send_auth_response(int fd
, int version
, enum authmethod meth
) {
218 unsigned char buf
[2];
224 static void send_error(int fd
, enum errorcode ec
) {
225 /* position 4 contains ATYP, the address type, which is the same as used in the connect
226 request. we're lazy and return always IPV4 address type in errors. */
227 char buf
[10] = { 5, ec
, 0, 1 /*AT_IPV4*/, 0,0,0,0, 0,0 };
231 static void copyloop(int fd1
, int fd2
) {
233 if(fd1
> fd2
) maxfd
= fd1
;
240 memcpy(&fds
, &fdsc
, sizeof(fds
));
241 /* inactive connections are reaped after 15 min to free resources.
242 usually programs send keep-alive packets so this should only happen
243 when a connection is really unused. */
244 struct timeval timeout
= {.tv_sec
= 60*15, .tv_usec
= 0};
245 switch(select(maxfd
+1, &fds
, 0, 0, &timeout
)) {
247 send_error(fd1
, EC_TTL_EXPIRED
);
250 if(errno
== EINTR
) continue;
251 else perror("select");
254 int infd
= FD_ISSET(fd1
, &fds
) ? fd1
: fd2
;
255 int outfd
= infd
== fd2
? fd1
: fd2
;
257 ssize_t sent
= 0, n
= read(infd
, buf
, sizeof buf
);
260 ssize_t m
= write(outfd
, buf
+sent
, n
-sent
);
267 static enum errorcode
check_credentials(unsigned char* buf
, size_t n
) {
268 if(n
< 5) return EC_GENERAL_FAILURE
;
269 if(buf
[0] != 1) return EC_GENERAL_FAILURE
;
272 if(n
< 2 + ulen
+ 2) return EC_GENERAL_FAILURE
;
274 if(n
< 2 + ulen
+ 1 + plen
) return EC_GENERAL_FAILURE
;
275 char user
[256], pass
[256];
276 memcpy(user
, buf
+2, ulen
);
277 memcpy(pass
, buf
+2+ulen
+1, plen
);
278 if(!strcmp(user
, auth_user
) && !strcmp(pass
, auth_pass
)) return EC_SUCCESS
;
279 return EC_NOT_ALLOWED
;
282 static void* clientthread(void *data
) {
283 struct thread
*t
= data
;
284 t
->state
= SS_1_CONNECTED
;
285 unsigned char buf
[1024];
290 while((n
= recv(t
->client
.fd
, buf
, sizeof buf
, 0)) > 0) {
293 am
= check_auth_method(buf
, n
, &t
->client
);
294 if(am
== AM_NO_AUTH
) t
->state
= SS_3_AUTHED
;
295 else if (am
== AM_USERNAME
) t
->state
= SS_2_NEED_AUTH
;
296 send_auth_response(t
->client
.fd
, 5, am
);
297 if(am
== AM_INVALID
) goto breakloop
;
300 ret
= check_credentials(buf
, n
);
301 send_auth_response(t
->client
.fd
, 1, ret
);
302 if(ret
!= EC_SUCCESS
)
304 t
->state
= SS_3_AUTHED
;
305 if(auth_ips
) add_auth_ip(&t
->client
);
308 ret
= connect_socks_target(buf
, n
, &t
->client
);
310 send_error(t
->client
.fd
, ret
*-1);
314 send_error(t
->client
.fd
, EC_SUCCESS
);
315 copyloop(t
->client
.fd
, remotefd
);
331 static void collect(sblist
*threads
) {
333 for(i
=0;i
<sblist_getsize(threads
);) {
334 struct thread
* thread
= *((struct thread
**)sblist_get(threads
, i
));
336 pthread_join(thread
->pt
, 0);
337 sblist_delete(threads
, i
);
344 static int usage(void) {
346 "MicroSocks SOCKS5 Server\n"
347 "------------------------\n"
348 "usage: microsocks -1 -b -i listenip -p port -u user -P password\n"
349 "all arguments are optional.\n"
350 "by default listenip is 0.0.0.0 and port 1080.\n\n"
351 "option -b forces outgoing connections to be bound to the ip specified with -i\n"
352 "option -1 activates auth_once mode: once a specific ip address\n"
353 "authed successfully with user/pass, it is added to a whitelist\n"
354 "and may use the proxy without auth.\n"
355 "this is handy for programs like firefox that don't support\n"
356 "user/pass auth. for it to work you'd basically make one connection\n"
357 "with another program that supports it, and then you can use firefox too.\n"
362 /* prevent username and password from showing up in top. */
363 static void zero_arg(char *s
) {
364 size_t i
, l
= strlen(s
);
365 for(i
=0;i
<l
;i
++) s
[i
] = 0;
368 int main(int argc
, char** argv
) {
370 const char *listenip
= "0.0.0.0";
371 unsigned port
= 1080;
372 while((c
= getopt(argc
, argv
, ":1bi:p:u:P:")) != -1) {
375 auth_ips
= sblist_new(sizeof(union sockaddr_union
), 8);
381 auth_user
= strdup(optarg
);
385 auth_pass
= strdup(optarg
);
395 dprintf(2, "error: option -%c requires an operand\n", optopt
);
400 if((auth_user
&& !auth_pass
) || (!auth_user
&& auth_pass
)) {
401 dprintf(2, "error: user and pass must be used together\n");
404 if(auth_ips
&& !auth_pass
) {
405 dprintf(2, "error: auth-once option must be used together with user/pass\n");
408 signal(SIGPIPE
, SIG_IGN
);
410 sblist
*threads
= sblist_new(sizeof (struct thread
*), 8);
411 if(server_setup(&s
, listenip
, port
)) {
412 perror("server_setup");
416 size_t stacksz
= MAX(8192, PTHREAD_STACK_MIN
); /* 4KB for us, 4KB for libc */
421 struct thread
*curr
= malloc(sizeof (struct thread
));
424 if(server_waitclient(&s
, &c
)) continue;
426 if(!sblist_add(threads
, &curr
)) {
427 close(curr
->client
.fd
);
430 dolog("rejecting connection due to OOM\n");
431 usleep(16); /* prevent 100% CPU usage in OOM situation */
434 pthread_attr_t
*a
= 0, attr
;
435 if(pthread_attr_init(&attr
) == 0) {
437 pthread_attr_setstacksize(a
, stacksz
);
439 if(pthread_create(&curr
->pt
, a
, clientthread
, curr
) != 0)
440 dolog("pthread_create failed. OOM?\n");
441 if(a
) pthread_attr_destroy(&attr
);