diff --git a/configure b/configure index b3dfb3ae..0b6294ab 100755 --- a/configure +++ b/configure @@ -1,6 +1,6 @@ #! /bin/sh # Guess values for system-dependent variables and create Makefiles. -# Generated by GNU Autoconf 2.69 for shadowsocks-libev 2.2.4. +# Generated by GNU Autoconf 2.69 for shadowsocks-libev 2.3.0. # # Report bugs to . # @@ -590,8 +590,8 @@ MAKEFLAGS= # Identity of this package. PACKAGE_NAME='shadowsocks-libev' PACKAGE_TARNAME='shadowsocks-libev' -PACKAGE_VERSION='2.2.4' -PACKAGE_STRING='shadowsocks-libev 2.2.4' +PACKAGE_VERSION='2.3.0' +PACKAGE_STRING='shadowsocks-libev 2.3.0' PACKAGE_BUGREPORT='max.c.lv@gmail.com' PACKAGE_URL='' @@ -1339,7 +1339,7 @@ if test "$ac_init_help" = "long"; then # Omit some internal or obsolete options to make the list less imposing. # This message is too long to be a string in the A/UX 3.1 sh. cat <<_ACEOF -\`configure' configures shadowsocks-libev 2.2.4 to adapt to many kinds of systems. +\`configure' configures shadowsocks-libev 2.3.0 to adapt to many kinds of systems. Usage: $0 [OPTION]... [VAR=VALUE]... @@ -1410,7 +1410,7 @@ fi if test -n "$ac_init_help"; then case $ac_init_help in - short | recursive ) echo "Configuration of shadowsocks-libev 2.2.4:";; + short | recursive ) echo "Configuration of shadowsocks-libev 2.3.0:";; esac cat <<\_ACEOF @@ -1536,7 +1536,7 @@ fi test -n "$ac_init_help" && exit $ac_status if $ac_init_version; then cat <<\_ACEOF -shadowsocks-libev configure 2.2.4 +shadowsocks-libev configure 2.3.0 generated by GNU Autoconf 2.69 Copyright (C) 2012 Free Software Foundation, Inc. @@ -2059,7 +2059,7 @@ cat >config.log <<_ACEOF This file contains any messages produced by compilers while running configure, to aid debugging if configure makes a mistake. -It was created by shadowsocks-libev $as_me 2.2.4, which was +It was created by shadowsocks-libev $as_me 2.3.0, which was generated by GNU Autoconf 2.69. Invocation command line was $ $0 $@ @@ -2880,7 +2880,7 @@ fi # Define the identity of the package. PACKAGE='shadowsocks-libev' - VERSION='2.2.4' + VERSION='2.3.0' cat >>confdefs.h <<_ACEOF @@ -15894,7 +15894,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 # report actual input values of CONFIG_FILES etc. instead of their # values after options handling. ac_log=" -This file was extended by shadowsocks-libev $as_me 2.2.4, which was +This file was extended by shadowsocks-libev $as_me 2.3.0, which was generated by GNU Autoconf 2.69. Invocation command line was CONFIG_FILES = $CONFIG_FILES @@ -15960,7 +15960,7 @@ _ACEOF cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`" ac_cs_version="\\ -shadowsocks-libev config.status 2.2.4 +shadowsocks-libev config.status 2.3.0 configured by $0, generated by GNU Autoconf 2.69, with options \\"\$ac_cs_config\\" diff --git a/configure.ac b/configure.ac index 74e1e45f..702ee259 100755 --- a/configure.ac +++ b/configure.ac @@ -2,7 +2,7 @@ dnl -*- Autoconf -*- dnl Process this file with autoconf to produce a configure script. AC_PREREQ([2.67]) -AC_INIT([shadowsocks-libev], [2.2.4], [max.c.lv@gmail.com]) +AC_INIT([shadowsocks-libev], [2.3.0], [max.c.lv@gmail.com]) AC_CONFIG_SRCDIR([src/encrypt.c]) AC_CONFIG_HEADERS([config.h]) AC_CONFIG_AUX_DIR(auto) diff --git a/debian/changelog b/debian/changelog index f1c6417a..aae740cd 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,5 +1,6 @@ -shadowsocks-libev (2.2.4-1) unstable; urgency=low +shadowsocks-libev (2.3.0-1) unstable; urgency=low + * Add manager mode to support multi-user and traffic stat. * Fix a build issue on OS X El Capitan. -- Max Lv Thu, 30 Jul 2015 17:30:43 +0900 diff --git a/openwrt/Makefile b/openwrt/Makefile index 55d1cc41..3160f97a 100644 --- a/openwrt/Makefile +++ b/openwrt/Makefile @@ -1,7 +1,7 @@ include $(TOPDIR)/rules.mk PKG_NAME:=shadowsocks-libev -PKG_VERSION:=2.2.4 +PKG_VERSION:=2.3.0 PKG_RELEASE=$(PKG_SOURCE_VERSION) PKG_SOURCE_URL:=https://github.com/shadowsocks/shadowsocks-libev/archive diff --git a/src/Makefile.am b/src/Makefile.am index 73d70936..e389d743 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -19,7 +19,7 @@ SS_COMMON_LIBS = $(PTHREAD_LIBS) \ bin_PROGRAMS = ss-local ss-tunnel if !BUILD_WINCOMPAT -bin_PROGRAMS += ss-server +bin_PROGRAMS += ss-server ss-manager endif ss_local_SOURCES = utils.c \ @@ -52,9 +52,15 @@ ss_server_SOURCES = utils.c \ resolv.c \ server.c +ss_manager_SOURCES = utils.c \ + jconf.c \ + json.c \ + manager.c + ss_local_LDADD = $(SS_COMMON_LIBS) ss_tunnel_LDADD = $(SS_COMMON_LIBS) ss_server_LDADD = $(SS_COMMON_LIBS) +ss_manager_LDADD = $(SS_COMMON_LIBS) ss_local_LDADD += $(top_builddir)/libudns/libudns.la ss_tunnel_LDADD += $(top_builddir)/libudns/libudns.la ss_server_LDADD += $(top_builddir)/libudns/libudns.la diff --git a/src/Makefile.in b/src/Makefile.in index 257c9d1f..94af3db0 100644 --- a/src/Makefile.in +++ b/src/Makefile.in @@ -55,7 +55,7 @@ build_triplet = @build@ host_triplet = @host@ bin_PROGRAMS = ss-local$(EXEEXT) ss-tunnel$(EXEEXT) $(am__EXEEXT_1) \ $(am__EXEEXT_2) -@BUILD_WINCOMPAT_FALSE@am__append_1 = ss-server +@BUILD_WINCOMPAT_FALSE@am__append_1 = ss-server ss-manager @BUILD_WINCOMPAT_TRUE@am__append_2 = win32.c @BUILD_WINCOMPAT_TRUE@am__append_3 = win32.c @BUILD_REDIRECTOR_TRUE@am__append_4 = ss-redir @@ -133,7 +133,8 @@ libshadowsocks_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ $(libshadowsocks_la_CFLAGS) $(CFLAGS) \ $(libshadowsocks_la_LDFLAGS) $(LDFLAGS) -o $@ -@BUILD_WINCOMPAT_FALSE@am__EXEEXT_1 = ss-server$(EXEEXT) +@BUILD_WINCOMPAT_FALSE@am__EXEEXT_1 = ss-server$(EXEEXT) \ +@BUILD_WINCOMPAT_FALSE@ ss-manager$(EXEEXT) @BUILD_REDIRECTOR_TRUE@am__EXEEXT_2 = ss-redir$(EXEEXT) PROGRAMS = $(bin_PROGRAMS) am__ss_local_SOURCES_DIST = utils.c jconf.c json.c encrypt.c \ @@ -151,6 +152,10 @@ ss_local_DEPENDENCIES = $(am__DEPENDENCIES_2) \ ss_local_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(ss_local_CFLAGS) \ $(CFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@ +am_ss_manager_OBJECTS = utils.$(OBJEXT) jconf.$(OBJEXT) json.$(OBJEXT) \ + manager.$(OBJEXT) +ss_manager_OBJECTS = $(am_ss_manager_OBJECTS) +ss_manager_DEPENDENCIES = $(am__DEPENDENCIES_2) am__ss_redir_SOURCES_DIST = utils.c jconf.c json.c encrypt.c \ netutils.c cache.c udprelay.c redir.c @BUILD_REDIRECTOR_TRUE@am_ss_redir_OBJECTS = ss_redir-utils.$(OBJEXT) \ @@ -220,10 +225,12 @@ AM_V_GEN = $(am__v_GEN_@AM_V@) am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) am__v_GEN_0 = @echo " GEN " $@; SOURCES = $(libshadowsocks_la_SOURCES) $(ss_local_SOURCES) \ - $(ss_redir_SOURCES) $(ss_server_SOURCES) $(ss_tunnel_SOURCES) + $(ss_manager_SOURCES) $(ss_redir_SOURCES) $(ss_server_SOURCES) \ + $(ss_tunnel_SOURCES) DIST_SOURCES = $(am__libshadowsocks_la_SOURCES_DIST) \ - $(am__ss_local_SOURCES_DIST) $(am__ss_redir_SOURCES_DIST) \ - $(ss_server_SOURCES) $(am__ss_tunnel_SOURCES_DIST) + $(am__ss_local_SOURCES_DIST) $(ss_manager_SOURCES) \ + $(am__ss_redir_SOURCES_DIST) $(ss_server_SOURCES) \ + $(am__ss_tunnel_SOURCES_DIST) am__can_run_installinfo = \ case $$AM_UPDATE_INFO_DIR in \ n|no|NO) false;; \ @@ -384,9 +391,15 @@ ss_server_SOURCES = utils.c \ resolv.c \ server.c +ss_manager_SOURCES = utils.c \ + jconf.c \ + json.c \ + manager.c + ss_local_LDADD = $(SS_COMMON_LIBS) $(top_builddir)/libudns/libudns.la ss_tunnel_LDADD = $(SS_COMMON_LIBS) $(top_builddir)/libudns/libudns.la ss_server_LDADD = $(SS_COMMON_LIBS) $(top_builddir)/libudns/libudns.la +ss_manager_LDADD = $(SS_COMMON_LIBS) ss_local_CFLAGS = $(AM_CFLAGS) -DUDPRELAY_LOCAL ss_tunnel_CFLAGS = $(AM_CFLAGS) -DUDPRELAY_LOCAL -DUDPRELAY_TUNNEL ss_server_CFLAGS = $(AM_CFLAGS) -DUDPRELAY_REMOTE @@ -525,6 +538,9 @@ clean-binPROGRAMS: ss-local$(EXEEXT): $(ss_local_OBJECTS) $(ss_local_DEPENDENCIES) $(EXTRA_ss_local_DEPENDENCIES) @rm -f ss-local$(EXEEXT) $(AM_V_CCLD)$(ss_local_LINK) $(ss_local_OBJECTS) $(ss_local_LDADD) $(LIBS) +ss-manager$(EXEEXT): $(ss_manager_OBJECTS) $(ss_manager_DEPENDENCIES) $(EXTRA_ss_manager_DEPENDENCIES) + @rm -f ss-manager$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(ss_manager_OBJECTS) $(ss_manager_LDADD) $(LIBS) ss-redir$(EXEEXT): $(ss_redir_OBJECTS) $(ss_redir_DEPENDENCIES) $(EXTRA_ss_redir_DEPENDENCIES) @rm -f ss-redir$(EXEEXT) $(AM_V_CCLD)$(ss_redir_LINK) $(ss_redir_OBJECTS) $(ss_redir_LDADD) $(LIBS) @@ -541,6 +557,8 @@ mostlyclean-compile: distclean-compile: -rm -f *.tab.c +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/jconf.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/json.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libshadowsocks_la-acl.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libshadowsocks_la-cache.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libshadowsocks_la-encrypt.Plo@am__quote@ @@ -551,6 +569,7 @@ distclean-compile: @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libshadowsocks_la-udprelay.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libshadowsocks_la-utils.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libshadowsocks_la-win32.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/manager.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ss_local-acl.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ss_local-cache.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ss_local-encrypt.Po@am__quote@ @@ -588,6 +607,7 @@ distclean-compile: @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ss_tunnel-udprelay.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ss_tunnel-utils.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ss_tunnel-win32.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/utils.Po@am__quote@ .c.o: @am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.o$$||'`;\ diff --git a/src/jconf.c b/src/jconf.c index 3807d22d..400a8e41 100644 --- a/src/jconf.c +++ b/src/jconf.c @@ -159,6 +159,21 @@ jconf_t *read_jconf(const char * file) conf.remote_addr[0].port = NULL; conf.remote_num = 1; } + } else if (strcmp(name, "port_password") == 0) { + if (value->type == json_object) { + for (j = 0; j < value->u.object.length; j++) { + if (j >= MAX_PORT_NUM) { + break; + } + json_value *v = value->u.object.values[j].value; + if (v->type == json_string) { + conf.port_password[j].port = ss_strndup(value->u.object.values[j].name, + value->u.object.values[j].name_length); + conf.port_password[j].password = to_string(v); + conf.port_password_num = j + 1; + } + } + } } else if (strcmp(name, "server_port") == 0) { conf.remote_port = to_string(value); } else if (strcmp(name, "local_address") == 0) { diff --git a/src/jconf.h b/src/jconf.h index b6286f6d..e1e0b946 100644 --- a/src/jconf.h +++ b/src/jconf.h @@ -22,8 +22,9 @@ #ifndef _JCONF_H #define _JCONF_H +#define MAX_PORT_NUM 1024 #define MAX_REMOTE_NUM 10 -#define MAX_CONF_SIZE 16 * 1024 +#define MAX_CONF_SIZE 128 * 1024 #define MAX_DNS_NUM 4 #define MAX_CONNECT_TIMEOUT 10 #define MIN_UDP_TIMEOUT 60 @@ -33,9 +34,16 @@ typedef struct { char *port; } ss_addr_t; +typedef struct { + char *port; + char *password; +} ss_port_password_t; + typedef struct { int remote_num; ss_addr_t remote_addr[MAX_REMOTE_NUM]; + int port_password_num; + ss_port_password_t port_password[MAX_PORT_NUM]; char *remote_port; char *local_addr; char *local_port; diff --git a/src/manager.c b/src/manager.c new file mode 100644 index 00000000..b5133184 --- /dev/null +++ b/src/manager.c @@ -0,0 +1,720 @@ +/* + * server.c - Provide shadowsocks service + * + * Copyright (C) 2013 - 2015, Max Lv + * + * This file is part of the shadowsocks-libev. + * + * shadowsocks-libev is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * shadowsocks-libev is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with shadowsocks-libev; see the file COPYING. If not, see + * . + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef __MINGW32__ +#include +#include +#include +#include +#include +#include +#include +#include +#endif + +#include +#include + +#ifdef __MINGW32__ +#include "win32.h" +#endif + +#if defined(HAVE_SYS_IOCTL_H) && defined(HAVE_NET_IF_H) && defined(__linux__) +#include +#include +#define SET_INTERFACE +#endif + +#include "json.h" +#include "utils.h" +#include "manager.h" + +#ifndef EAGAIN +#define EAGAIN EWOULDBLOCK +#endif + +#ifndef EWOULDBLOCK +#define EWOULDBLOCK EAGAIN +#endif + +#ifndef BUF_SIZE +#define BUF_SIZE 2048 +#endif + +#ifndef SSMAXCONN +#define SSMAXCONN 1024 +#endif + +#ifndef UPDATE_INTERVAL +#define UPDATE_INTERVAL 30 +#endif + +int verbose = 0; +char *executable = "ss-server"; + +static struct cork_hash_table *server_table; + +#ifndef __MINGW32__ +int setnonblocking(int fd) +{ + int flags; + if (-1 == (flags = fcntl(fd, F_GETFL, 0))) { + flags = 0; + } + return fcntl(fd, F_SETFL, flags | O_NONBLOCK); +} +#endif + +static char *construct_command_line(struct manager_ctx *manager, struct server *server) { + static char cmd[BUF_SIZE]; + int i; + + memset(cmd, 0, BUF_SIZE); + snprintf(cmd, BUF_SIZE, + "%s -p %s -m %s -k %s --manager-address %s -f %s_%s.pid", executable, + server->port, manager->method, server->password, manager->manager_address, + manager->manager_address, server->port); + if (manager->acl != NULL) { + int len = strlen(cmd); + snprintf(cmd + len, BUF_SIZE - len, " --acl %s", manager->acl); + } + if (manager->timeout != NULL) { + int len = strlen(cmd); + snprintf(cmd + len, BUF_SIZE - len, " -t %s", manager->timeout); + } + if (manager->user != NULL) { + int len = strlen(cmd); + snprintf(cmd + len, BUF_SIZE - len, " -a %s", manager->user); + } + if (manager->verbose) { + int len = strlen(cmd); + snprintf(cmd + len, BUF_SIZE - len, " -v"); + } + if (manager->mode == UDP_ONLY) { + int len = strlen(cmd); + snprintf(cmd + len, BUF_SIZE - len, " -U"); + } + if (manager->mode == TCP_AND_UDP) { + int len = strlen(cmd); + snprintf(cmd + len, BUF_SIZE - len, " -u"); + } + if (manager->mode == TCP_AND_UDP) { + int len = strlen(cmd); + snprintf(cmd + len, BUF_SIZE - len, " -u"); + } + if (manager->fast_open) { + int len = strlen(cmd); + snprintf(cmd + len, BUF_SIZE - len, " --fast_open"); + } + for (i = 0; i < manager->nameserver_num; i++) { + int len = strlen(cmd); + snprintf(cmd + len, BUF_SIZE - len, " -d %s", manager->nameservers[i]); + } + for (i = 0; i < manager->host_num; i++) { + int len = strlen(cmd); + snprintf(cmd + len, BUF_SIZE - len, " -s %s", manager->hosts[i]); + } + + if (verbose) { + LOGI("cmd: %s", cmd); + } + + return cmd; +} + + +static char *get_data(char *buf, int len) { + char *data; + int pos = 0; + + while(buf[pos] != '{' && pos < len) pos++; + if (pos == len) return NULL; + data = buf + pos - 1; + + return data; +} + +static char *get_action(char *buf, int len) { + char *action; + int pos = 0; + + while(isspace(buf[pos]) && pos < len) pos++; + if (pos == len) return NULL; + action = buf + pos; + + while((!isspace(buf[pos]) && buf[pos] != ':') && pos < len) pos++; + buf[pos] = '\0'; + + return action; +} + +static struct server *get_server(char *buf, int len) { + char *data = get_data(buf, len); + char error_buf[512]; + struct server *server = (struct server *)malloc(sizeof(struct server)); + + if (data == NULL) { + LOGE("No data found"); + return NULL; + } + + memset(server, 0, sizeof(struct server)); + json_settings settings = { 0 }; + json_value *obj = json_parse_ex(&settings, data, strlen(data), error_buf); + + if (obj == NULL) { + LOGE("%s", error_buf); + return NULL; + } + + if (obj->type == json_object) { + int i = 0; + for (i = 0; i < obj->u.object.length; i++) { + char *name = obj->u.object.values[i].name; + json_value *value = obj->u.object.values[i].value; + if (strcmp(name, "server_port") == 0) { + if (value->type == json_string) { + strncpy(server->port, value->u.string.ptr, 8); + } else if (value->type == json_integer) { + snprintf(server->port, 8, "%"PRIu64"", value->u.integer); + } + } else if (strcmp(name, "password") == 0) { + if (value->type == json_string) { + strncpy(server->password, value->u.string.ptr, 128); + } + } else { + LOGE("invalid data: %s", data); + json_value_free(obj); + return NULL; + } + } + } + + json_value_free(obj); + return server; +} + +static int parse_traffic(char *buf, int len, char *port, uint64_t *traffic) { + char *data = get_data(buf, len); + char error_buf[512]; + json_settings settings = { 0 }; + + if (data == NULL) { + LOGE("No data found"); + return -1; + } + + json_value *obj = json_parse_ex(&settings, data, strlen(data), error_buf); + if (obj == NULL) { + LOGE("%s", error_buf); + return -1; + } + + if (obj->type == json_object) { + int i = 0; + for (i = 0; i < obj->u.object.length; i++) { + char *name = obj->u.object.values[i].name; + json_value *value = obj->u.object.values[i].value; + if (value->type == json_integer) { + strncpy(port, name, 8); + *traffic = value->u.integer; + } + } + } + + json_value_free(obj); + return 0; +} + +static void add_server(struct manager_ctx *manager, struct server *server) +{ + bool new = false; + cork_hash_table_put(server_table, (void *)server->port, (void *)server, &new, NULL, NULL); + + char *cmd = construct_command_line(manager, server); + if (system(cmd) == -1) { + ERROR("add_server_system"); + } +} + +static void stop_server(char *prefix, char *port) +{ + char path[128]; + int pid; + snprintf(path, 128, "%s_%s.pid", prefix, port); + FILE *f = fopen(path, "r"); + if (f == NULL) { + if (verbose) { + LOGE("unable to open pid file"); + } + return; + } + if (fscanf(f, "%d", &pid) != EOF) { + kill(pid, SIGTERM); + } + fclose(f); + +} + +static void remove_server(char *prefix, char *port) +{ + char *old_port = NULL; + struct server *old_server = NULL; + + cork_hash_table_delete(server_table, (void *)port, (void **)&old_port, (void **)&old_server); + + if (old_server != NULL) { + free(old_server); + } + + stop_server(prefix, port); +} + +static void update_stat(char *port, uint64_t traffic) +{ + void *ret = cork_hash_table_get(server_table, (void*)port); + if (ret != NULL) { + struct server *server = (struct server *)ret; + server->traffic = traffic; + } +} + +static void manager_recv_cb(EV_P_ ev_io *w, int revents) +{ + struct manager_ctx *manager = (struct manager_ctx *)w; + socklen_t len; + size_t r; + struct sockaddr_un claddr; + char buf[BUF_SIZE]; + + memset(buf, 0, BUF_SIZE); + + len = sizeof(struct sockaddr_un); + r = recvfrom(manager->fd, buf, BUF_SIZE, 0, (struct sockaddr *) &claddr, &len); + if (r == -1) { + ERROR("manager_recvfrom"); + return; + } + + if (r > BUF_SIZE / 2) { + LOGE("too large request: %d", (int)r); + return; + } + + char *action = get_action(buf, r); + + if (strcmp(action, "add") == 0) { + struct server *server = get_server(buf, r); + + if (server == NULL || server->port[0] == 0 || server->password[0] == 0) { + LOGE("invalid command: %s:%s", buf, get_data(buf, r)); + if (server != NULL) { + free(server); + } + goto ERROR_MSG; + } + + remove_server(manager->manager_address, server->port); + add_server(manager, server); + + char msg[3] = "ok"; + if (sendto(manager->fd, msg, 3, 0, (struct sockaddr *)&claddr, len) != 3) { + ERROR("add_sendto"); + } + + } else if (strcmp(action, "remove") == 0) { + struct server *server = get_server(buf, r); + + if (server == NULL || server->port[0] == 0) { + LOGE("invalid command: %s:%s", buf, get_data(buf, r)); + if (server != NULL) { + free(server); + } + goto ERROR_MSG; + } + + remove_server(manager->manager_address, server->port); + free(server); + + char msg[3] = "ok"; + if (sendto(manager->fd, msg, 3, 0, (struct sockaddr *)&claddr, len) != 3) { + ERROR("remove_sendto"); + } + + } else if (strcmp(action, "stat") == 0) { + char port[8]; + uint64_t traffic = 0; + + if (parse_traffic(buf, r, port, &traffic) == -1) { + LOGE("invalid command: %s:%s", buf, get_data(buf, r)); + return; + } + + update_stat(port, traffic); + + } else if (strcmp(action, "ping") == 0) { + + struct cork_hash_table_entry *entry; + struct cork_hash_table_iterator server_iter; + + char buf[BUF_SIZE]; + + memset(buf, 0, BUF_SIZE); + sprintf(buf, "stat: {"); + + cork_hash_table_iterator_init(server_table, &server_iter); + + while((entry = cork_hash_table_iterator_next(&server_iter)) != NULL) { + struct server *server = (struct server*)entry->value; + size_t pos = strlen(buf); + if (pos > BUF_SIZE / 2) { + buf[pos - 1] = '}'; + if (sendto(manager->fd, buf, pos + 1, 0, (struct sockaddr *)&claddr, len) + != pos + 1) { + ERROR("ping_sendto"); + } + memset(buf, 0, BUF_SIZE); + } else { + sprintf(buf + pos, "\"%s\":%"PRIu64",", server->port, server->traffic); + } + } + + size_t pos = strlen(buf); + if (pos > 7) { + buf[pos - 1] = '}'; + } else { + buf[pos] = '}'; + } + + if (sendto(manager->fd, buf, pos + 1, 0, (struct sockaddr *)&claddr, len) + != pos + 1) { + ERROR("ping_sendto"); + } + } + + return; + +ERROR_MSG: + strcpy(buf, "err"); + if (sendto(manager->fd, buf, 4, 0, (struct sockaddr *)&claddr, len) != 4) { + ERROR("error_sendto"); + } +} + +static void signal_cb(EV_P_ ev_signal *w, int revents) +{ + if (revents & EV_SIGNAL) { + switch (w->signum) { + case SIGINT: + case SIGTERM: + ev_unloop(EV_A_ EVUNLOOP_ALL); + } + } +} + +int main(int argc, char **argv) +{ + + int i, c; + int pid_flags = 0; + char *acl = NULL; + char *user = NULL; + char *password = NULL; + char *timeout = NULL; + char *method = NULL; + char *pid_path = NULL; + char *conf_path = NULL; + char *iface = NULL; + char *manager_address = NULL; + + int fast_open = 0; + int mode = TCP_ONLY; + + int server_num = 0; + char *server_host[MAX_REMOTE_NUM]; + + char * nameservers[MAX_DNS_NUM + 1]; + int nameserver_num = 0; + + jconf_t *conf = NULL; + + int option_index = 0; + static struct option long_options[] = + { + { "fast-open", no_argument, 0, 0 }, + { "acl", required_argument, 0, 0 }, + { "manager-address", required_argument, 0, 0 }, + { "executable", required_argument, 0, 0 }, + { 0, 0, 0, 0 } + }; + + opterr = 0; + + USE_TTY(); + + while ((c = getopt_long(argc, argv, "f:s:l:k:t:m:c:i:d:a:uUv", + long_options, &option_index)) != -1) { + switch (c) { + case 0: + if (option_index == 0) { + fast_open = 1; + } else if (option_index == 1) { + acl = optarg; + } else if (option_index == 2) { + manager_address = optarg; + } else if (option_index == 3) { + executable = optarg; + } + break; + case 's': + if (server_num < MAX_REMOTE_NUM) { + server_host[server_num++] = optarg; + } + break; + case 'k': + password = optarg; + break; + case 'f': + pid_flags = 1; + pid_path = optarg; + break; + case 't': + timeout = optarg; + break; + case 'm': + method = optarg; + break; + case 'c': + conf_path = optarg; + break; + case 'i': + iface = optarg; + break; + case 'd': + if (nameserver_num < MAX_DNS_NUM) { + nameservers[nameserver_num++] = optarg; + } + break; + case 'a': + user = optarg; + break; + case 'u': + mode = TCP_AND_UDP; + break; + case 'U': + mode = UDP_ONLY; + break; + case 'v': + verbose = 1; + break; + } + } + + if (opterr) { + usage(); + exit(EXIT_FAILURE); + } + + if (conf_path != NULL) { + conf = read_jconf(conf_path); + if (server_num == 0) { + server_num = conf->remote_num; + for (i = 0; i < server_num; i++) { + server_host[i] = conf->remote_addr[i].host; + } + } + if (password == NULL) { + password = conf->password; + } + if (method == NULL) { + method = conf->method; + } + if (timeout == NULL) { + timeout = conf->timeout; + } +#ifdef TCP_FASTOPEN + if (fast_open == 0) { + fast_open = conf->fast_open; + } +#endif + if (conf->nameserver != NULL) { + nameservers[nameserver_num++] = conf->nameserver; + } + } + + if (server_num == 0) { + server_host[server_num++] = NULL; + } + + if (method == NULL) { + method = "table"; + } + + if (timeout == NULL) { + timeout = "60"; + } + + if (pid_flags) { + USE_SYSLOG(argv[0]); + daemonize(pid_path); + } + + if (server_num == 0 || manager_address == NULL) { + usage(); + exit(EXIT_FAILURE); + } + + if (fast_open == 1) { +#ifdef TCP_FASTOPEN + LOGI("using tcp fast open"); +#else + LOGE("tcp fast open is not supported by this environment"); +#endif + } + +#ifdef __MINGW32__ + winsock_init(); +#else + // ignore SIGPIPE + signal(SIGPIPE, SIG_IGN); + signal(SIGCHLD, SIG_IGN); + signal(SIGABRT, SIG_IGN); +#endif + + struct ev_signal sigint_watcher; + struct ev_signal sigterm_watcher; + ev_signal_init(&sigint_watcher, signal_cb, SIGINT); + ev_signal_init(&sigterm_watcher, signal_cb, SIGTERM); + ev_signal_start(EV_DEFAULT, &sigint_watcher); + ev_signal_start(EV_DEFAULT, &sigterm_watcher); + + struct manager_ctx manager; + memset(&manager, 0, sizeof(struct manager_ctx)); + + manager.fast_open = fast_open; + manager.verbose = verbose; + manager.mode = mode; + manager.password = password; + manager.timeout = timeout; + manager.method = method; + manager.iface = iface; + manager.acl = acl; + manager.user = user; + manager.manager_address = manager_address; + manager.hosts = server_host; + manager.host_num = server_num; + manager.nameservers = nameservers; + manager.nameserver_num = nameserver_num; + + // inilitialize ev loop + struct ev_loop *loop = EV_DEFAULT; + + // setuid + if (user != NULL) { + run_as(user); + } + + server_table = cork_string_hash_table_new(MAX_PORT_NUM, 0); + + if (conf != NULL) { + for (i = 0; i < conf->port_password_num; i++) { + struct server *server = (struct server *)malloc(sizeof(struct server)); + strncpy(server->port, conf->port_password[i].port, 8); + strncpy(server->password, conf->port_password[i].password, 128); + add_server(&manager, server); + } + } + + struct sockaddr_un svaddr; + int sfd; + + 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 */ + + 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); + + if (bind(sfd, (struct sockaddr *) &svaddr, sizeof(struct sockaddr_un)) == -1) { + ERROR("bind"); + exit(EXIT_FAILURE); + } + + manager.fd = sfd; + ev_io_init(&manager.io, manager_recv_cb, manager.fd, EV_READ); + ev_io_start(loop, &manager.io); + + // start ev loop + ev_run(loop, 0); + + if (verbose) { + LOGI("closed gracefully"); + } + + // Clean up + struct cork_hash_table_entry *entry; + struct cork_hash_table_iterator server_iter; + + cork_hash_table_iterator_init(server_table, &server_iter); + + while((entry = cork_hash_table_iterator_next(&server_iter)) != NULL) { + struct server *server = (struct server*)entry->value; + stop_server(manager_address, server->port); + } + +#ifdef __MINGW32__ + winsock_cleanup(); +#endif + + ev_signal_stop(EV_DEFAULT, &sigint_watcher); + ev_signal_stop(EV_DEFAULT, &sigterm_watcher); + + return 0; +} diff --git a/src/manager.h b/src/manager.h new file mode 100644 index 00000000..4a7517d3 --- /dev/null +++ b/src/manager.h @@ -0,0 +1,59 @@ +/* + * server.h - Define shadowsocks server's buffers and callbacks + * + * Copyright (C) 2013 - 2015, Max Lv + * + * This file is part of the shadowsocks-libev. + * + * shadowsocks-libev is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * shadowsocks-libev is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with shadowsocks-libev; see the file COPYING. If not, see + * . + */ + +#ifndef _MANAGER_H +#define _MANAGER_H + +#include +#include +#include + +#include "jconf.h" + +#include "common.h" + +struct manager_ctx { + ev_io io; + int fd; + int fast_open; + int verbose; + int mode; + char *password; + char *timeout; + char *method; + char *iface; + char *acl; + char *user; + char *manager_address; + char **hosts; + int host_num; + char **nameservers; + int nameserver_num; +}; + +struct server { + char port[8]; + char password[128]; + uint64_t traffic; +}; + +#endif // _MANAGER_H diff --git a/src/server.c b/src/server.c index cc3aaa5d..08152f1f 100644 --- a/src/server.c +++ b/src/server.c @@ -44,6 +44,7 @@ #include #include #include +#include #endif #include @@ -79,6 +80,10 @@ #define SSMAXCONN 1024 #endif +#ifndef UPDATE_INTERVAL +#define UPDATE_INTERVAL 30 +#endif + static void signal_cb(EV_P_ ev_signal *w, int revents); static void accept_cb(EV_P_ ev_io *w, int revents); static void server_send_cb(EV_P_ ev_io *w, int revents); @@ -111,8 +116,59 @@ static int nofile = 0; static int remote_conn = 0; static int server_conn = 0; +static char *server_port = NULL; +static char *manager_address = NULL; +uint64_t tx = 0; +uint64_t rx = 0; +ev_timer stat_update_watcher; + static struct cork_dllist connections; +static void stat_update_cb(EV_P_ ev_timer *watcher, int revents) +{ + struct sockaddr_un svaddr, claddr; + int sfd; + size_t msgLen; + char resp[BUF_SIZE]; + + if (verbose) { + LOGI("update traffic stat: tx: %"PRIu64" rx: %"PRIu64"", tx, rx); + } + + sfd = socket(AF_UNIX, SOCK_DGRAM, 0); + if (sfd == -1) { + ERROR("stat_socket"); + 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); + + unlink(claddr.sun_path); + + 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); + + 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; + } + + close(sfd); + unlink(claddr.sun_path); +} + static void free_connections(struct ev_loop *loop) { struct cork_dllist_item *curr; @@ -373,6 +429,8 @@ static void server_recv_cb(EV_P_ ev_io *w, int revents) } } + tx += r; + // handle incomplete header if (server->stage == 0) { r += server->buf_len; @@ -779,6 +837,8 @@ static void remote_recv_cb(EV_P_ ev_io *w, int revents) } } + rx += r; + server->buf = ss_encrypt(BUF_SIZE, server->buf, &r, server->e_ctx); if (server->buf == NULL) { @@ -965,8 +1025,7 @@ static struct server * new_server(int fd, struct listen_ctx *listener) ev_io_init(&server->recv_ctx->io, server_recv_cb, fd, EV_READ); ev_io_init(&server->send_ctx->io, server_send_cb, fd, EV_WRITE); ev_timer_init(&server->recv_ctx->watcher, server_timeout_cb, - min(MAX_CONNECT_TIMEOUT, - listener->timeout), listener->timeout); + min(MAX_CONNECT_TIMEOUT, listener->timeout), listener->timeout); server->recv_ctx->server = server; server->recv_ctx->connected = 0; server->send_ctx->server = server; @@ -1085,7 +1144,6 @@ int main(int argc, char **argv) int server_num = 0; const char *server_host[MAX_REMOTE_NUM]; - const char *server_port = NULL; char * nameservers[MAX_DNS_NUM + 1]; int nameserver_num = 0; @@ -1093,9 +1151,10 @@ int main(int argc, char **argv) int option_index = 0; static struct option long_options[] = { - { "fast-open", no_argument, 0, 0 }, - { "acl", required_argument, 0, 0 }, - { 0, 0, 0, 0 } + { "fast-open", no_argument, 0, 0 }, + { "acl", required_argument, 0, 0 }, + { "manager-address", required_argument, 0, 0 }, + { 0, 0, 0, 0 } }; opterr = 0; @@ -1111,6 +1170,8 @@ int main(int argc, char **argv) } else if (option_index == 1) { LOGI("initialize acl..."); acl = !init_acl(optarg); + } else if (option_index == 2) { + manager_address = optarg; } break; case 's': @@ -1327,6 +1388,11 @@ int main(int argc, char **argv) } + if (manager_address != NULL) { + ev_timer_init(&stat_update_watcher, stat_update_cb, UPDATE_INTERVAL, UPDATE_INTERVAL); + ev_timer_start(EV_DEFAULT, &stat_update_watcher); + } + if (mode != TCP_ONLY) { LOGI("UDP relay enabled"); } @@ -1350,6 +1416,10 @@ int main(int argc, char **argv) LOGI("closed gracefully"); } + if (manager_address != NULL) { + ev_timer_stop(EV_DEFAULT, &stat_update_watcher); + } + // Clean up for (int i = 0; i <= server_num; i++) { struct listen_ctx *listen_ctx = &listen_ctx_list[i]; diff --git a/src/udprelay.c b/src/udprelay.c index da0dcfa2..4b33e7f3 100644 --- a/src/udprelay.c +++ b/src/udprelay.c @@ -96,6 +96,10 @@ static struct remote_ctx * new_remote(int fd, struct server_ctx * server_ctx); extern int verbose; extern int vpn; +#ifdef UDPRELAY_REMOTE +extern uint64_t tx; +extern uint64_t rx; +#endif static int server_num = 0; static struct server_ctx *server_ctx_list[MAX_REMOTE_NUM] = { NULL }; @@ -688,6 +692,8 @@ static void remote_recv_cb(EV_P_ ev_io *w, int revents) #ifdef UDPRELAY_REMOTE + rx += buf_len; + char addr_header_buf[256]; char *addr_header = remote_ctx->addr_header; int addr_header_len = remote_ctx->addr_header_len; @@ -818,6 +824,9 @@ static void server_recv_cb(EV_P_ ev_io *w, int revents) } #ifdef UDPRELAY_REMOTE + + tx += buf_len; + buf = ss_decrypt_all(BUF_SIZE, buf, &buf_len, server_ctx->method); if (buf == NULL) { ERROR("[udp] server_ss_decrypt_all"); diff --git a/src/utils.c b/src/utils.c index 100e2def..b81f3b03 100644 --- a/src/utils.c +++ b/src/utils.c @@ -193,7 +193,7 @@ void usage() printf( " maintained by Max Lv and Linus Yang \n\n"); printf(" usage:\n\n"); - printf(" ss-[local|redir|server|tunnel]\n"); + printf(" ss-[local|redir|server|tunnel|manager]\n"); printf("\n"); printf( " -s host name or ip address of your remote server\n"); @@ -271,6 +271,16 @@ void usage() printf( " only available in local and server mode\n"); printf("\n"); + printf( + " [--manager_address ] UNIX domain socket address\n"); + printf( + " only available in server and manager mode\n"); + printf("\n"); + printf( + " [--executable ] path to the executable of ss-server\n"); + printf( + " only available in manager mode\n"); + printf("\n"); printf( " [-v] verbose mode\n"); printf("\n");