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.
30 #include <sys/select.h>
31 #include <arpa/inet.h>
36 static const char* auth_user
;
37 static const char* auth_pass
;
38 static sblist
* auth_ips
;
39 static pthread_mutex_t auth_ips_mutex
= PTHREAD_MUTEX_INITIALIZER
;
43 SS_2_NEED_AUTH
, /* skipped if NO_AUTH method supported */
56 EC_GENERAL_FAILURE
= 1,
58 EC_NET_UNREACHABLE
= 3,
59 EC_HOST_UNREACHABLE
= 4,
62 EC_COMMAND_NOT_SUPPORTED
= 7,
63 EC_ADDRESSTYPE_NOT_SUPPORTED
= 8,
69 enum socksstate state
;
77 /* we log to stderr because it's not using line buffering, i.e. malloc which would need
78 locking when called from different threads. for the same reason we use dprintf,
79 which writes directly to an fd. */
80 #define dolog(...) dprintf(2, __VA_ARGS__)
82 static void dolog(const char* fmt
, ...) { }
85 static int connect_socks_target(unsigned char *buf
, size_t n
, struct client
*client
) {
86 if(n
< 5) return -EC_GENERAL_FAILURE
;
87 if(buf
[0] != 5) return -EC_GENERAL_FAILURE
;
88 if(buf
[1] != 1) return -EC_COMMAND_NOT_SUPPORTED
; /* we support only CONNECT method */
89 if(buf
[2] != 0) return -EC_GENERAL_FAILURE
; /* malformed packet */
92 size_t minlen
= 4 + 4 + 2, l
;
94 struct addrinfo
* remote
;
102 if(n
< minlen
) return -EC_GENERAL_FAILURE
;
103 if(namebuf
!= inet_ntop(af
, buf
+4, namebuf
, sizeof namebuf
))
104 return -EC_GENERAL_FAILURE
; /* malformed or too long addr */
106 case 3: /* dns name */
108 minlen
= 4 + 2 + l
+ 1;
109 if(n
< 4 + 2 + l
+ 1) return -EC_GENERAL_FAILURE
;
110 memcpy(namebuf
, buf
+4+1, l
);
114 return -EC_ADDRESSTYPE_NOT_SUPPORTED
;
117 port
= (buf
[minlen
-2] << 8) | buf
[minlen
-1];
118 if(resolve(namebuf
, port
, &remote
)) return -9;
119 int fd
= socket(remote
->ai_addr
->sa_family
, SOCK_STREAM
, 0);
122 freeaddrinfo(remote
);
125 case EPROTONOSUPPORT
:
127 return -EC_ADDRESSTYPE_NOT_SUPPORTED
;
129 return -EC_CONN_REFUSED
;
132 return -EC_NET_UNREACHABLE
;
134 return -EC_HOST_UNREACHABLE
;
137 perror("socket/connect");
138 return -EC_GENERAL_FAILURE
;
141 if(connect(fd
, remote
->ai_addr
, remote
->ai_addrlen
) == -1)
144 freeaddrinfo(remote
);
146 char clientname
[256];
147 af
= client
->addr
.v4
.sin_family
;
148 void *ipdata
= af
== AF_INET
? (void*)&client
->addr
.v4
.sin_addr
: (void*)&client
->addr
.v6
.sin6_addr
;
149 inet_ntop(af
, ipdata
, clientname
, sizeof clientname
);
150 dolog("client[%d] %s: connected to %s:%d\n", client
->fd
, clientname
, namebuf
, port
);
155 static int is_authed(union sockaddr_union
*client
, union sockaddr_union
*authedip
) {
156 if(authedip
->v4
.sin_family
== client
->v4
.sin_family
) {
157 int af
= authedip
->v4
.sin_family
;
158 size_t cmpbytes
= af
== AF_INET
? 4 : 16;
159 void *cmp1
= af
== AF_INET
? (void*)&client
->v4
.sin_addr
: (void*)&client
->v6
.sin6_addr
;
160 void *cmp2
= af
== AF_INET
? (void*)&authedip
->v4
.sin_addr
: (void*)&authedip
->v6
.sin6_addr
;
161 if(!memcmp(cmp1
, cmp2
, cmpbytes
)) return 1;
166 static enum authmethod
check_auth_method(unsigned char *buf
, size_t n
, struct client
*client
) {
167 if(buf
[0] != 5) return AM_INVALID
;
169 if(idx
>= n
) return AM_INVALID
;
170 int n_methods
= buf
[idx
];
173 if(buf
[idx
] == AM_NO_AUTH
) {
174 if(!auth_user
) return AM_NO_AUTH
;
178 pthread_mutex_lock(&auth_ips_mutex
);
179 for(i
=0;i
<sblist_getsize(auth_ips
);i
++) {
180 if((authed
= is_authed(&client
->addr
, sblist_get(auth_ips
, i
))))
183 pthread_mutex_unlock(&auth_ips_mutex
);
184 if(authed
) return AM_NO_AUTH
;
186 } else if(buf
[idx
] == AM_USERNAME
) {
187 if(auth_user
) return AM_USERNAME
;
194 static void add_auth_ip(struct client
*client
) {
195 pthread_mutex_lock(&auth_ips_mutex
);
196 sblist_add(auth_ips
, &client
->addr
);
197 pthread_mutex_unlock(&auth_ips_mutex
);
200 static void send_auth_response(int fd
, enum authmethod meth
) {
201 unsigned char buf
[2];
207 static void send_error(int fd
, enum errorcode ec
) {
208 /* position 4 contains ATYP, the address type, which is the same as used in the connect
209 request. we're lazy and return always IPV4 address type in errors. */
210 char buf
[10] = { 5, ec
, 0, 1 /*AT_IPV4*/, 0,0,0,0, 0,0 };
214 static void copyloop(int fd1
, int fd2
) {
216 if(fd1
> fd2
) maxfd
= fd1
;
223 memcpy(&fds
, &fdsc
, sizeof(fds
));
224 /* inactive connections are reaped after 15 min to free resources.
225 usually programs send keep-alive packets so this should only happen
226 when a connection is really unused. */
227 struct timeval timeout
= {.tv_sec
= 60*15, .tv_usec
= 0};
228 switch(select(maxfd
+1, &fds
, 0, 0, &timeout
)) {
230 send_error(fd1
, EC_TTL_EXPIRED
);
233 if(errno
= EINTR
) continue;
234 else perror("select");
237 int infd
= FD_ISSET(fd1
, &fds
) ? fd1
: fd2
;
238 int outfd
= infd
== fd2
? fd1
: fd2
;
240 ssize_t sent
= 0, n
= read(infd
, buf
, sizeof buf
);
243 ssize_t m
= write(outfd
, buf
+sent
, n
-sent
);
250 static enum errorcode
check_credentials(unsigned char* buf
, size_t n
) {
251 if(n
< 5) return EC_GENERAL_FAILURE
;
252 if(buf
[0] != 1) return EC_GENERAL_FAILURE
;
255 if(n
< 2 + ulen
+ 2) return EC_GENERAL_FAILURE
;
257 if(n
< 2 + ulen
+ 1 + plen
) return EC_GENERAL_FAILURE
;
258 char user
[256], pass
[256];
259 memcpy(user
, buf
+2, ulen
);
260 memcpy(pass
, buf
+2+ulen
+1, plen
);
261 if(!strcmp(user
, auth_user
) && !strcmp(pass
, auth_pass
)) return EC_SUCCESS
;
262 return EC_NOT_ALLOWED
;
265 static void* clientthread(void *data
) {
266 struct thread
*t
= data
;
267 t
->state
= SS_1_CONNECTED
;
268 unsigned char buf
[1024];
273 while((n
= recv(t
->client
.fd
, buf
, sizeof buf
, 0)) > 0) {
276 am
= check_auth_method(buf
, n
, &t
->client
);
277 if(am
== AM_NO_AUTH
) t
->state
= SS_3_AUTHED
;
278 else if (am
== AM_USERNAME
) t
->state
= SS_2_NEED_AUTH
;
279 send_auth_response(t
->client
.fd
, am
);
280 if(am
== AM_INVALID
) goto breakloop
;
283 ret
= check_credentials(buf
, n
);
284 send_auth_response(t
->client
.fd
, ret
);
285 if(ret
!= EC_SUCCESS
)
287 t
->state
= SS_3_AUTHED
;
288 if(auth_ips
) add_auth_ip(&t
->client
);
291 ret
= connect_socks_target(buf
, n
, &t
->client
);
293 send_error(t
->client
.fd
, ret
*-1);
297 send_error(t
->client
.fd
, EC_SUCCESS
);
298 copyloop(t
->client
.fd
, remotefd
);
314 static void collect(sblist
*threads
) {
316 for(i
=0;i
<sblist_getsize(threads
);) {
317 struct thread
* thread
= *((struct thread
**)sblist_get(threads
, i
));
319 pthread_join(thread
->pt
, 0);
320 sblist_delete(threads
, i
);
327 static int usage(void) {
329 "MicroSocks SOCKS5 Server\n"
330 "------------------------\n"
331 "usage: microsocks -1 -i listenip -p port -u user -P password\n"
332 "all arguments are optional.\n"
333 "by default listenip is 0.0.0.0 and port 1080.\n\n"
334 "option -1 activates auth_once mode: once a specific ip address\n"
335 "authed successfully with user/pass, it is added to a whitelist\n"
336 "and may use the proxy without auth.\n"
337 "this is handy for programs like firefox that don't support\n"
338 "user/pass auth. for it to work you'd basically make one connection\n"
339 "with another program that supports it, and then you can use firefox too.\n"
344 /* prevent username and password from showing up in top. */
345 static void zero_arg(char *s
) {
346 size_t i
, l
= strlen(s
);
347 for(i
=0;i
<l
;i
++) s
[i
] = 0;
350 int main(int argc
, char** argv
) {
352 const char *listenip
= "0.0.0.0";
353 unsigned port
= 1080;
354 while((c
= getopt(argc
, argv
, ":1i:p:u:P:")) != -1) {
357 auth_ips
= sblist_new(sizeof(union sockaddr_union
), 8);
360 auth_user
= strdup(optarg
);
364 auth_pass
= strdup(optarg
);
374 dprintf(2, "error: option -%c requires an operand\n", optopt
);
379 if((auth_user
&& !auth_pass
) || (!auth_user
&& auth_pass
)) {
380 dprintf(2, "error: user and pass must be used together\n");
383 if(auth_ips
&& !auth_pass
) {
384 dprintf(2, "error: auth-once option must be used together with user/pass\n");
388 sblist
*threads
= sblist_new(sizeof (struct thread
*), 8);
389 if(server_setup(&s
, listenip
, port
)) {
390 perror("server_setup");
396 struct thread
*curr
= malloc(sizeof (struct thread
));
399 if(server_waitclient(&s
, &c
)) continue;
401 if(!sblist_add(threads
, &curr
)) {
402 close(curr
->client
.fd
);
405 dolog("rejecting connection due to OOM\n");
406 usleep(16); /* prevent 100% CPU usage in OOM situation */
409 pthread_attr_t
*a
= 0, attr
;
410 if(pthread_attr_init(&attr
) == 0) {
412 pthread_attr_setstacksize(a
, 8192); /* 4KB for us, 4KB for libc */
414 if(pthread_create(&curr
->pt
, a
, clientthread
, curr
) != 0)
415 dolog("pthread_create failed. OOM?\n");
416 if(a
) pthread_attr_destroy(&attr
);