microsocks: add socks5 forwarding rules support (#553)

A self modified enhanced version prepared for optimizing passwall functionality.

Version from: https://github.com/lwb1978/microsocks/tree/forward
This commit is contained in:
A-BO
2025-12-23 16:13:43 +08:00
committed by GitHub
parent 9feb8ca7db
commit ee78424347
2 changed files with 424 additions and 2 deletions

View File

@@ -6,7 +6,7 @@ include $(TOPDIR)/rules.mk
PKG_NAME:=microsocks
PKG_VERSION:=1.0.5
PKG_RELEASE:=1
PKG_RELEASE:=2
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
PKG_SOURCE_URL:=https://codeload.github.com/rofl0r/microsocks/tar.gz/v$(PKG_VERSION)?
@@ -25,7 +25,7 @@ define Package/microsocks
SECTION:=net
CATEGORY:=Network
SUBMENU:=Web Servers/Proxies
TITLE:=Tiny, portable SOCKS5 server
TITLE:=Tiny, portable SOCKS5 server. Support forwarding rules
URL:=https://github.com/rofl0r/microsocks
DEPENDS:=+libpthread
endef
@@ -33,6 +33,7 @@ endef
define Package/microsocks/description
A SOCKS5 service that you can run on your remote boxes to tunnel connections
through them, if for some reason SSH doesn't cut it for you.
This version supports forwarding rules.
endef
define Package/microsocks/install

View File

@@ -0,0 +1,421 @@
--- a/sockssrv.c
+++ b/sockssrv.c
@@ -33,8 +33,10 @@
#include <arpa/inet.h>
#include <errno.h>
#include <limits.h>
+#include <sys/time.h>
#include "server.h"
#include "sblist.h"
+#define MICROSOCKS_VERSION "1.0.5-forward"
/* timeout in microseconds on resource exhaustion to prevent excessive
cpu usage. */
@@ -71,6 +73,7 @@
static pthread_rwlock_t auth_ips_lock = PTHREAD_RWLOCK_INITIALIZER;
static const struct server* server;
static union sockaddr_union bind_addr = {.v4.sin_family = AF_UNSPEC};
+static sblist *fwd_rules;
enum socksstate {
SS_1_CONNECTED,
@@ -97,6 +100,17 @@
EC_ADDRESSTYPE_NOT_SUPPORTED = 8,
};
+struct fwd_rule {
+ char *match_name;
+ short match_port;
+ char *auth_buf; /* Username/Password request buffer (RFC-1929) */
+ size_t auth_len;
+ char *upstream_name;
+ short upstream_port;
+ char *req_buf; /* Client Connection Request buffer to send to upstream */
+ size_t req_len;
+};
+
struct thread {
pthread_t pt;
struct client client;
@@ -116,6 +130,109 @@
static void dolog(const char* fmt, ...) { }
#endif
+static int upstream_handshake(const struct fwd_rule* rule, unsigned char *client_buf, size_t client_buf_len,
+ int client_fd, int upstream_fd, unsigned short client_port) {
+ unsigned char sbuf[512];
+ ssize_t r;
+
+ if(rule->auth_buf) {
+ unsigned char handshake[4] = {5, 2, 0, 2};
+ if (write(upstream_fd, handshake, 4) != 4) {
+ close(upstream_fd);
+ return -1;
+ }
+ } else {
+ unsigned char handshake[3] = {5, 1, 0};
+ if (write(upstream_fd, handshake, 3) != 3) {
+ close(upstream_fd);
+ return -1;
+ }
+ }
+
+ if (read(upstream_fd, sbuf, 2) != 2 || sbuf[0] != 5) {
+ close(upstream_fd);
+ return -1;
+ }
+
+ if (sbuf[1] == 2) {
+ if (!rule->auth_buf) {
+ close(upstream_fd);
+ return -1;
+ }
+ if (write(upstream_fd, rule->auth_buf, rule->auth_len) != (ssize_t)rule->auth_len) {
+ close(upstream_fd);
+ return -1;
+ }
+ if (read(upstream_fd, sbuf, 2) != 2 || sbuf[0] != 1 || sbuf[1] != 0) {
+ close(upstream_fd);
+ return -1;
+ }
+ } else if (sbuf[1] != 0) {
+ close(upstream_fd);
+ return -1;
+ }
+
+ if (write(upstream_fd, client_buf, client_buf_len) != (ssize_t)client_buf_len) {
+ close(upstream_fd);
+ return -1;
+ }
+
+ size_t total = 0;
+ size_t need = 4;
+
+ while (total < need) {
+ r = read(upstream_fd, sbuf + total, need - total);
+ if (r <= 0) {
+ close(upstream_fd);
+ return -1;
+ }
+ total += r;
+ }
+
+ if (sbuf[1] != 0) {
+ close(upstream_fd);
+ return -sbuf[1];
+ }
+
+ size_t need_more = 0;
+ switch (sbuf[3]) {
+ case 1:
+ need_more = 4 + 2;
+ break;
+ case 4:
+ need_more = 16 + 2;
+ break;
+ case 3:
+ r = read(upstream_fd, sbuf + total, 1);
+ if (r != 1) {
+ close(upstream_fd);
+ return -1;
+ }
+ total += r;
+ need_more = sbuf[4] + 2;
+ break;
+ default:
+ close(upstream_fd);
+ return -EC_ADDRESSTYPE_NOT_SUPPORTED;
+ }
+
+ while (total < need + need_more) {
+ r = read(upstream_fd, sbuf + total, (need + need_more) - total);
+ if (r <= 0) {
+ close(upstream_fd);
+ return -1;
+ }
+ total += r;
+ }
+
+ if (write(client_fd, sbuf, total) != (ssize_t)total) {
+ close(upstream_fd);
+ return -1;
+ }
+
+ return upstream_fd;
+}
+
static struct addrinfo* addr_choose(struct addrinfo* list, union sockaddr_union* bindaddr) {
int af = SOCKADDR_UNION_AF(bindaddr);
if(af == AF_UNSPEC) return list;
@@ -125,7 +242,9 @@
return list;
}
-static int connect_socks_target(unsigned char *buf, size_t n, struct client *client) {
+static int connect_socks_target(unsigned char *buf, size_t n, struct client *client, int *used_rule) {
+ *used_rule = 0;
+
if(n < 5) return -EC_GENERAL_FAILURE;
if(buf[0] != 5) return -EC_GENERAL_FAILURE;
if(buf[1] != 1) return -EC_COMMAND_NOT_SUPPORTED; /* we support only CONNECT method */
@@ -158,6 +277,29 @@
}
unsigned short port;
port = (buf[minlen-2] << 8) | buf[minlen-1];
+
+ size_t i;
+ struct fwd_rule *rule = NULL;
+ char original_name[256];
+ unsigned short original_port = port;
+ strncpy(original_name, namebuf, sizeof(original_name) - 1);
+ original_name[sizeof(original_name) - 1] = '\0';
+ if(fwd_rules) {
+ for(i=0;i<sblist_getsize(fwd_rules);++i) {
+ struct fwd_rule* r = (struct fwd_rule*)sblist_get(fwd_rules, i);
+ int name_match = (r->match_name[0]=='\0' || strcmp(r->match_name, namebuf) == 0);
+ int port_match = (r->match_port == 0 || r->match_port == port);
+ if(name_match && port_match) {
+ rule = r;
+ *used_rule = 1;
+ strncpy(namebuf, r->upstream_name, sizeof(namebuf)-1);
+ namebuf[sizeof(namebuf)-1] = '\0';
+ port = r->upstream_port;
+ break;
+ }
+ }
+ }
+
/* there's no suitable errorcode in rfc1928 for dns lookup failure */
if(resolve(namebuf, port, &remote)) return -EC_GENERAL_FAILURE;
struct addrinfo* raddr = addr_choose(remote, &bind_addr);
@@ -186,6 +328,11 @@
return -EC_GENERAL_FAILURE;
}
}
+
+ struct timeval tv = {5, 0};
+ setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, (const char*)&tv, sizeof(tv));
+ setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, (const char*)&tv, sizeof(tv));
+
if(SOCKADDR_UNION_AF(&bind_addr) == raddr->ai_family &&
bindtoip(fd, &bind_addr) == -1)
goto eval_errno;
@@ -198,9 +345,22 @@
af = SOCKADDR_UNION_AF(&client->addr);
void *ipdata = SOCKADDR_UNION_ADDRESS(&client->addr);
inet_ntop(af, ipdata, clientname, sizeof clientname);
- dolog("client[%d] %s: connected to %s:%d\n", client->fd, clientname, namebuf, port);
+ if (rule) {
+ dolog("client[%d] %s: %s:%d -> via %s:%d\n", client->fd, clientname, original_name, original_port, rule->upstream_name, rule->upstream_port);
+ } else {
+ dolog("client[%d] %s: connected to %s:%d\n", client->fd, clientname, namebuf, port);
+ }
}
- return fd;
+
+ if (rule) {
+ int result = upstream_handshake(rule, buf, n, client->fd, fd, original_port);
+ if (result < 0) {
+ close(fd);
+ return result;
+ }
+ return result;
+ }
+ return fd;
}
static int is_authed(union sockaddr_union *client, union sockaddr_union *authedip) {
@@ -345,12 +505,15 @@
}
break;
case SS_3_AUTHED:
- ret = connect_socks_target(buf, n, &t->client);
+ int used_rule = 0;
+ ret = connect_socks_target(buf, n, &t->client, &used_rule);
if(ret < 0) {
send_error(t->client.fd, ret*-1);
return -1;
}
- send_error(t->client.fd, EC_SUCCESS);
+ if (!used_rule) {
+ send_error(t->client.fd, EC_SUCCESS);
+ }
return ret;
}
}
@@ -382,11 +545,131 @@
}
}
+static short host_get_port(char *name) {
+ int p,n;
+ char *c;
+ if((c = strrchr(name, ':')) && sscanf(c+1,"%d%n",&p, &n)==1 && n == (int)(strlen(c + 1)) && p >= 0 && p < USHRT_MAX)
+ return (*c='\0'),(short)p;
+ else
+ return -1;
+}
+
+static int fwd_rules_add(char *str) {
+ char *match = NULL, *upstream = NULL, *remote = NULL;
+ unsigned short match_port, upstream_port, remote_port;
+ int ncred;
+
+ if(sscanf(str, "%m[^,],%n%m[^,],%ms\n", &match, &ncred, &upstream, &remote) != 3)
+ return 1;
+
+ match_port = host_get_port(match);
+ upstream_port = host_get_port(upstream);
+ remote_port = host_get_port(remote);
+
+ if(match_port < 0 || upstream_port <= 0 || remote_port < 0) {
+ free(match);
+ free(upstream);
+ free(remote);
+ return 1;
+ }
+
+ char *match_copy = strdup(match);
+ char *upstream_copy = strdup(upstream);
+ char *remote_copy = strdup(remote);
+
+ struct fwd_rule *rule = (struct fwd_rule*)malloc(sizeof(struct fwd_rule));
+ if (!rule) {
+ free(match_copy);
+ free(upstream_copy);
+ free(remote_copy);
+ free(match);
+ free(upstream);
+ free(remote);
+ return 1;
+ }
+
+ if(strcmp(match_copy, "0.0.0.0") == 0 || strcmp(match_copy, "*") == 0) {
+ free(match_copy);
+ rule->match_name = strdup("");
+ } else {
+ rule->match_name = match_copy;
+ }
+ rule->match_port = match_port;
+ rule->auth_buf = NULL;
+ rule->auth_len = 0;
+
+ char *at_sign = strchr(upstream_copy, '@');
+ if (at_sign) {
+ *at_sign = '\0';
+ char *auth_part = upstream_copy;
+ char *host_part = at_sign + 1;
+ char *colon = strchr(auth_part, ':');
+ if (!colon) {
+ free(rule);
+ free(upstream_copy);
+ free(remote_copy);
+ free(match);
+ free(upstream);
+ free(remote);
+ return 1;
+ }
+ *colon++ = '\0';
+ char *username = auth_part;
+ char *password = colon;
+ size_t ulen = strlen(username);
+ size_t plen = strlen(password);
+ if (ulen > 255 || plen > 255) {
+ free(rule);
+ free(upstream_copy);
+ free(remote_copy);
+ free(match);
+ free(upstream);
+ free(remote);
+ return 1;
+ }
+ rule->auth_len = 1 + 1 + ulen + 1 + plen;
+ rule->auth_buf = malloc(rule->auth_len);
+ rule->auth_buf[0] = 1;
+ rule->auth_buf[1] = ulen;
+ memcpy(&rule->auth_buf[2], username, ulen);
+ rule->auth_buf[2 + ulen] = plen;
+ memcpy(&rule->auth_buf[3 + ulen], password, plen);
+ rule->upstream_name = strdup(host_part);
+ rule->upstream_port = upstream_port;
+ /* hide from ps */
+ memset(str+ncred, '*', ulen+1+plen);
+ } else {
+ rule->upstream_name = strdup(upstream_copy);
+ rule->upstream_port = upstream_port;
+ }
+
+ free(upstream_copy);
+ short rlen = strlen(remote_copy);
+ rule->req_len = 3 + 1 + 1 + rlen + 2;
+ rule->req_buf = (char*)malloc(rule->req_len);
+ rule->req_buf[0] = 5;
+ rule->req_buf[1] = 1;
+ rule->req_buf[2] = 0;
+ rule->req_buf[3] = 3;
+ rule->req_buf[4] = rlen;
+ memcpy(&rule->req_buf[5], remote_copy, rlen);
+ unsigned short rport = remote_port ? remote_port : 0;
+ rule->req_buf[5 + rlen] = (rport >> 8) & 0xFF;
+ rule->req_buf[5 + rlen + 1] = (rport & 0xFF);
+ free(remote_copy);
+ sblist_add(fwd_rules, rule);
+ free(match);
+ free(upstream);
+ free(remote);
+
+ return 0;
+}
+
static int usage(void) {
dprintf(2,
"MicroSocks SOCKS5 Server\n"
"------------------------\n"
- "usage: microsocks -1 -q -i listenip -p port -u user -P pass -b bindaddr -w ips\n"
+ "usage: microsocks -1 -q -i listenip -p port -u user -P pass -b bindaddr -w ips -f fwdrule\n"
"all arguments are optional.\n"
"by default listenip is 0.0.0.0 and port 1080.\n\n"
"option -q disables logging.\n"
@@ -401,6 +684,12 @@
" this is handy for programs like firefox that don't support\n"
" user/pass auth. for it to work you'd basically make one connection\n"
" with another program that supports it, and then you can use firefox too.\n"
+ "option -f specifies a forwarding rule of the form\n"
+ " match_name:match_port,[user:password@]upstream_name:upstream_port,remote_name:remote_port\n"
+ " this will cause requests that /match/ to be renamed to /remote/\n"
+ " and sent to the /upstream/ SOCKS5 proxy server.\n"
+ " this option may be specified multiple times.\n"
+ "option -V prints version information and exits.\n"
);
return 1;
}
@@ -416,7 +705,7 @@
const char *listenip = "0.0.0.0";
char *p, *q;
unsigned port = 1080;
- while((ch = getopt(argc, argv, ":1qb:i:p:u:P:w:")) != -1) {
+ while((ch = getopt(argc, argv, ":1qb:i:p:u:P:w:f:V")) != -1) {
switch(ch) {
case 'w': /* fall-through */
case '1':
@@ -456,11 +745,20 @@
case 'p':
port = atoi(optarg);
break;
+ case 'f':
+ if(!fwd_rules)
+ fwd_rules = sblist_new(sizeof(struct fwd_rule), 16);
+ if(fwd_rules_add(optarg))
+ return dprintf(2, "error: could not parse forwarding rule %s\n", optarg), 1;
+ break;
case ':':
dprintf(2, "error: option -%c requires an operand\n", optopt);
/* fall through */
case '?':
return usage();
+ case 'V':
+ dprintf(1, "MicroSocks %s\n", MICROSOCKS_VERSION);
+ return 0;
}
}
if((auth_user && !auth_pass) || (!auth_user && auth_pass)) {