You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

839 lines
22 KiB

/*
* 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>
#include <limits.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>
#include <sys/socket.h>
#include <pwd.h>
#endif
#include <libcork/core.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 BUF_SIZE
#define BUF_SIZE 65535
#endif
int verbose = 0;
char *executable = "ss-server";
char working_dir[PATH_MAX];
static struct cork_hash_table *server_table;
#ifndef __MINGW32__
static 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 void build_config(char *prefix, struct server *server)
{
char path[PATH_MAX];
snprintf(path, PATH_MAX, "%s/.shadowsocks_%s.conf", prefix, server->port);
FILE *f = fopen(path, "w+");
if (f == NULL) {
if (verbose) {
LOGE("unable to open config file");
}
return;
}
fprintf(f, "{\n");
fprintf(f, "\"server_port\":\"%s\",\n", server->port);
fprintf(f, "\"password\":\"%s\",\n", server->password);
fprintf(f, "}\n");
fclose(f);
}
static char *construct_command_line(struct manager_ctx *manager, struct server *server)
{
static char cmd[BUF_SIZE];
int i;
build_config(working_dir, server);
memset(cmd, 0, BUF_SIZE);
snprintf(cmd, BUF_SIZE,
"%s -m %s --manager-address %s -f %s/.shadowsocks_%s.pid -c %s/.shadowsocks_%s.conf",
executable, manager->method, manager->manager_address,
working_dir, server->port, working_dir, 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->auth) {
int len = strlen(cmd);
snprintf(cmd + len, BUF_SIZE - len, " -A");
}
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((unsigned char)buf[pos]) && pos < len)
pos++;
if (pos == len) {
return NULL;
}
action = buf + pos;
while ((!isspace((unsigned char)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[PATH_MAX];
int pid;
snprintf(path, PATH_MAX, "%s/.shadowsocks_%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(working_dir, 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(working_dir, 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 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));
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)
{
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 auth = 0;
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:uUvA",
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;
case 'A':
auth = 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 (auth == 0) {
auth = conf->auth;
}
}
if (server_num == 0) {
server_host[server_num++] = "0.0.0.0";
}
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
}
if (auth) {
LOGI("onetime authentication enabled");
}
#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.auth = auth;
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);
}
struct passwd *pw = getpwuid(getuid());
const char *homedir = pw->pw_dir;
snprintf(working_dir, PATH_MAX, "%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) {
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);
}
}
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");
}
setnonblocking(sfd);
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);
}
} else {
sfd = create_server_socket(ip_addr.host, ip_addr.port);
if (sfd == -1) {
FATAL("socket");
}
}
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(working_dir, server->port);
}
#ifdef __MINGW32__
winsock_cleanup();
#endif
ev_signal_stop(EV_DEFAULT, &sigint_watcher);
ev_signal_stop(EV_DEFAULT, &sigterm_watcher);
return 0;
}