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 #ifdef PTHREAD_STACK_MIN
44 #define THREAD_STACK_SIZE MAX(8*1024, PTHREAD_STACK_MIN)
46 #define THREAD_STACK_SIZE 64*1024
49 #if defined(__APPLE__)
50 #undef THREAD_STACK_SIZE
51 #define THREAD_STACK_SIZE 64*1024
52 #elif defined(__GLIBC__) || defined(__FreeBSD__)
53 #undef THREAD_STACK_SIZE
54 #define THREAD_STACK_SIZE 32*1024
57 static const char* auth_user
;
58 static const char* auth_pass
;
59 static sblist
* auth_ips
;
60 static pthread_mutex_t auth_ips_mutex
= PTHREAD_MUTEX_INITIALIZER
;
61 static const struct server
* server
;
62 static union sockaddr_union bind_addr
= {.v4
.sin_family
= AF_UNSPEC
};
66 SS_2_NEED_AUTH
, /* skipped if NO_AUTH method supported */
79 EC_GENERAL_FAILURE
= 1,
81 EC_NET_UNREACHABLE
= 3,
82 EC_HOST_UNREACHABLE
= 4,
85 EC_COMMAND_NOT_SUPPORTED
= 7,
86 EC_ADDRESSTYPE_NOT_SUPPORTED
= 8,
92 enum socksstate state
;
100 /* we log to stderr because it's not using line buffering, i.e. malloc which would need
101 locking when called from different threads. for the same reason we use dprintf,
102 which writes directly to an fd. */
103 #define dolog(...) dprintf(2, __VA_ARGS__)
105 static void dolog(const char* fmt
, ...) { }
108 static int connect_socks_target(unsigned char *buf
, size_t n
, struct client
*client
) {
109 if(n
< 5) return -EC_GENERAL_FAILURE
;
110 if(buf
[0] != 5) return -EC_GENERAL_FAILURE
;
111 if(buf
[1] != 1) return -EC_COMMAND_NOT_SUPPORTED
; /* we support only CONNECT method */
112 if(buf
[2] != 0) return -EC_GENERAL_FAILURE
; /* malformed packet */
115 size_t minlen
= 4 + 4 + 2, l
;
117 struct addrinfo
* remote
;
125 if(n
< minlen
) return -EC_GENERAL_FAILURE
;
126 if(namebuf
!= inet_ntop(af
, buf
+4, namebuf
, sizeof namebuf
))
127 return -EC_GENERAL_FAILURE
; /* malformed or too long addr */
129 case 3: /* dns name */
131 minlen
= 4 + 2 + l
+ 1;
132 if(n
< 4 + 2 + l
+ 1) return -EC_GENERAL_FAILURE
;
133 memcpy(namebuf
, buf
+4+1, l
);
137 return -EC_ADDRESSTYPE_NOT_SUPPORTED
;
140 port
= (buf
[minlen
-2] << 8) | buf
[minlen
-1];
141 /* there's no suitable errorcode in rfc1928 for dns lookup failure */
142 if(resolve(namebuf
, port
, &remote
)) return -EC_GENERAL_FAILURE
;
143 int fd
= socket(remote
->ai_addr
->sa_family
, SOCK_STREAM
, 0);
146 if(fd
!= -1) close(fd
);
147 freeaddrinfo(remote
);
150 return -EC_TTL_EXPIRED
;
152 case EPROTONOSUPPORT
:
154 return -EC_ADDRESSTYPE_NOT_SUPPORTED
;
156 return -EC_CONN_REFUSED
;
159 return -EC_NET_UNREACHABLE
;
161 return -EC_HOST_UNREACHABLE
;
164 perror("socket/connect");
165 return -EC_GENERAL_FAILURE
;
168 if(SOCKADDR_UNION_AF(&bind_addr
) != AF_UNSPEC
&& bindtoip(fd
, &bind_addr
) == -1)
170 if(connect(fd
, remote
->ai_addr
, remote
->ai_addrlen
) == -1)
173 freeaddrinfo(remote
);
175 char clientname
[256];
176 af
= SOCKADDR_UNION_AF(&client
->addr
);
177 void *ipdata
= SOCKADDR_UNION_ADDRESS(&client
->addr
);
178 inet_ntop(af
, ipdata
, clientname
, sizeof clientname
);
179 dolog("client[%d] %s: connected to %s:%d\n", client
->fd
, clientname
, namebuf
, port
);
184 static int is_authed(union sockaddr_union
*client
, union sockaddr_union
*authedip
) {
185 int af
= SOCKADDR_UNION_AF(authedip
);
186 if(af
== SOCKADDR_UNION_AF(client
)) {
187 size_t cmpbytes
= af
== AF_INET
? 4 : 16;
188 void *cmp1
= SOCKADDR_UNION_ADDRESS(client
);
189 void *cmp2
= SOCKADDR_UNION_ADDRESS(authedip
);
190 if(!memcmp(cmp1
, cmp2
, cmpbytes
)) return 1;
195 static int is_in_authed_list(union sockaddr_union
*caddr
) {
197 for(i
=0;i
<sblist_getsize(auth_ips
);i
++)
198 if(is_authed(caddr
, sblist_get(auth_ips
, i
)))
203 static void add_auth_ip(union sockaddr_union
*caddr
) {
204 sblist_add(auth_ips
, caddr
);
207 static enum authmethod
check_auth_method(unsigned char *buf
, size_t n
, struct client
*client
) {
208 if(buf
[0] != 5) return AM_INVALID
;
210 if(idx
>= n
) return AM_INVALID
;
211 int n_methods
= buf
[idx
];
213 while(idx
< n
&& n_methods
> 0) {
214 if(buf
[idx
] == AM_NO_AUTH
) {
215 if(!auth_user
) return AM_NO_AUTH
;
218 pthread_mutex_lock(&auth_ips_mutex
);
219 authed
= is_in_authed_list(&client
->addr
);
220 pthread_mutex_unlock(&auth_ips_mutex
);
221 if(authed
) return AM_NO_AUTH
;
223 } else if(buf
[idx
] == AM_USERNAME
) {
224 if(auth_user
) return AM_USERNAME
;
232 static void send_auth_response(int fd
, int version
, enum authmethod meth
) {
233 unsigned char buf
[2];
239 static void send_error(int fd
, enum errorcode ec
) {
240 /* position 4 contains ATYP, the address type, which is the same as used in the connect
241 request. we're lazy and return always IPV4 address type in errors. */
242 char buf
[10] = { 5, ec
, 0, 1 /*AT_IPV4*/, 0,0,0,0, 0,0 };
246 static void copyloop(int fd1
, int fd2
) {
248 if(fd1
> fd2
) maxfd
= fd1
;
255 memcpy(&fds
, &fdsc
, sizeof(fds
));
256 /* inactive connections are reaped after 15 min to free resources.
257 usually programs send keep-alive packets so this should only happen
258 when a connection is really unused. */
259 struct timeval timeout
= {.tv_sec
= 60*15, .tv_usec
= 0};
260 switch(select(maxfd
+1, &fds
, 0, 0, &timeout
)) {
262 send_error(fd1
, EC_TTL_EXPIRED
);
265 if(errno
== EINTR
) continue;
266 else perror("select");
269 int infd
= FD_ISSET(fd1
, &fds
) ? fd1
: fd2
;
270 int outfd
= infd
== fd2
? fd1
: fd2
;
272 ssize_t sent
= 0, n
= read(infd
, buf
, sizeof buf
);
275 ssize_t m
= write(outfd
, buf
+sent
, n
-sent
);
282 static enum errorcode
check_credentials(unsigned char* buf
, size_t n
) {
283 if(n
< 5) return EC_GENERAL_FAILURE
;
284 if(buf
[0] != 1) return EC_GENERAL_FAILURE
;
287 if(n
< 2 + ulen
+ 2) return EC_GENERAL_FAILURE
;
289 if(n
< 2 + ulen
+ 1 + plen
) return EC_GENERAL_FAILURE
;
290 char user
[256], pass
[256];
291 memcpy(user
, buf
+2, ulen
);
292 memcpy(pass
, buf
+2+ulen
+1, plen
);
295 if(!strcmp(user
, auth_user
) && !strcmp(pass
, auth_pass
)) return EC_SUCCESS
;
296 return EC_NOT_ALLOWED
;
299 static void* clientthread(void *data
) {
300 struct thread
*t
= data
;
301 t
->state
= SS_1_CONNECTED
;
302 unsigned char buf
[1024];
307 while((n
= recv(t
->client
.fd
, buf
, sizeof buf
, 0)) > 0) {
310 am
= check_auth_method(buf
, n
, &t
->client
);
311 if(am
== AM_NO_AUTH
) t
->state
= SS_3_AUTHED
;
312 else if (am
== AM_USERNAME
) t
->state
= SS_2_NEED_AUTH
;
313 send_auth_response(t
->client
.fd
, 5, am
);
314 if(am
== AM_INVALID
) goto breakloop
;
317 ret
= check_credentials(buf
, n
);
318 send_auth_response(t
->client
.fd
, 1, ret
);
319 if(ret
!= EC_SUCCESS
)
321 t
->state
= SS_3_AUTHED
;
323 pthread_mutex_lock(&auth_ips_mutex
);
324 if(!is_in_authed_list(&t
->client
.addr
))
325 add_auth_ip(&t
->client
.addr
);
326 pthread_mutex_unlock(&auth_ips_mutex
);
330 ret
= connect_socks_target(buf
, n
, &t
->client
);
332 send_error(t
->client
.fd
, ret
*-1);
336 send_error(t
->client
.fd
, EC_SUCCESS
);
337 copyloop(t
->client
.fd
, remotefd
);
353 static void collect(sblist
*threads
) {
355 for(i
=0;i
<sblist_getsize(threads
);) {
356 struct thread
* thread
= *((struct thread
**)sblist_get(threads
, i
));
358 pthread_join(thread
->pt
, 0);
359 sblist_delete(threads
, i
);
366 static int usage(void) {
368 "MicroSocks SOCKS5 Server\n"
369 "------------------------\n"
370 "usage: microsocks -1 -i listenip -p port -u user -P password -b bindaddr\n"
371 "all arguments are optional.\n"
372 "by default listenip is 0.0.0.0 and port 1080.\n\n"
373 "option -b specifies which ip outgoing connections are bound to\n"
374 "option -1 activates auth_once mode: once a specific ip address\n"
375 "authed successfully with user/pass, it is added to a whitelist\n"
376 "and may use the proxy without auth.\n"
377 "this is handy for programs like firefox that don't support\n"
378 "user/pass auth. for it to work you'd basically make one connection\n"
379 "with another program that supports it, and then you can use firefox too.\n"
384 /* prevent username and password from showing up in top. */
385 static void zero_arg(char *s
) {
386 size_t i
, l
= strlen(s
);
387 for(i
=0;i
<l
;i
++) s
[i
] = 0;
390 int main(int argc
, char** argv
) {
392 const char *listenip
= "0.0.0.0";
393 unsigned port
= 1080;
394 while((ch
= getopt(argc
, argv
, ":1b:i:p:u:P:")) != -1) {
397 auth_ips
= sblist_new(sizeof(union sockaddr_union
), 8);
400 resolve_sa(optarg
, 0, &bind_addr
);
403 auth_user
= strdup(optarg
);
407 auth_pass
= strdup(optarg
);
417 dprintf(2, "error: option -%c requires an operand\n", optopt
);
423 if((auth_user
&& !auth_pass
) || (!auth_user
&& auth_pass
)) {
424 dprintf(2, "error: user and pass must be used together\n");
427 if(auth_ips
&& !auth_pass
) {
428 dprintf(2, "error: auth-once option must be used together with user/pass\n");
431 signal(SIGPIPE
, SIG_IGN
);
433 sblist
*threads
= sblist_new(sizeof (struct thread
*), 8);
434 if(server_setup(&s
, listenip
, port
)) {
435 perror("server_setup");
443 struct thread
*curr
= malloc(sizeof (struct thread
));
446 if(server_waitclient(&s
, &c
)) continue;
448 if(!sblist_add(threads
, &curr
)) {
449 close(curr
->client
.fd
);
452 dolog("rejecting connection due to OOM\n");
453 usleep(16); /* prevent 100% CPU usage in OOM situation */
456 pthread_attr_t
*a
= 0, attr
;
457 if(pthread_attr_init(&attr
) == 0) {
459 pthread_attr_setstacksize(a
, THREAD_STACK_SIZE
);
461 if(pthread_create(&curr
->pt
, a
, clientthread
, curr
) != 0)
462 dolog("pthread_create failed. OOM?\n");
463 if(a
) pthread_attr_destroy(&attr
);