Browse Source

Merge pull request #371 from shadowsocks/manager-int

Add ss-manager for multi-user control and traffic statistics.
pull/477/head
Max Lv 9 years ago
parent
commit
07a8bb1e0d
13 changed files with 945 additions and 27 deletions
  1. 20
      configure
  2. 2
      configure.ac
  3. 3
      debian/changelog
  4. 2
      openwrt/Makefile
  5. 8
      src/Makefile.am
  6. 30
      src/Makefile.in
  7. 15
      src/jconf.c
  8. 10
      src/jconf.h
  9. 720
      src/manager.c
  10. 59
      src/manager.h
  11. 82
      src/server.c
  12. 9
      src/udprelay.c
  13. 12
      src/utils.c

20
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 <max.c.lv@gmail.com>.
#
@ -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\\"

2
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)

3
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 <max.c.lv@gmail.com> Thu, 30 Jul 2015 17:30:43 +0900

2
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

8
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

30
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$$||'`;\

15
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) {

10
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;

720
src/manager.c

@ -0,0 +1,720 @@
/*
* server.c - Provide shadowsocks service
*
* Copyright (C) 2013 - 2015, Max Lv <max.c.lv@gmail.com>
*
* 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
* <http://www.gnu.org/licenses/>.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <locale.h>
#include <signal.h>
#include <string.h>
#include <strings.h>
#include <time.h>
#include <unistd.h>
#include <getopt.h>
#include <math.h>
#include <ctype.h>
#ifndef __MINGW32__
#include <netdb.h>
#include <errno.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <pthread.h>
#include <sys/un.h>
#endif
#include <libcork/core.h>
#include <udns.h>
#ifdef __MINGW32__
#include "win32.h"
#endif
#if defined(HAVE_SYS_IOCTL_H) && defined(HAVE_NET_IF_H) && defined(__linux__)
#include <net/if.h>
#include <sys/ioctl.h>
#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;
}

59
src/manager.h

@ -0,0 +1,59 @@
/*
* server.h - Define shadowsocks server's buffers and callbacks
*
* Copyright (C) 2013 - 2015, Max Lv <max.c.lv@gmail.com>
*
* 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
* <http://www.gnu.org/licenses/>.
*/
#ifndef _MANAGER_H
#define _MANAGER_H
#include <ev.h>
#include <time.h>
#include <libcork/ds.h>
#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

82
src/server.c

@ -44,6 +44,7 @@
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <pthread.h>
#include <sys/un.h>
#endif
#include <libcork/core.h>
@ -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];

9
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");

12
src/utils.c

@ -193,7 +193,7 @@ void usage()
printf(
" maintained by Max Lv <max.c.lv@gmail.com> and Linus Yang <laokongzi@gmail.com>\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 <server_host> 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 <addr>] UNIX domain socket address\n");
printf(
" only available in server and manager mode\n");
printf("\n");
printf(
" [--executable <path>] path to the executable of ss-server\n");
printf(
" only available in manager mode\n");
printf("\n");
printf(
" [-v] verbose mode\n");
printf("\n");

Loading…
Cancel
Save