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.
31 #include <sys/select.h>
32 #include <arpa/inet.h>
37 static const char* auth_user
;
38 static const char* auth_pass
;
39 static sblist
* auth_ips
;
40 static pthread_mutex_t auth_ips_mutex
= PTHREAD_MUTEX_INITIALIZER
;
44 SS_2_NEED_AUTH
, /* skipped if NO_AUTH method supported */
57 EC_GENERAL_FAILURE
= 1,
59 EC_NET_UNREACHABLE
= 3,
60 EC_HOST_UNREACHABLE
= 4,
63 EC_COMMAND_NOT_SUPPORTED
= 7,
64 EC_ADDRESSTYPE_NOT_SUPPORTED
= 8,
70 enum socksstate state
;
78 /* we log to stderr because it's not using line buffering, i.e. malloc which would need
79 locking when called from different threads. for the same reason we use dprintf,
80 which writes directly to an fd. */
81 #define dolog(...) dprintf(2, __VA_ARGS__)
83 static void dolog(const char* fmt
, ...) { }
86 static int connect_socks_target(unsigned char *buf
, size_t n
, struct client
*client
) {
87 if(n
< 5) return -EC_GENERAL_FAILURE
;
88 if(buf
[0] != 5) return -EC_GENERAL_FAILURE
;
89 if(buf
[1] != 1) return -EC_COMMAND_NOT_SUPPORTED
; /* we support only CONNECT method */
90 if(buf
[2] != 0) return -EC_GENERAL_FAILURE
; /* malformed packet */
93 size_t minlen
= 4 + 4 + 2, l
;
95 struct addrinfo
* remote
;
103 if(n
< minlen
) return -EC_GENERAL_FAILURE
;
104 if(namebuf
!= inet_ntop(af
, buf
+4, namebuf
, sizeof namebuf
))
105 return -EC_GENERAL_FAILURE
; /* malformed or too long addr */
107 case 3: /* dns name */
109 minlen
= 4 + 2 + l
+ 1;
110 if(n
< 4 + 2 + l
+ 1) return -EC_GENERAL_FAILURE
;
111 memcpy(namebuf
, buf
+4+1, l
);
115 return -EC_ADDRESSTYPE_NOT_SUPPORTED
;
118 port
= (buf
[minlen
-2] << 8) | buf
[minlen
-1];
119 if(resolve(namebuf
, port
, &remote
)) return -9;
120 int fd
= socket(remote
->ai_addr
->sa_family
, SOCK_STREAM
, 0);
123 freeaddrinfo(remote
);
126 case EPROTONOSUPPORT
:
128 return -EC_ADDRESSTYPE_NOT_SUPPORTED
;
130 return -EC_CONN_REFUSED
;
133 return -EC_NET_UNREACHABLE
;
135 return -EC_HOST_UNREACHABLE
;
138 perror("socket/connect");
139 return -EC_GENERAL_FAILURE
;
142 if(connect(fd
, remote
->ai_addr
, remote
->ai_addrlen
) == -1)
145 freeaddrinfo(remote
);
147 char clientname
[256];
148 af
= client
->addr
.v4
.sin_family
;
149 void *ipdata
= af
== AF_INET
? (void*)&client
->addr
.v4
.sin_addr
: (void*)&client
->addr
.v6
.sin6_addr
;
150 inet_ntop(af
, ipdata
, clientname
, sizeof clientname
);
151 dolog("client[%d] %s: connected to %s:%d\n", client
->fd
, clientname
, namebuf
, port
);
156 static int is_authed(union sockaddr_union
*client
, union sockaddr_union
*authedip
) {
157 if(authedip
->v4
.sin_family
== client
->v4
.sin_family
) {
158 int af
= authedip
->v4
.sin_family
;
159 size_t cmpbytes
= af
== AF_INET
? 4 : 16;
160 void *cmp1
= af
== AF_INET
? (void*)&client
->v4
.sin_addr
: (void*)&client
->v6
.sin6_addr
;
161 void *cmp2
= af
== AF_INET
? (void*)&authedip
->v4
.sin_addr
: (void*)&authedip
->v6
.sin6_addr
;
162 if(!memcmp(cmp1
, cmp2
, cmpbytes
)) return 1;
167 static enum authmethod
check_auth_method(unsigned char *buf
, size_t n
, struct client
*client
) {
168 if(buf
[0] != 5) return AM_INVALID
;
170 if(idx
>= n
) return AM_INVALID
;
171 int n_methods
= buf
[idx
];
173 while(idx
< n
&& n_methods
> 0) {
174 if(buf
[idx
] == AM_NO_AUTH
) {
175 if(!auth_user
) return AM_NO_AUTH
;
179 pthread_mutex_lock(&auth_ips_mutex
);
180 for(i
=0;i
<sblist_getsize(auth_ips
);i
++) {
181 if((authed
= is_authed(&client
->addr
, sblist_get(auth_ips
, i
))))
184 pthread_mutex_unlock(&auth_ips_mutex
);
185 if(authed
) return AM_NO_AUTH
;
187 } else if(buf
[idx
] == AM_USERNAME
) {
188 if(auth_user
) return AM_USERNAME
;
196 static void add_auth_ip(struct client
*client
) {
197 pthread_mutex_lock(&auth_ips_mutex
);
198 sblist_add(auth_ips
, &client
->addr
);
199 pthread_mutex_unlock(&auth_ips_mutex
);
202 static void send_auth_response(int fd
, enum authmethod meth
) {
203 unsigned char buf
[2];
209 static void send_error(int fd
, enum errorcode ec
) {
210 /* position 4 contains ATYP, the address type, which is the same as used in the connect
211 request. we're lazy and return always IPV4 address type in errors. */
212 char buf
[10] = { 5, ec
, 0, 1 /*AT_IPV4*/, 0,0,0,0, 0,0 };
216 static void copyloop(int fd1
, int fd2
) {
218 if(fd1
> fd2
) maxfd
= fd1
;
225 memcpy(&fds
, &fdsc
, sizeof(fds
));
226 /* inactive connections are reaped after 15 min to free resources.
227 usually programs send keep-alive packets so this should only happen
228 when a connection is really unused. */
229 struct timeval timeout
= {.tv_sec
= 60*15, .tv_usec
= 0};
230 switch(select(maxfd
+1, &fds
, 0, 0, &timeout
)) {
232 send_error(fd1
, EC_TTL_EXPIRED
);
235 if(errno
== EINTR
) continue;
236 else perror("select");
239 int infd
= FD_ISSET(fd1
, &fds
) ? fd1
: fd2
;
240 int outfd
= infd
== fd2
? fd1
: fd2
;
242 ssize_t sent
= 0, n
= read(infd
, buf
, sizeof buf
);
245 ssize_t m
= write(outfd
, buf
+sent
, n
-sent
);
252 static enum errorcode
check_credentials(unsigned char* buf
, size_t n
) {
253 if(n
< 5) return EC_GENERAL_FAILURE
;
254 if(buf
[0] != 1) return EC_GENERAL_FAILURE
;
257 if(n
< 2 + ulen
+ 2) return EC_GENERAL_FAILURE
;
259 if(n
< 2 + ulen
+ 1 + plen
) return EC_GENERAL_FAILURE
;
260 char user
[256], pass
[256];
261 memcpy(user
, buf
+2, ulen
);
262 memcpy(pass
, buf
+2+ulen
+1, plen
);
263 if(!strcmp(user
, auth_user
) && !strcmp(pass
, auth_pass
)) return EC_SUCCESS
;
264 return EC_NOT_ALLOWED
;
267 static void* clientthread(void *data
) {
268 struct thread
*t
= data
;
269 t
->state
= SS_1_CONNECTED
;
270 unsigned char buf
[1024];
275 while((n
= recv(t
->client
.fd
, buf
, sizeof buf
, 0)) > 0) {
278 am
= check_auth_method(buf
, n
, &t
->client
);
279 if(am
== AM_NO_AUTH
) t
->state
= SS_3_AUTHED
;
280 else if (am
== AM_USERNAME
) t
->state
= SS_2_NEED_AUTH
;
281 send_auth_response(t
->client
.fd
, am
);
282 if(am
== AM_INVALID
) goto breakloop
;
285 ret
= check_credentials(buf
, n
);
286 send_auth_response(t
->client
.fd
, ret
);
287 if(ret
!= EC_SUCCESS
)
289 t
->state
= SS_3_AUTHED
;
290 if(auth_ips
) add_auth_ip(&t
->client
);
293 ret
= connect_socks_target(buf
, n
, &t
->client
);
295 send_error(t
->client
.fd
, ret
*-1);
299 send_error(t
->client
.fd
, EC_SUCCESS
);
300 copyloop(t
->client
.fd
, remotefd
);
316 static void collect(sblist
*threads
) {
318 for(i
=0;i
<sblist_getsize(threads
);) {
319 struct thread
* thread
= *((struct thread
**)sblist_get(threads
, i
));
321 pthread_join(thread
->pt
, 0);
322 sblist_delete(threads
, i
);
329 static int usage(void) {
331 "MicroSocks SOCKS5 Server\n"
332 "------------------------\n"
333 "usage: microsocks -1 -i listenip -p port -u user -P password\n"
334 "all arguments are optional.\n"
335 "by default listenip is 0.0.0.0 and port 1080.\n\n"
336 "option -1 activates auth_once mode: once a specific ip address\n"
337 "authed successfully with user/pass, it is added to a whitelist\n"
338 "and may use the proxy without auth.\n"
339 "this is handy for programs like firefox that don't support\n"
340 "user/pass auth. for it to work you'd basically make one connection\n"
341 "with another program that supports it, and then you can use firefox too.\n"
346 /* prevent username and password from showing up in top. */
347 static void zero_arg(char *s
) {
348 size_t i
, l
= strlen(s
);
349 for(i
=0;i
<l
;i
++) s
[i
] = 0;
352 int main(int argc
, char** argv
) {
354 const char *listenip
= "0.0.0.0";
355 unsigned port
= 1080;
356 while((c
= getopt(argc
, argv
, ":1i:p:u:P:")) != -1) {
359 auth_ips
= sblist_new(sizeof(union sockaddr_union
), 8);
362 auth_user
= strdup(optarg
);
366 auth_pass
= strdup(optarg
);
376 dprintf(2, "error: option -%c requires an operand\n", optopt
);
381 if((auth_user
&& !auth_pass
) || (!auth_user
&& auth_pass
)) {
382 dprintf(2, "error: user and pass must be used together\n");
385 if(auth_ips
&& !auth_pass
) {
386 dprintf(2, "error: auth-once option must be used together with user/pass\n");
389 signal(SIGPIPE
, SIG_IGN
);
391 sblist
*threads
= sblist_new(sizeof (struct thread
*), 8);
392 if(server_setup(&s
, listenip
, port
)) {
393 perror("server_setup");
399 struct thread
*curr
= malloc(sizeof (struct thread
));
402 if(server_waitclient(&s
, &c
)) continue;
404 if(!sblist_add(threads
, &curr
)) {
405 close(curr
->client
.fd
);
408 dolog("rejecting connection due to OOM\n");
409 usleep(16); /* prevent 100% CPU usage in OOM situation */
412 pthread_attr_t
*a
= 0, attr
;
413 if(pthread_attr_init(&attr
) == 0) {
415 pthread_attr_setstacksize(a
, 8192); /* 4KB for us, 4KB for libc */
417 if(pthread_create(&curr
->pt
, a
, clientthread
, curr
) != 0)
418 dolog("pthread_create failed. OOM?\n");
419 if(a
) pthread_attr_destroy(&attr
);