diff --git a/README.md b/README.md index c904f288..21f50550 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ It is a port of [shadowsocks](https://github.com/shadowsocks/shadowsocks) created by [@clowwindy](https://github.com/clowwindy) maintained by [@madeye](https://github.com/madeye) and [@linusyang](https://github.com/linusyang). -Current version: 2.2.4 | [Changelog](debian/changelog) +Current version: 2.3.0 | [Changelog](debian/changelog) Travis CI: [![Travis CI](https://travis-ci.org/shadowsocks/shadowsocks-libev.png?branch=master)](https://travis-ci.org/shadowsocks/shadowsocks-libev) | Jenkins Matrix: [![Jenkins](https://jenkins.shadowvpn.org/buildStatus/icon?job=Shadowsocks-libev)](https://jenkins.shadowvpn.org/job/Shadowsocks-libev/) @@ -272,6 +272,12 @@ Usage [--acl ] config file of ACL (Access Control List) only available in local and server mode + [--manager_address ] UNIX domain socket address + only available in server and manager mode + + [--executable ] path to the executable of ss-server + only available in manager mode + [-v] verbose mode notes: diff --git a/shadowsocks-libev.8 b/shadowsocks-libev.8 index e0ca2092..252e6470 100644 --- a/shadowsocks-libev.8 +++ b/shadowsocks-libev.8 @@ -23,6 +23,7 @@ .ds Re \fBss-redir\fR .ds Se \fBss-server\fR .ds Tu \fBss-tunnel\fR +.ds Ma \fBss-manager\fR .ds Me \fBShadowsocks\fR .TH SHADOWSOCKS-LIBEV 8 "January 7, 2015" @@ -30,7 +31,7 @@ shadowsocks-libev \- a lightweight and secure socks5 proxy .SH SYNOPSIS -\*(Lo|\*(Re|\*(Se +\*(Lo|\*(Re|\*(Se|\*(Tu|\*(Ma \-s server_host \-p server_port \-l local_port \-k password \-m encrypt_method \-f pid_file @@ -48,6 +49,11 @@ machines to proxy TCP traffic. \*(Tu is a tool for local port forwarding. While \*(Lo works as a standard socks5 proxy, \*(Re works as a transparent proxy and requires netfilter's NAT module. For more information, check out the example section. +.PP +\*(Ma is a controller for multi-user management and traffic statistics, using UNIX +domain socket to talk with \*(Se. Also, it provides a UNIX domain socket or IP based +API for other software. About the details of this API, please refer to the protocol +section. .SH OPTIONS .TP @@ -75,7 +81,7 @@ not work. Start shadowsocks as a daemon with specific pid file. .TP .B \-t \fItimeout\fP -Set the socket timeout in secondes. The default value is 10. +Set the socket timeout in seconds. The default value is 10. .TP .B \-c \fIconfig_file\fP Use a configuration file. @@ -98,6 +104,12 @@ Enable TCP fast open. .TP .B \--acl \fIacl_config\fP Enable ACL (Access Control List). +.TP +.B \--manager-address \fIpath_to_unix_domain\fP +Enable manager mode. +.TP +.B \--executable \fIpath_to_server_executable\fP +Specify the executable path of ss-server for manager mode. .SH EXAMPLE \*(Re requires netfilter's NAT function. Here is an example: @@ -136,6 +148,29 @@ Enable ACL (Access Control List). # Start the shadowsocks-redir root@Wrt:~# ss-redir -u -c /etc/config/shadowsocks.json -f /var/run/shadowsocks.pid +.SH PROTOCOL +\*(Ma provides several APIs through UDP protocol: + + Send UDP commands in the following format to the manager-address provided to \*(Ma. + + command: [JSON data] + + To add a port: + + add: {"server_port": 8001, "password":"7cd308cc059"} + + To remove a port: + + remove: {"server_port": 8001} + + To receive a pong: + + ping + + Then \*(Ma will send back the traffic statistics: + + stat: {"8001":11370} + .SH SEE ALSO .BR iptables (8), /etc/shadowsocks-libev/config.json diff --git a/src/manager.c b/src/manager.c index cd851cc6..75309868 100644 --- a/src/manager.c +++ b/src/manager.c @@ -47,6 +47,7 @@ #include #include #include +#include #endif #include @@ -71,6 +72,7 @@ int verbose = 0; char *executable = "ss-server"; +char working_dir[128]; static struct cork_hash_table *server_table; @@ -91,9 +93,9 @@ static char *construct_command_line(struct manager_ctx *manager, struct server * memset(cmd, 0, BUF_SIZE); snprintf(cmd, BUF_SIZE, - "%s -p %s -m %s -k %s --manager-address %s -f %s_%s.pid", executable, + "%s -p %s -m %s -k %s --manager-address %s -f %s/.shadowsocks_%s.pid", executable, server->port, manager->method, server->password, manager->manager_address, - manager->manager_address, server->port); + working_dir, server->port); if (manager->acl != NULL) { int len = strlen(cmd); snprintf(cmd + len, BUF_SIZE - len, " --acl %s", manager->acl); @@ -261,7 +263,7 @@ static void stop_server(char *prefix, char *port) { char path[128]; int pid; - snprintf(path, 128, "%s_%s.pid", prefix, port); + snprintf(path, 128, "%s/.shadowsocks_%s.pid", prefix, port); FILE *f = fopen(path, "r"); if (f == NULL) { if (verbose) { @@ -334,7 +336,7 @@ static void manager_recv_cb(EV_P_ ev_io *w, int revents) goto ERROR_MSG; } - remove_server(manager->manager_address, server->port); + remove_server(working_dir, server->port); add_server(manager, server); char msg[3] = "ok"; @@ -353,7 +355,7 @@ static void manager_recv_cb(EV_P_ ev_io *w, int revents) goto ERROR_MSG; } - remove_server(manager->manager_address, server->port); + remove_server(working_dir, server->port); free(server); char msg[3] = "ok"; @@ -432,6 +434,84 @@ static void signal_cb(EV_P_ ev_signal *w, int revents) } } +int create_server_socket(const char *host, const char *port) +{ + struct addrinfo hints; + struct addrinfo *result, *rp, *ipv4v6bindall; + int s, server_sock; + + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_family = AF_UNSPEC; /* Return IPv4 and IPv6 choices */ + hints.ai_socktype = SOCK_DGRAM; /* We want a UDP socket */ + hints.ai_flags = AI_PASSIVE | AI_ADDRCONFIG; /* For wildcard IP address */ + hints.ai_protocol = IPPROTO_UDP; + + s = getaddrinfo(host, port, &hints, &result); + if (s != 0) { + LOGE("getaddrinfo: %s", gai_strerror(s)); + return -1; + } + + rp = result; + + /* + On Linux, with net.ipv6.bindv6only = 0 (the default), getaddrinfo(NULL) with + AI_PASSIVE returns 0.0.0.0 and :: (in this order). AI_PASSIVE was meant to + return a list of addresses to listen on, but it is impossible to listen on + 0.0.0.0 and :: at the same time, if :: implies dualstack mode. + */ + if (!host) { + ipv4v6bindall = result; + + /* Loop over all address infos found until a IPV6 address is found. */ + while (ipv4v6bindall) { + if (ipv4v6bindall->ai_family == AF_INET6) { + rp = ipv4v6bindall; /* Take first IPV6 address available */ + break; + } + ipv4v6bindall = ipv4v6bindall->ai_next; /* Get next address info, if any */ + } + } + + for (/*rp = result*/; rp != NULL; rp = rp->ai_next) { + server_sock = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); + if (server_sock == -1) { + continue; + } + + if (rp->ai_family == AF_INET6) { + int ipv6only = host ? 1 : 0; + setsockopt(server_sock, IPPROTO_IPV6, IPV6_V6ONLY, &ipv6only, sizeof(ipv6only)); + } + + int opt = 1; + setsockopt(server_sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); +#ifdef SO_NOSIGPIPE + set_nosigpipe(server_sock); +#endif + + s = bind(server_sock, rp->ai_addr, rp->ai_addrlen); + if (s == 0) { + /* We managed to bind successfully! */ + break; + } else { + ERROR("bind"); + } + + close(server_sock); + } + + if (rp == NULL) { + LOGE("cannot bind"); + return -1; + } + + freeaddrinfo(result); + + return server_sock; +} + + int main(int argc, char **argv) { @@ -634,6 +714,16 @@ int main(int argc, char **argv) run_as(user); } + struct passwd *pw = getpwuid(getuid()); + const char *homedir = pw->pw_dir; + snprintf(working_dir, 128, "%s/.shadowsocks", homedir); + + int err = mkdir(working_dir, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH); + if (err != 0 && errno != EEXIST) { + ERROR("mkdir"); + FATAL("unable to create working directory"); + } + server_table = cork_string_hash_table_new(MAX_PORT_NUM, 0); if (conf != NULL) { @@ -645,30 +735,37 @@ int main(int argc, char **argv) } } - struct sockaddr_un svaddr; int sfd; + ss_addr_t ip_addr = { .host = NULL, .port = NULL }; + parse_addr(manager_address, &ip_addr); + + if (ip_addr.host == NULL || ip_addr.port == NULL) { + struct sockaddr_un svaddr; + sfd = socket(AF_UNIX, SOCK_DGRAM, 0); /* Create server socket */ + if (sfd == -1) { + FATAL("socket"); + } - sfd = socket(AF_UNIX, SOCK_DGRAM, 0); /* Create server socket */ - if (sfd == -1) { - FATAL("socket"); - } - - setnonblocking(sfd); - - /* Construct well-known address and bind server socket to it */ + setnonblocking(sfd); - if (remove(manager_address) == -1 && errno != ENOENT) { - ERROR("bind"); - exit(EXIT_FAILURE); - } + if (remove(manager_address) == -1 && errno != ENOENT) { + ERROR("bind"); + exit(EXIT_FAILURE); + } - memset(&svaddr, 0, sizeof(struct sockaddr_un)); - svaddr.sun_family = AF_UNIX; - strncpy(svaddr.sun_path, manager_address, sizeof(svaddr.sun_path) - 1); + memset(&svaddr, 0, sizeof(struct sockaddr_un)); + svaddr.sun_family = AF_UNIX; + strncpy(svaddr.sun_path, manager_address, sizeof(svaddr.sun_path) - 1); - if (bind(sfd, (struct sockaddr *) &svaddr, sizeof(struct sockaddr_un)) == -1) { - ERROR("bind"); - exit(EXIT_FAILURE); + if (bind(sfd, (struct sockaddr *) &svaddr, sizeof(struct sockaddr_un)) == -1) { + ERROR("bind"); + exit(EXIT_FAILURE); + } + } else { + sfd = create_server_socket(ip_addr.host, ip_addr.port); + if (sfd == -1) { + FATAL("socket"); + } } manager.fd = sfd; @@ -690,7 +787,7 @@ int main(int argc, char **argv) while((entry = cork_hash_table_iterator_next(&server_iter)) != NULL) { struct server *server = (struct server*)entry->value; - stop_server(manager_address, server->port); + stop_server(working_dir, server->port); } #ifdef __MINGW32__ diff --git a/src/server.c b/src/server.c index 08152f1f..c80d6a36 100644 --- a/src/server.c +++ b/src/server.c @@ -60,6 +60,7 @@ #define SET_INTERFACE #endif +#include "netutils.h" #include "utils.h" #include "acl.h" #include "server.h" @@ -127,7 +128,7 @@ static struct cork_dllist connections; static void stat_update_cb(EV_P_ ev_timer *watcher, int revents) { struct sockaddr_un svaddr, claddr; - int sfd; + int sfd = -1; size_t msgLen; char resp[BUF_SIZE]; @@ -135,38 +136,69 @@ static void stat_update_cb(EV_P_ ev_timer *watcher, int revents) LOGI("update traffic stat: tx: %"PRIu64" rx: %"PRIu64"", tx, rx); } - sfd = socket(AF_UNIX, SOCK_DGRAM, 0); - if (sfd == -1) { - ERROR("stat_socket"); - return; - } + snprintf(resp, BUF_SIZE, "stat: {\"%s\":%"PRIu64"}", server_port, tx + rx); + msgLen = strlen(resp) + 1; - memset(&claddr, 0, sizeof(struct sockaddr_un)); - claddr.sun_family = AF_UNIX; - snprintf(claddr.sun_path, sizeof(claddr.sun_path), "/tmp/shadowsocks.%s", server_port); + ss_addr_t ip_addr = { .host = NULL, .port = NULL }; + parse_addr(manager_address, &ip_addr); - unlink(claddr.sun_path); + if (ip_addr.host == NULL || ip_addr.port == NULL) { + sfd = socket(AF_UNIX, SOCK_DGRAM, 0); + if (sfd == -1) { + ERROR("stat_socket"); + return; + } - if (bind(sfd, (struct sockaddr *) &claddr, sizeof(struct sockaddr_un)) == -1) { - ERROR("stat_bind"); - close(sfd); - return; - } + memset(&claddr, 0, sizeof(struct sockaddr_un)); + claddr.sun_family = AF_UNIX; + snprintf(claddr.sun_path, sizeof(claddr.sun_path), "/tmp/shadowsocks.%s", server_port); - memset(&svaddr, 0, sizeof(struct sockaddr_un)); - svaddr.sun_family = AF_UNIX; - strncpy(svaddr.sun_path, manager_address, sizeof(svaddr.sun_path) - 1); + unlink(claddr.sun_path); - snprintf(resp, BUF_SIZE, "stat: {\"%s\":%"PRIu64"}", server_port, tx + rx); - msgLen = strlen(resp) + 1; - if (sendto(sfd, resp, strlen(resp) + 1, 0, (struct sockaddr *) &svaddr, - sizeof(struct sockaddr_un)) != msgLen) { - ERROR("stat_sendto"); - return; + if (bind(sfd, (struct sockaddr *) &claddr, sizeof(struct sockaddr_un)) == -1) { + ERROR("stat_bind"); + close(sfd); + return; + } + + memset(&svaddr, 0, sizeof(struct sockaddr_un)); + svaddr.sun_family = AF_UNIX; + strncpy(svaddr.sun_path, manager_address, sizeof(svaddr.sun_path) - 1); + + if (sendto(sfd, resp, strlen(resp) + 1, 0, (struct sockaddr *) &svaddr, + sizeof(struct sockaddr_un)) != msgLen) { + ERROR("stat_sendto"); + close(sfd); + return; + } + + unlink(claddr.sun_path); + + } else { + struct sockaddr_storage storage; + memset(&storage, 0, sizeof(struct sockaddr_storage)); + if (get_sockaddr(ip_addr.host, ip_addr.port, &storage, 0) == -1) { + ERROR("failed to parse the manager addr"); + return; + } + + sfd = socket(storage.ss_family, SOCK_DGRAM, 0); + + if (sfd == -1) { + ERROR("stat_socket"); + return; + } + + size_t addr_len = get_sockaddr_len((struct sockaddr *)&storage); + if (sendto(sfd, resp, strlen(resp) + 1, 0, (struct sockaddr *)&storage, + addr_len) != msgLen) { + ERROR("stat_sendto"); + close(sfd); + return; + } } close(sfd); - unlink(claddr.sun_path); } static void free_connections(struct ev_loop *loop)