Files
shadowsocks-libev/src/server.c
T
Max Lv 1f259d0fa0 Make the event loop, UDP relay and programs compile as C++
Cast allocator results and void* callback data explicitly, cast string
literals stored in char * variables, replace compound literals in
ss_event.c with zeroing plus member assignment, and give ss-server's
listener array its MAX_REMOTE_NUM bound instead of a VLA.

Where a goto CLEAN_UP jumps over an initialised declaration (C++
rejects that), split the declaration from its initialisation in place.
The variables were already indeterminate at the label in C, and the
cleanup code does not read them. Checked for every MODULE_* variant of
udprelay.c, plus LIB_ONLY for local.c. Still valid C.
2026-10-14 22:51:16 +08:00

2669 lines
83 KiB
C

/*
* server.c - Provide shadowsocks service
*
* Copyright (C) 2013 - 2019, 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>
#ifndef __MINGW32__
#include <netdb.h>
#include <errno.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <pthread.h>
#include <sys/un.h>
#endif
#include "core.h"
#include <inttypes.h>
#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
#ifdef USE_NFTABLES
#include <ctype.h>
#include <linux/netfilter.h>
#include <linux/netfilter/nf_tables.h>
#include <libmnl/libmnl.h>
#include <libnftnl/set.h>
/* the datatypes enum is picked from libnftables/datatype.h
to avoid to depend libnftables */
enum datatypes {
TYPE_IPADDR = 7,
TYPE_IP6ADDR
};
#endif
#include "netutils.h"
#include "utils.h"
#include "acl.h"
#include "plugin.h"
#include "server.h"
#include "ss_windows.h"
#include "resolv.h"
#ifndef EAGAIN
#define EAGAIN EWOULDBLOCK
#endif
#ifndef EWOULDBLOCK
#define EWOULDBLOCK EAGAIN
#endif
#ifndef SSMAXCONN
#define SSMAXCONN 1024
#endif
#ifdef USE_NFCONNTRACK_TOS
#ifndef MARK_MAX_PACKET
#define MARK_MAX_PACKET 10
#endif
#ifndef MARK_MASK_PREFIX
#define MARK_MASK_PREFIX 0xDC00
#endif
#endif
static void signal_cb(SS_P_ ss_signal *w, int revents);
static void accept_cb(SS_P_ ss_io *w, int revents);
static void server_send_cb(SS_P_ ss_io *w, int revents);
static void server_recv_cb(SS_P_ ss_io *w, int revents);
static void remote_recv_cb(SS_P_ ss_io *w, int revents);
static void remote_send_cb(SS_P_ ss_io *w, int revents);
static void server_timeout_cb(SS_P_ ss_timer *watcher, int revents);
static remote_t *new_remote(int fd);
static server_t *new_server(int fd, listen_ctx_t *listener);
static remote_t *connect_to_remote(SS_P_ struct addrinfo *res,
server_t *server);
static void free_remote(remote_t *remote);
static void close_and_free_remote(SS_P_ remote_t *remote);
static void free_server(server_t *server);
static void close_and_free_server(SS_P_ server_t *server);
static void resolv_cb(struct sockaddr *addr, void *data);
static void resolv_free_cb(void *data);
int verbose = 0;
int reuse_port = 0;
int tcp_incoming_sndbuf = 0;
int tcp_incoming_rcvbuf = 0;
int tcp_outgoing_sndbuf = 0;
int tcp_outgoing_rcvbuf = 0;
int is_bind_local_addr = 0;
struct sockaddr_storage local_addr_v4;
struct sockaddr_storage local_addr_v6;
static crypto_t *crypto;
static int acl = 0;
static int mode = TCP_ONLY;
static int ipv6first = 0;
int fast_open = 0;
static int no_delay = 0;
static int ret_val = 0;
#ifdef HAVE_SETRLIMIT
static int nofile = 0;
#endif
static int remote_conn = 0;
static int server_conn = 0;
static char *plugin = NULL;
static char *remote_port = NULL;
static char *manager_addr = NULL;
uint64_t tx = 0;
uint64_t rx = 0;
#ifndef __MINGW32__
ss_timer stat_update_watcher;
#endif
static struct ss_signal sigint_watcher;
static struct ss_signal sigterm_watcher;
#ifndef __MINGW32__
static struct ss_signal sigchld_watcher;
#else
static struct plugin_watcher_t {
ss_io io;
SOCKET fd;
uint16_t port;
int valid;
} plugin_watcher;
#endif
static struct ss_list connections;
#ifndef __MINGW32__
static int setnonblocking(int fd);
static void stat_resolved(struct sockaddr *address, void *data)
{
if (!address) return;
int fd = ss_socket(address->sa_family, SOCK_DGRAM, 0);
if (fd == -1) return;
setnonblocking(fd);
const char *message = (const char *)data;
size_t length = strlen(message) + 1;
if (sendto(fd, message, length, 0, address, get_sockaddr_len(address)) != (ssize_t)length)
ERROR("stat_sendto");
ss_socket_close(fd);
}
static void
stat_update_cb(SS_P_ ss_timer *watcher, int revents)
{
struct sockaddr_un svaddr, claddr;
int sfd = -1;
size_t msgLen;
char resp[SOCKET_BUF_SIZE];
if (verbose) {
LOGI("update traffic stat: tx: %" PRIu64 " rx: %" PRIu64 "", tx, rx);
}
snprintf(resp, SOCKET_BUF_SIZE, "stat: {\"%s\":%" PRIu64 "}", remote_port, tx + rx);
msgLen = strlen(resp) + 1;
ss_addr_t ip_addr = { .host = NULL, .port = NULL };
parse_addr(manager_addr, &ip_addr);
if (ip_addr.host == NULL || ip_addr.port == NULL) {
ss_free(ip_addr.host);
ss_free(ip_addr.port);
sfd = ss_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", remote_port);
unlink(claddr.sun_path);
if (bind(sfd, (struct sockaddr *)&claddr, sizeof(struct sockaddr_un)) == -1) {
ERROR("stat_bind");
ss_socket_close(sfd);
return;
}
memset(&svaddr, 0, sizeof(struct sockaddr_un));
svaddr.sun_family = AF_UNIX;
if (strlen(manager_addr) >= sizeof(svaddr.sun_path)) {
LOGE("manager unix socket path is too long");
ss_socket_close(sfd);
unlink(claddr.sun_path);
return;
}
strncpy(svaddr.sun_path, manager_addr, sizeof(svaddr.sun_path) - 1);
if (sendto(sfd, resp, strlen(resp) + 1, 0, (struct sockaddr *)&svaddr,
sizeof(struct sockaddr_un)) != msgLen) {
ERROR("stat_sendto");
ss_socket_close(sfd);
return;
}
unlink(claddr.sun_path);
} else {
uint16_t port;
if (parse_numeric_port(ip_addr.port, &port) == 0) {
char *message = strdup(resp);
if (!message) FATAL("out of memory");
resolv_start(ip_addr.host, htons(port), stat_resolved, NULL, message);
}
ss_free(ip_addr.host);
ss_free(ip_addr.port);
return;
}
ss_socket_close(sfd);
}
#endif
static void
free_connections(struct ss_loop *loop)
{
struct ss_list_item *curr, *next;
ss_list_foreach_void(&connections, curr, next) {
server_t *server = ss_container_of(curr, server_t, entries);
remote_t *remote = server->remote;
close_and_free_server(loop, server);
close_and_free_remote(loop, remote);
}
}
static char *
get_peer_name(int fd)
{
static char peer_name[INET6_ADDRSTRLEN] = { 0 };
struct sockaddr_storage addr;
socklen_t len = sizeof(struct sockaddr_storage);
memset(&addr, 0, len);
memset(peer_name, 0, INET6_ADDRSTRLEN);
int err = getpeername(fd, (struct sockaddr *)&addr, &len);
if (err == 0) {
if (addr.ss_family == AF_INET) {
struct sockaddr_in *s = (struct sockaddr_in *)&addr;
ss_inet_ntop(AF_INET, &s->sin_addr, peer_name, INET_ADDRSTRLEN);
} else if (addr.ss_family == AF_INET6) {
struct sockaddr_in6 *s = (struct sockaddr_in6 *)&addr;
ss_inet_ntop(AF_INET6, &s->sin6_addr, peer_name, INET6_ADDRSTRLEN);
}
} else {
return NULL;
}
return peer_name;
}
static void
stop_server(SS_P_ server_t *server)
{
server->stage = STAGE_STOP;
}
#ifdef USE_NFTABLES
struct nftbl_set_info {
uint32_t family;
char *table;
char *name;
uint32_t type;
}* nftbl_badip_sets[16];
static struct nftnl_set *
nftbl_build_set(const char* table, const char* name, void* addr, size_t len)
{
struct nftnl_set *set = nftnl_set_alloc();
if (set == NULL) return NULL;
nftnl_set_set_str(set, NFTNL_SET_TABLE, table);
nftnl_set_set_str(set, NFTNL_SET_NAME, name);
struct nftnl_set_elem *elem = nftnl_set_elem_alloc();
if (elem == NULL) {
nftnl_set_free(set);
return NULL;
}
nftnl_set_elem_set(elem, NFTNL_SET_ELEM_KEY, addr, len);
nftnl_set_elem_add(set, elem);
return set;
}
static uint32_t
nftbl_build_nlmsg(void* buf, size_t *len, uint32_t family,
struct nftnl_set *set)
{
uint32_t seq = time(NULL);
struct nlmsghdr *nlh;
struct mnl_nlmsg_batch *batch = mnl_nlmsg_batch_start(buf, *len);
nftnl_batch_begin(mnl_nlmsg_batch_current(batch), seq);
mnl_nlmsg_batch_next(batch);
nlh = nftnl_nlmsg_build_hdr(mnl_nlmsg_batch_current(batch),
NFT_MSG_NEWSETELEM, family,
NLM_F_CREATE | NLM_F_EXCL | NLM_F_ACK,
++seq);
nftnl_set_elems_nlmsg_build_payload(nlh, set);
mnl_nlmsg_batch_next(batch);
nftnl_batch_end(mnl_nlmsg_batch_current(batch), seq + 1);
mnl_nlmsg_batch_next(batch);
*len = mnl_nlmsg_batch_size(batch);
mnl_nlmsg_batch_stop(batch);
return seq;
}
static int
nftbl_send_request(void *request, size_t len, uint32_t seq,
mnl_cb_t cb, void *data)
{
struct mnl_socket *nl = mnl_socket_open(NETLINK_NETFILTER);
if (nl == NULL) return -1;
int ret = -1;
uint8_t buf[MNL_SOCKET_BUFFER_SIZE];
if (mnl_socket_bind(nl, 0, MNL_SOCKET_AUTOPID) == 0 &&
mnl_socket_sendto(nl, request, len) >= 0) {
uint32_t portid = mnl_socket_get_portid(nl);
while ((ret = mnl_socket_recvfrom(nl, buf, sizeof(buf))) > 0) {
ret = mnl_cb_run(buf, ret, seq, portid, cb, data);
if (ret != MNL_CB_OK)
break;
}
mnl_socket_close(nl);
}
return ret;
}
static void
nftbl_report_addr(const struct sockaddr* addr)
{
uint32_t type;
void* data;
size_t size;
if (addr->sa_family == AF_INET) {
type = TYPE_IPADDR;
data = &((struct sockaddr_in*)addr)->sin_addr;
size = sizeof(struct in_addr);
} else if (addr->sa_family == AF_INET6) {
type = TYPE_IP6ADDR;
data = &((struct sockaddr_in6*)addr)->sin6_addr;
size = sizeof(struct in6_addr);
} else {
return;
}
char buf[MNL_SOCKET_BUFFER_SIZE];
for (int i = 0; nftbl_badip_sets[i]; ++i) {
struct nftbl_set_info* si = nftbl_badip_sets[i];
struct nftnl_set *set;
if (si->type == type &&
(set = nftbl_build_set(si->table, si->name, data, size))) {
size_t len = sizeof(buf);
uint32_t seq = nftbl_build_nlmsg(buf, &len, si->family, set);
nftnl_set_free(set);
if (nftbl_send_request(buf, len, seq, NULL, NULL) < 0 &&
errno != EEXIST)
ERROR("nftbl_report_addr");
}
}
}
static int
nftbl_check_cb(const struct nlmsghdr *nlh, void *data)
{
struct nftnl_set *set = (struct nftnl_set*)data;
if (nftnl_set_nlmsg_parse(nlh, set) < 0)
return MNL_CB_ERROR;
uint32_t type = nftnl_set_get_u32(set, NFTNL_SET_KEY_TYPE);
if (type != TYPE_IPADDR && type != TYPE_IP6ADDR)
return MNL_CB_OK;
uint32_t len;
const char *name = nftnl_set_get_data(set, NFTNL_SET_NAME, &len);
for (int i = 0; nftbl_badip_sets[i]; ++i) {
struct nftbl_set_info* si = nftbl_badip_sets[i];
if (!memcmp(name, si->name, len)) {
name = nftnl_set_get_data(set, NFTNL_SET_TABLE, &len);
if (!si->table) {
size_t l = strlen(si->name) + 1;
si = realloc(si, sizeof(*si) + l + len);
si->name = (char*)(si + 1);
si->table = memcpy(si->name + l, name, len);
nftbl_badip_sets[i] = si;
} else if (memcmp(name, si->table, len)) {
continue; /* table name not match */
}
si->family = nftnl_set_get_u32(set, NFTNL_SET_FAMILY);
si->type = type;
}
}
return MNL_CB_OK;
}
static int
nftbl_check(void)
{
struct nftnl_set *set = nftnl_set_alloc();
if (!set) return -1;
int ret;
char buf[MNL_SOCKET_BUFFER_SIZE];
uint32_t seq = time(NULL);
struct nlmsghdr *nlh;
nlh = nftnl_set_nlmsg_build_hdr(buf, NFT_MSG_GETSET, NFPROTO_UNSPEC,
NLM_F_DUMP|NLM_F_ACK, seq);
nftnl_set_nlmsg_build_payload(nlh, set);
ret = nftbl_send_request(nlh, nlh->nlmsg_len, seq, nftbl_check_cb, set);
nftnl_set_free(set);
if (ret < 0) return ret;
for (int i = 0; nftbl_badip_sets[i]; ++i) {
struct nftbl_set_info* si = nftbl_badip_sets[i];
if (si->family == NFPROTO_UNSPEC) {
if (si->table)
LOGE("set '%s' not found in table '%s'", si->name, si->table);
else
LOGE("set '%s' not found", si->name);
ret = -1;
}
}
if (ret < 0)
FATAL("Check nftables configuration.");
return ret;
}
static int
nftbl_init(const char* set_str)
{
struct nftbl_set_info* si;
const char *p0 = set_str, *p = p0, *d = NULL;
int i = 0;
do {
if (*p == ':') {
d = p;
} else if (*p == ',' || *p == '\0') {
size_t l = p - p0 + 1;
si = malloc(sizeof(*si) + l);
memset(si, 0, sizeof(*si));
si->name = memcpy(si + 1, p0, l);
si->name[l - 1] = '\0';
if (d) {
si->table = si->name;
si->name = si->table + (d - p0);
*(si->name++) = '\0';
d = NULL;
}
nftbl_badip_sets[i++] = si;
if (i == sizeof(nftbl_badip_sets) / sizeof(*si) - 1)
break;
while (*p && isspace(*(++p)));
p0 = p;
}
} while (*(p++));
return nftbl_check();
}
#endif
static void
report_addr(int fd, const char *info)
{
char *peer_name;
peer_name = get_peer_name(fd);
if (peer_name != NULL) {
LOGE("failed to handshake with %s: %s", peer_name, info);
}
#ifdef USE_NFTABLES
struct sockaddr_in6 addr;
socklen_t len = sizeof(struct sockaddr_in6);
if (!getpeername(fd, (struct sockaddr *)&addr, &len))
nftbl_report_addr((struct sockaddr *)&addr);
#endif
}
int
setfastopen(int fd)
{
int s = 0;
#ifdef TCP_FASTOPEN
if (fast_open) {
#if defined(__APPLE__) || defined(__MINGW32__)
int opt = 1;
#else
int opt = 5;
#endif
s = ss_setsockopt(fd, IPPROTO_TCP, TCP_FASTOPEN, &opt, sizeof(opt));
if (s == -1) {
if (ss_socket_error() == EPROTONOSUPPORT || ss_socket_error() == ENOPROTOOPT) {
LOGE("fast open is not supported on this platform");
fast_open = 0;
} else {
ERROR("setsockopt");
}
}
}
#endif
return s;
}
#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
int
create_and_bind(const char *host, const char *port, int mptcp)
{
struct addrinfo hints;
struct addrinfo *result, *rp, *ipv4v6bindall;
int s, listen_sock = -1;
memset(&hints, 0, sizeof(struct addrinfo));
hints.ai_family = AF_UNSPEC; /* Return IPv4 and IPv6 choices */
hints.ai_socktype = SOCK_STREAM; /* We want a TCP socket */
hints.ai_flags = AI_PASSIVE | AI_ADDRCONFIG; /* For wildcard IP address */
hints.ai_protocol = IPPROTO_TCP;
result = NULL;
s = getaddrinfo(host, port, &hints, &result);
if (s != 0) {
LOGE("failed to resolve server name %s", host);
return -1;
}
if (result == NULL) {
LOGE("Cannot bind");
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) {
int protocol = rp->ai_protocol;
if (mptcp < 0) {
protocol = IPPROTO_MPTCP; // Enable upstream MPTCP
}
listen_sock = ss_socket(rp->ai_family, rp->ai_socktype, protocol);
if (listen_sock == -1) {
continue;
}
if (rp->ai_family == AF_INET6) {
int opt = host ? 1 : 0;
ss_setsockopt(listen_sock, IPPROTO_IPV6, IPV6_V6ONLY, &opt, sizeof(opt));
}
int opt = 1;
ss_setsockopt(listen_sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
#ifdef SO_NOSIGPIPE
ss_setsockopt(listen_sock, SOL_SOCKET, SO_NOSIGPIPE, &opt, sizeof(opt));
#endif
if (reuse_port) {
int err = set_reuseport(listen_sock);
if (err == 0) {
LOGI("tcp port reuse enabled");
}
}
if (tcp_incoming_sndbuf > 0) {
ss_setsockopt(listen_sock, SOL_SOCKET, SO_SNDBUF, &tcp_incoming_sndbuf, sizeof(int));
}
if (tcp_incoming_rcvbuf > 0) {
ss_setsockopt(listen_sock, SOL_SOCKET, SO_RCVBUF, &tcp_incoming_rcvbuf, sizeof(int));
}
// Enable out-of-tree mptcp
if (mptcp == 1) {
int i = 0;
while ((mptcp = mptcp_enabled_values[i]) > 0) {
int err = ss_setsockopt(listen_sock, IPPROTO_TCP, mptcp, &opt, sizeof(opt));
if (err != -1) {
break;
}
i++;
}
if (mptcp == 0) {
ERROR("failed to enable out-of-tree multipath TCP");
}
}
s = bind(listen_sock, rp->ai_addr, rp->ai_addrlen);
if (s == 0) {
/* We managed to bind successfully! */
break;
} else {
ERROR("bind");
FATAL("failed to bind address");
}
ss_socket_close(listen_sock);
listen_sock = -1;
}
freeaddrinfo(result);
return listen_sock;
}
static remote_t *
connect_to_remote(SS_P_ struct addrinfo *res,
server_t *server)
{
int sockfd;
#ifdef SET_INTERFACE
const char *iface = server->listen_ctx->iface;
#endif
if (acl) {
char ipstr[INET6_ADDRSTRLEN];
memset(ipstr, 0, INET6_ADDRSTRLEN);
// NOLINTNEXTLINE(clang-analyzer-core.NullDereference): callers always set res->ai_addr
if (res->ai_addr->sa_family == AF_INET) {
struct sockaddr_in s;
memcpy(&s, res->ai_addr, sizeof(struct sockaddr_in));
ss_inet_ntop(AF_INET, &s.sin_addr, ipstr, INET_ADDRSTRLEN);
} else if (res->ai_addr->sa_family == AF_INET6) {
struct sockaddr_in6 s;
memcpy(&s, res->ai_addr, sizeof(struct sockaddr_in6));
ss_inet_ntop(AF_INET6, &s.sin6_addr, ipstr, INET6_ADDRSTRLEN);
}
if (outbound_block_match_host(ipstr) == 1) {
if (verbose)
LOGI("outbound blocked %s", ipstr);
return NULL;
}
}
// initialize remote socks
sockfd = ss_socket(res->ai_family, res->ai_socktype, res->ai_protocol);
if (sockfd == -1) {
ERROR("socket");
ss_socket_close(sockfd);
return NULL;
}
int opt = 1;
ss_setsockopt(sockfd, SOL_TCP, TCP_NODELAY, &opt, sizeof(opt));
#ifdef SO_NOSIGPIPE
ss_setsockopt(sockfd, SOL_SOCKET, SO_NOSIGPIPE, &opt, sizeof(opt));
#endif
ss_setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
if (tcp_outgoing_sndbuf > 0) {
ss_setsockopt(sockfd, SOL_SOCKET, SO_SNDBUF, &tcp_outgoing_sndbuf, sizeof(int));
}
if (tcp_outgoing_rcvbuf > 0) {
ss_setsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, &tcp_outgoing_rcvbuf, sizeof(int));
}
// setup remote socks
if (setnonblocking(sockfd) == -1)
ERROR("setnonblocking");
if (is_bind_local_addr) {
struct sockaddr_storage *local_addr =
res->ai_family == AF_INET ? &local_addr_v4 : &local_addr_v6;
if (res->ai_family == local_addr->ss_family) {
if (bind_to_addr(local_addr, sockfd) == -1) {
ERROR("bind_to_addr");
FATAL("cannot bind socket");
return NULL;
}
}
}
#ifdef SET_INTERFACE
if (iface) {
if (setinterface(sockfd, iface) == -1) {
ERROR("setinterface");
ss_socket_close(sockfd);
return NULL;
}
}
#endif
remote_t *remote = new_remote(sockfd);
if (fast_open) {
#if defined(MSG_FASTOPEN) && !defined(TCP_FASTOPEN_CONNECT)
int s = -1;
s = sendto(sockfd, server->buf->data + server->buf->idx, server->buf->len,
MSG_FASTOPEN, res->ai_addr, res->ai_addrlen);
#elif defined(TCP_FASTOPEN_WINSOCK)
DWORD s = -1;
DWORD err = 0;
do {
int optval = 1;
// Set fast open option
if (ss_setsockopt(sockfd, IPPROTO_TCP, TCP_FASTOPEN,
&optval, sizeof(optval)) != 0) {
ERROR("setsockopt");
break;
}
// Load ConnectEx function
LPFN_CONNECTEX ConnectEx = winsock_getconnectex();
if (ConnectEx == NULL) {
LOGE("Cannot load ConnectEx() function");
err = WSAENOPROTOOPT;
break;
}
// ConnectEx requires a bound socket
if (winsock_dummybind(sockfd, res->ai_addr) != 0) {
ERROR("bind");
break;
}
// Call ConnectEx to send data
memset(&remote->olap, 0, sizeof(remote->olap));
remote->connect_ex_done = 0;
if (ConnectEx(sockfd, res->ai_addr, res->ai_addrlen,
server->buf->data + server->buf->idx,
server->buf->len, &s, &remote->olap)) {
remote->connect_ex_done = 1;
break;
}
// XXX: ConnectEx pending, check later in remote_send
if (WSAGetLastError() == ERROR_IO_PENDING) {
err = CONNECT_IN_PROGRESS;
break;
}
ERROR("ConnectEx");
} while (0);
// Set error number
if (err) {
SetLastError(err);
}
#else
int s = -1;
#if defined(TCP_FASTOPEN_CONNECT)
int optval = 1;
if (ss_setsockopt(sockfd, IPPROTO_TCP, TCP_FASTOPEN_CONNECT,
(void *)&optval, sizeof(optval)) < 0)
FATAL("failed to set TCP_FASTOPEN_CONNECT");
s = connect(sockfd, res->ai_addr, res->ai_addrlen);
#elif defined(CONNECT_DATA_IDEMPOTENT)
struct sockaddr_in sa;
memcpy(&sa, res->ai_addr, sizeof(struct sockaddr_in));
sa.sin_len = sizeof(struct sockaddr_in);
sa_endpoints_t endpoints;
memset((char *)&endpoints, 0, sizeof(endpoints));
endpoints.sae_dstaddr = (struct sockaddr *)&sa;
endpoints.sae_dstaddrlen = res->ai_addrlen;
s = connectx(sockfd, &endpoints, SAE_ASSOCID_ANY, CONNECT_DATA_IDEMPOTENT,
NULL, 0, NULL, NULL);
#else
FATAL("fast open is not enabled in this build");
#endif
if (s == 0)
s = send(sockfd, server->buf->data + server->buf->idx, server->buf->len, 0);
#endif
if (s == -1) {
if (ss_socket_error() == CONNECT_IN_PROGRESS) {
// The remote server doesn't support tfo or it's the first connection to the server.
// It will automatically fall back to conventional TCP.
} else if (ss_socket_error() == EOPNOTSUPP || ss_socket_error() == EPROTONOSUPPORT ||
ss_socket_error() == ENOPROTOOPT) {
// Disable fast open as it's not supported
fast_open = 0;
LOGE("fast open is not supported on this platform");
} else {
ERROR("fast_open_connect");
}
} else {
server->buf->idx += s;
server->buf->len -= s;
}
}
if (!fast_open) {
int r = connect(sockfd, res->ai_addr, res->ai_addrlen);
if (r == -1 && ss_socket_error() != CONNECT_IN_PROGRESS) {
ERROR("connect");
close_and_free_remote(SS_A_ remote);
return NULL;
}
}
return remote;
}
#ifdef USE_NFCONNTRACK_TOS
int
setMarkDscpCallback(enum nf_conntrack_msg_type type, struct nf_conntrack *ct, void *data)
{
server_t *server = (server_t *)data;
struct dscptracker *tracker = server->tracker;
tracker->mark = nfct_get_attr_u32(ct, ATTR_MARK);
if ((tracker->mark & 0xff00) == MARK_MASK_PREFIX) {
// Extract DSCP value from mark value
tracker->dscp = tracker->mark & 0x00ff;
int tos = (tracker->dscp) << 2;
if (ss_setsockopt(server->fd, IPPROTO_IP, IP_TOS, &tos, sizeof(tos)) != 0) {
ERROR("iptable setsockopt IP_TOS");
}
}
return NFCT_CB_CONTINUE;
}
void
conntrackQuery(server_t *server)
{
struct dscptracker *tracker = server->tracker;
if (tracker && tracker->ct) {
// Trying query mark from nf conntrack
struct nfct_handle *h = nfct_open(CONNTRACK, 0);
if (h) {
nfct_callback_register(h, NFCT_T_ALL, setMarkDscpCallback, (void *)server);
int x = nfct_query(h, NFCT_Q_GET, tracker->ct);
if (x == -1) {
LOGE("QOS: Failed to retrieve connection mark %s", strerror(ss_socket_error()));
}
nfct_close(h);
} else {
LOGE("QOS: Failed to open conntrack handle for upstream netfilter mark retrieval.");
}
}
}
void
setTosFromConnmark(remote_t *remote, server_t *server)
{
if (server->tracker && server->tracker->ct) {
if (server->tracker->mark == 0 && server->tracker->packet_count < MARK_MAX_PACKET) {
server->tracker->packet_count++;
conntrackQuery(server);
}
} else {
socklen_t len;
struct sockaddr_storage sin;
len = sizeof(sin);
if (getpeername(remote->fd, (struct sockaddr *)&sin, &len) == 0) {
struct sockaddr_storage from_addr;
len = sizeof from_addr;
if (getsockname(remote->fd, (struct sockaddr *)&from_addr, &len) == 0) {
if ((server->tracker = (struct dscptracker *)ss_malloc(sizeof(struct dscptracker)))) {
if ((server->tracker->ct = nfct_new())) {
// Build conntrack query SELECT
if (from_addr.ss_family == AF_INET) {
struct sockaddr_in *src = (struct sockaddr_in *)&from_addr;
struct sockaddr_in *dst = (struct sockaddr_in *)&sin;
nfct_set_attr_u8(server->tracker->ct, ATTR_L3PROTO, AF_INET);
nfct_set_attr_u32(server->tracker->ct, ATTR_IPV4_DST, dst->sin_addr.s_addr);
nfct_set_attr_u32(server->tracker->ct, ATTR_IPV4_SRC, src->sin_addr.s_addr);
nfct_set_attr_u16(server->tracker->ct, ATTR_PORT_DST, dst->sin_port);
nfct_set_attr_u16(server->tracker->ct, ATTR_PORT_SRC, src->sin_port);
} else if (from_addr.ss_family == AF_INET6) {
struct sockaddr_in6 *src = (struct sockaddr_in6 *)&from_addr;
struct sockaddr_in6 *dst = (struct sockaddr_in6 *)&sin;
nfct_set_attr_u8(server->tracker->ct, ATTR_L3PROTO, AF_INET6);
nfct_set_attr(server->tracker->ct, ATTR_IPV6_DST, dst->sin6_addr.s6_addr);
nfct_set_attr(server->tracker->ct, ATTR_IPV6_SRC, src->sin6_addr.s6_addr);
nfct_set_attr_u16(server->tracker->ct, ATTR_PORT_DST, dst->sin6_port);
nfct_set_attr_u16(server->tracker->ct, ATTR_PORT_SRC, src->sin6_port);
}
nfct_set_attr_u8(server->tracker->ct, ATTR_L4PROTO, IPPROTO_TCP);
conntrackQuery(server);
} else {
LOGE("Failed to allocate new conntrack for upstream netfilter mark retrieval.");
server->tracker->ct = NULL;
}
}
}
}
}
}
#endif
static void
server_recv_cb(SS_P_ ss_io *w, int revents)
{
server_ctx_t *server_recv_ctx = (server_ctx_t *)w;
server_t *server = server_recv_ctx->server;
remote_t *remote = NULL;
buffer_t *buf = server->buf;
if (server->stage == STAGE_STREAM) {
remote = server->remote;
buf = remote->buf;
// Only timer the watcher if a valid connection is established
ss_timer_again(SS_A_ & server->recv_ctx->watcher);
}
ssize_t r = recv(server->fd, buf->data, SOCKET_BUF_SIZE, 0);
if (r == 0) {
// connection closed
close_and_free_remote(SS_A_ remote);
close_and_free_server(SS_A_ server);
return;
} else if (r == -1) {
if (ss_socket_error() == EAGAIN || ss_socket_error() == EWOULDBLOCK) {
// no data
// continue to wait for recv
return;
} else {
ERROR("server recv");
close_and_free_remote(SS_A_ remote);
close_and_free_server(SS_A_ server);
return;
}
}
// Ignore any new packet if the server is stopped
if (server->stage == STAGE_STOP) {
return;
}
tx += r;
buf->len = r;
int err = crypto->decrypt(buf, server->d_ctx, SOCKET_BUF_SIZE);
if (err == CRYPTO_ERROR) {
report_addr(server->fd, "authentication error");
stop_server(SS_A_ server);
return;
} else if (err == CRYPTO_NEED_MORE) {
if (server->stage != STAGE_STREAM) {
server->frag++;
}
return;
}
// handshake and transmit data
if (server->stage == STAGE_STREAM) {
int s = send(remote->fd, remote->buf->data, remote->buf->len, 0);
if (s == -1) {
if (ss_socket_error() == EAGAIN || ss_socket_error() == EWOULDBLOCK) {
// no data, wait for send
remote->buf->idx = 0;
ss_io_stop(SS_A_ & server_recv_ctx->io);
ss_io_start(SS_A_ & remote->send_ctx->io);
} else {
ERROR("server_recv_send");
close_and_free_remote(SS_A_ remote);
close_and_free_server(SS_A_ server);
}
} else if (s < remote->buf->len) {
remote->buf->len -= s;
remote->buf->idx = s;
ss_io_stop(SS_A_ & server_recv_ctx->io);
ss_io_start(SS_A_ & remote->send_ctx->io);
}
return;
} else if (server->stage == STAGE_INIT) {
/*
* Shadowsocks TCP Relay Header:
*
* +------+----------+----------+
* | ATYP | DST.ADDR | DST.PORT |
* +------+----------+----------+
* | 1 | Variable | 2 |
* +------+----------+----------+
*
*/
int offset = 0;
int need_query = 0;
if (server->buf->len < 1) {
report_addr(server->fd, "missing address type");
stop_server(SS_A_ server);
return;
}
char atyp = server->buf->data[offset++];
char host[256] = { 0 };
uint16_t port = 0;
struct addrinfo info;
struct sockaddr_storage storage;
memset(&info, 0, sizeof(struct addrinfo));
memset(&storage, 0, sizeof(struct sockaddr_storage));
// get remote addr and port
if ((atyp & ADDRTYPE_MASK) == 1) {
// IP V4
struct sockaddr_in *addr = (struct sockaddr_in *)&storage;
size_t in_addr_len = sizeof(struct in_addr);
addr->sin_family = AF_INET;
if (server->buf->len >= in_addr_len + 3) {
memcpy(&addr->sin_addr, server->buf->data + offset, in_addr_len);
ss_inet_ntop(AF_INET, (const void *)(server->buf->data + offset),
host, INET_ADDRSTRLEN);
offset += in_addr_len;
} else {
report_addr(server->fd, "invalid length for ipv4 address");
stop_server(SS_A_ server);
return;
}
memcpy(&addr->sin_port, server->buf->data + offset, sizeof(uint16_t));
info.ai_family = AF_INET;
info.ai_socktype = SOCK_STREAM;
info.ai_protocol = IPPROTO_TCP;
info.ai_addrlen = sizeof(struct sockaddr_in);
info.ai_addr = (struct sockaddr *)addr;
} else if ((atyp & ADDRTYPE_MASK) == 3) {
// Domain name
if (server->buf->len < offset + 1) {
report_addr(server->fd, "missing host name length");
stop_server(SS_A_ server);
return;
}
uint8_t name_len = *(uint8_t *)(server->buf->data + offset);
if (name_len + 4 <= server->buf->len) {
memcpy(host, server->buf->data + offset + 1, name_len);
host[name_len] = '\0';
offset += name_len + 1;
} else {
report_addr(server->fd, "invalid host name length");
stop_server(SS_A_ server);
return;
}
if (acl && outbound_block_match_host(host) == 1) {
if (verbose)
LOGI("outbound blocked %s", host);
close_and_free_server(SS_A_ server);
return;
}
struct ss_ip ip;
if (ss_ip_init(&ip, host) != -1) {
info.ai_socktype = SOCK_STREAM;
info.ai_protocol = IPPROTO_TCP;
if (ip.version == 4) {
struct sockaddr_in *addr = (struct sockaddr_in *)&storage;
inet_pton(AF_INET, host, &(addr->sin_addr));
memcpy(&addr->sin_port, server->buf->data + offset, sizeof(uint16_t));
addr->sin_family = AF_INET;
info.ai_family = AF_INET;
info.ai_addrlen = sizeof(struct sockaddr_in);
info.ai_addr = (struct sockaddr *)addr;
} else if (ip.version == 6) {
struct sockaddr_in6 *addr = (struct sockaddr_in6 *)&storage;
inet_pton(AF_INET6, host, &(addr->sin6_addr));
memcpy(&addr->sin6_port, server->buf->data + offset, sizeof(uint16_t));
addr->sin6_family = AF_INET6;
info.ai_family = AF_INET6;
info.ai_addrlen = sizeof(struct sockaddr_in6);
info.ai_addr = (struct sockaddr *)addr;
}
} else {
if (!validate_hostname(host, name_len)) {
report_addr(server->fd, "invalid host name");
stop_server(SS_A_ server);
return;
}
need_query = 1;
}
} else if ((atyp & ADDRTYPE_MASK) == 4) {
// IP V6
struct sockaddr_in6 *addr = (struct sockaddr_in6 *)&storage;
size_t in6_addr_len = sizeof(struct in6_addr);
addr->sin6_family = AF_INET6;
if (server->buf->len >= in6_addr_len + 3) {
memcpy(&addr->sin6_addr, server->buf->data + offset, in6_addr_len);
ss_inet_ntop(AF_INET6, (const void *)(server->buf->data + offset),
host, INET6_ADDRSTRLEN);
offset += in6_addr_len;
} else {
LOGE("invalid header with addr type %d", atyp);
report_addr(server->fd, "invalid length for ipv6 address");
stop_server(SS_A_ server);
return;
}
memcpy(&addr->sin6_port, server->buf->data + offset, sizeof(uint16_t));
info.ai_family = AF_INET6;
info.ai_socktype = SOCK_STREAM;
info.ai_protocol = IPPROTO_TCP;
info.ai_addrlen = sizeof(struct sockaddr_in6);
info.ai_addr = (struct sockaddr *)addr;
}
if (offset == 1) {
report_addr(server->fd, "invalid address type");
stop_server(SS_A_ server);
return;
}
port = ntohs(load16_be(server->buf->data + offset));
offset += 2;
if (server->buf->len < offset) {
report_addr(server->fd, "invalid request length");
stop_server(SS_A_ server);
return;
} else {
server->buf->len -= offset;
server->buf->idx = offset;
}
if (verbose) {
if ((atyp & ADDRTYPE_MASK) == 4)
LOGI("[%s] connect to [%s]:%d", remote_port, host, ntohs(port));
else
LOGI("[%s] connect to %s:%d", remote_port, host, ntohs(port));
}
if (!need_query) {
remote_t *remote = connect_to_remote(SS_A_ & info, server);
if (remote == NULL) {
LOGE("connect error");
close_and_free_server(SS_A_ server);
return;
} else {
server->remote = remote;
remote->server = server;
// XXX: should handle buffer carefully
if (server->buf->len > 0) {
brealloc(remote->buf, server->buf->len, SOCKET_BUF_SIZE);
memcpy(remote->buf->data, server->buf->data + server->buf->idx,
server->buf->len);
remote->buf->len = server->buf->len;
remote->buf->idx = 0;
server->buf->len = 0;
server->buf->idx = 0;
}
// waiting on remote connected event
ss_io_stop(SS_A_ & server_recv_ctx->io);
ss_io_start(SS_A_ & remote->send_ctx->io);
}
} else {
ss_io_stop(SS_A_ & server_recv_ctx->io);
query_t *query = (query_t *)ss_malloc(sizeof(query_t));
memset(query, 0, sizeof(query_t));
query->server = server;
server->query = query;
snprintf(query->hostname, MAX_HOSTNAME_LEN, "%s", host);
server->stage = STAGE_RESOLVE;
resolv_start(host, port, resolv_cb, resolv_free_cb, query);
}
return;
}
// should not reach here
FATAL("server context error");
}
static void
server_send_cb(SS_P_ ss_io *w, int revents)
{
server_ctx_t *server_send_ctx = (server_ctx_t *)w;
server_t *server = server_send_ctx->server;
remote_t *remote = server->remote;
if (remote == NULL) {
LOGE("invalid server");
close_and_free_server(SS_A_ server);
return;
}
if (server->buf->len == 0) {
// close and free
close_and_free_remote(SS_A_ remote);
close_and_free_server(SS_A_ server);
return;
} else {
// has data to send
ssize_t s = send(server->fd, server->buf->data + server->buf->idx,
server->buf->len, 0);
if (s == -1) {
if (ss_socket_error() != EAGAIN && ss_socket_error() != EWOULDBLOCK) {
ERROR("server_send_send");
close_and_free_remote(SS_A_ remote);
close_and_free_server(SS_A_ server);
}
return;
} else if (s < server->buf->len) {
// partly sent, move memory, wait for the next time to send
server->buf->len -= s;
server->buf->idx += s;
return;
} else {
// all sent out, wait for reading
server->buf->len = 0;
server->buf->idx = 0;
ss_io_stop(SS_A_ & server_send_ctx->io);
if (remote != NULL) {
ss_io_start(SS_A_ & remote->recv_ctx->io);
return;
} else {
LOGE("invalid remote");
close_and_free_remote(SS_A_ remote);
close_and_free_server(SS_A_ server);
return;
}
}
}
}
static void
server_timeout_cb(SS_P_ ss_timer *watcher, int revents)
{
server_ctx_t *server_ctx
= ss_container_of(watcher, server_ctx_t, watcher);
server_t *server = server_ctx->server;
remote_t *remote = server->remote;
if (verbose) {
LOGI("TCP connection timeout");
}
close_and_free_remote(SS_A_ remote);
close_and_free_server(SS_A_ server);
}
static void
resolv_free_cb(void *data)
{
query_t *query = (query_t *)data;
if (query != NULL) {
if (query->server != NULL)
query->server->query = NULL;
ss_free(query);
}
}
static void
resolv_cb(struct sockaddr *addr, void *data)
{
query_t *query = (query_t *)data;
server_t *server = query->server;
if (server == NULL)
return;
struct ss_loop *loop = server->listen_ctx->loop;
if (addr == NULL) {
LOGE("unable to resolve %s", query->hostname);
close_and_free_server(SS_A_ server);
} else {
if (verbose) {
LOGI("successfully resolved %s", query->hostname);
}
struct addrinfo info;
memset(&info, 0, sizeof(struct addrinfo));
info.ai_socktype = SOCK_STREAM;
info.ai_protocol = IPPROTO_TCP;
info.ai_addr = addr;
if (addr->sa_family == AF_INET) {
info.ai_family = AF_INET;
info.ai_addrlen = sizeof(struct sockaddr_in);
} else if (addr->sa_family == AF_INET6) {
info.ai_family = AF_INET6;
info.ai_addrlen = sizeof(struct sockaddr_in6);
}
remote_t *remote = connect_to_remote(SS_A_ & info, server);
if (remote == NULL) {
close_and_free_server(SS_A_ server);
} else {
server->remote = remote;
remote->server = server;
// XXX: should handle buffer carefully
if (server->buf->len > 0) {
brealloc(remote->buf, server->buf->len, SOCKET_BUF_SIZE);
memcpy(remote->buf->data, server->buf->data + server->buf->idx,
server->buf->len);
remote->buf->len = server->buf->len;
remote->buf->idx = 0;
server->buf->len = 0;
server->buf->idx = 0;
}
// listen to remote connected event
ss_io_start(SS_A_ & remote->send_ctx->io);
}
}
}
static void
remote_recv_cb(SS_P_ ss_io *w, int revents)
{
remote_ctx_t *remote_recv_ctx = (remote_ctx_t *)w;
remote_t *remote = remote_recv_ctx->remote;
server_t *server = remote->server;
if (server == NULL) {
LOGE("invalid server");
close_and_free_remote(SS_A_ remote);
return;
}
ss_timer_again(SS_A_ & server->recv_ctx->watcher);
ssize_t r = recv(remote->fd, server->buf->data, SOCKET_BUF_SIZE, 0);
if (r == 0) {
// connection closed
close_and_free_remote(SS_A_ remote);
close_and_free_server(SS_A_ server);
return;
} else if (r == -1) {
if (ss_socket_error() == EAGAIN || ss_socket_error() == EWOULDBLOCK) {
// no data
// continue to wait for recv
return;
} else {
ERROR("remote recv");
close_and_free_remote(SS_A_ remote);
close_and_free_server(SS_A_ server);
return;
}
}
rx += r;
// Ignore any new packet if the server is stopped
if (server->stage == STAGE_STOP) {
return;
}
server->buf->len = r;
int err = crypto->encrypt(server->buf, server->e_ctx, SOCKET_BUF_SIZE);
if (err) {
LOGE("invalid password or cipher");
close_and_free_remote(SS_A_ remote);
close_and_free_server(SS_A_ server);
return;
}
#ifdef USE_NFCONNTRACK_TOS
setTosFromConnmark(remote, server);
#endif
int s = send(server->fd, server->buf->data, server->buf->len, 0);
if (s == -1) {
if (ss_socket_error() == EAGAIN || ss_socket_error() == EWOULDBLOCK) {
// no data, wait for send
server->buf->idx = 0;
ss_io_stop(SS_A_ & remote_recv_ctx->io);
ss_io_start(SS_A_ & server->send_ctx->io);
} else {
ERROR("remote_recv_send");
close_and_free_remote(SS_A_ remote);
close_and_free_server(SS_A_ server);
return;
}
} else if (s < server->buf->len) {
server->buf->len -= s;
server->buf->idx = s;
ss_io_stop(SS_A_ & remote_recv_ctx->io);
ss_io_start(SS_A_ & server->send_ctx->io);
}
// Disable TCP_NODELAY after the first response are sent
if (!remote->recv_ctx->connected && !no_delay) {
int opt = 0;
ss_setsockopt(server->fd, SOL_TCP, TCP_NODELAY, &opt, sizeof(opt));
ss_setsockopt(remote->fd, SOL_TCP, TCP_NODELAY, &opt, sizeof(opt));
}
remote->recv_ctx->connected = 1;
}
static void
remote_send_cb(SS_P_ ss_io *w, int revents)
{
remote_ctx_t *remote_send_ctx = (remote_ctx_t *)w;
remote_t *remote = remote_send_ctx->remote;
server_t *server = remote->server;
if (server == NULL) {
LOGE("invalid server");
close_and_free_remote(SS_A_ remote);
return;
}
if (!remote_send_ctx->connected) {
#ifdef TCP_FASTOPEN_WINSOCK
if (fast_open) {
// Check if ConnectEx is done
if (!remote->connect_ex_done) {
DWORD numBytes;
DWORD flags;
// Non-blocking way to fetch ConnectEx result
if (WSAGetOverlappedResult(remote->fd, &remote->olap,
&numBytes, FALSE, &flags)) {
remote->buf->len -= numBytes;
remote->buf->idx = numBytes;
remote->connect_ex_done = 1;
} else if (WSAGetLastError() == WSA_IO_INCOMPLETE) {
// XXX: ConnectEx still not connected, wait for next time
return;
} else {
ERROR("WSAGetOverlappedResult");
// not connected
close_and_free_remote(SS_A_ remote);
close_and_free_server(SS_A_ server);
return;
}
}
// Make getpeername work
if (ss_setsockopt(remote->fd, SOL_SOCKET,
SO_UPDATE_CONNECT_CONTEXT, NULL, 0) != 0) {
ERROR("setsockopt");
}
}
#endif
struct sockaddr_storage addr;
socklen_t len = sizeof(struct sockaddr_storage);
memset(&addr, 0, len);
int r = getpeername(remote->fd, (struct sockaddr *)&addr, &len);
if (r == 0) {
remote_send_ctx->connected = 1;
if (remote->buf->len == 0) {
server->stage = STAGE_STREAM;
ss_io_stop(SS_A_ & remote_send_ctx->io);
ss_io_start(SS_A_ & server->recv_ctx->io);
ss_io_start(SS_A_ & remote->recv_ctx->io);
return;
}
} else {
ERROR("getpeername");
// not connected
close_and_free_remote(SS_A_ remote);
close_and_free_server(SS_A_ server);
return;
}
}
if (remote->buf->len == 0) {
// close and free
close_and_free_remote(SS_A_ remote);
close_and_free_server(SS_A_ server);
return;
} else {
// has data to send
ssize_t s = send(remote->fd, remote->buf->data + remote->buf->idx,
remote->buf->len, 0);
if (s == -1) {
if (ss_socket_error() != EAGAIN && ss_socket_error() != EWOULDBLOCK) {
ERROR("remote_send_send");
// close and free
close_and_free_remote(SS_A_ remote);
close_and_free_server(SS_A_ server);
}
return;
} else if (s < remote->buf->len) {
// partly sent, move memory, wait for the next time to send
remote->buf->len -= s;
remote->buf->idx += s;
return;
} else {
// all sent out, wait for reading
remote->buf->len = 0;
remote->buf->idx = 0;
ss_io_stop(SS_A_ & remote_send_ctx->io);
if (server != NULL) {
ss_io_start(SS_A_ & server->recv_ctx->io);
if (server->stage != STAGE_STREAM) {
server->stage = STAGE_STREAM;
ss_io_start(SS_A_ & remote->recv_ctx->io);
}
} else {
LOGE("invalid server");
close_and_free_remote(SS_A_ remote);
close_and_free_server(SS_A_ server);
}
return;
}
}
}
static remote_t *
new_remote(int fd)
{
if (verbose) {
remote_conn++;
LOGI("new connection to remote, %d opened remote connections", remote_conn);
}
remote_t *remote = (remote_t *)ss_malloc(sizeof(remote_t));
memset(remote, 0, sizeof(remote_t));
remote->recv_ctx = (struct remote_ctx *)ss_malloc(sizeof(remote_ctx_t));
remote->send_ctx = (struct remote_ctx *)ss_malloc(sizeof(remote_ctx_t));
remote->buf = (buffer_t *)ss_malloc(sizeof(buffer_t));
balloc(remote->buf, SOCKET_BUF_SIZE);
memset(remote->recv_ctx, 0, sizeof(remote_ctx_t));
memset(remote->send_ctx, 0, sizeof(remote_ctx_t));
remote->fd = fd;
remote->recv_ctx->remote = remote;
remote->recv_ctx->connected = 0;
remote->send_ctx->remote = remote;
remote->send_ctx->connected = 0;
remote->server = NULL;
ss_io_init(&remote->recv_ctx->io, remote_recv_cb, fd, SS_READ);
ss_io_init(&remote->send_ctx->io, remote_send_cb, fd, SS_WRITE);
return remote;
}
static void
free_remote(remote_t *remote)
{
if (remote->server != NULL) {
remote->server->remote = NULL;
}
if (remote->buf != NULL) {
bfree(remote->buf);
ss_free(remote->buf);
}
ss_free(remote->recv_ctx);
ss_free(remote->send_ctx);
ss_free(remote);
}
static void
close_and_free_remote(SS_P_ remote_t *remote)
{
if (remote != NULL) {
ss_io_stop(SS_A_ & remote->send_ctx->io);
ss_io_stop(SS_A_ & remote->recv_ctx->io);
ss_socket_close(remote->fd);
free_remote(remote);
if (verbose) {
remote_conn--;
LOGI("close a connection to remote, %d opened remote connections", remote_conn);
}
}
}
static server_t *
new_server(int fd, listen_ctx_t *listener)
{
if (verbose) {
server_conn++;
LOGI("new connection from client, %d opened client connections", server_conn);
}
server_t *server;
server = (server_t *)ss_malloc(sizeof(server_t));
memset(server, 0, sizeof(server_t));
server->recv_ctx = (struct server_ctx *)ss_malloc(sizeof(server_ctx_t));
server->send_ctx = (struct server_ctx *)ss_malloc(sizeof(server_ctx_t));
server->buf = (buffer_t *)ss_malloc(sizeof(buffer_t));
memset(server->recv_ctx, 0, sizeof(server_ctx_t));
memset(server->send_ctx, 0, sizeof(server_ctx_t));
balloc(server->buf, SOCKET_BUF_SIZE);
server->fd = fd;
server->recv_ctx->server = server;
server->recv_ctx->connected = 0;
server->send_ctx->server = server;
server->send_ctx->connected = 0;
server->stage = STAGE_INIT;
server->frag = 0;
server->query = NULL;
server->listen_ctx = listener;
server->remote = NULL;
server->e_ctx = (cipher_ctx_t *)ss_malloc(sizeof(cipher_ctx_t));
server->d_ctx = (cipher_ctx_t *)ss_malloc(sizeof(cipher_ctx_t));
crypto->ctx_init(crypto->cipher, server->e_ctx, 1);
crypto->ctx_init(crypto->cipher, server->d_ctx, 0);
cipher_ctx_pair(server->e_ctx, server->d_ctx);
int timeout = max(MIN_TCP_IDLE_TIMEOUT, server->listen_ctx->timeout);
ss_io_init(&server->recv_ctx->io, server_recv_cb, fd, SS_READ);
ss_io_init(&server->send_ctx->io, server_send_cb, fd, SS_WRITE);
ss_timer_init(&server->recv_ctx->watcher, server_timeout_cb,
timeout, timeout);
ss_list_add(&connections, &server->entries);
return server;
}
static void
free_server(server_t *server)
{
#ifdef USE_NFCONNTRACK_TOS
if (server->tracker) {
struct dscptracker *tracker = server->tracker;
struct nf_conntrack *ct = server->tracker->ct;
server->tracker = NULL;
if (ct) {
nfct_destroy(ct);
}
free(tracker);
}
#endif
ss_list_remove(&server->entries);
if (server->remote != NULL) {
server->remote->server = NULL;
}
if (server->e_ctx != NULL) {
crypto->ctx_release(server->e_ctx);
ss_free(server->e_ctx);
}
if (server->d_ctx != NULL) {
crypto->ctx_release(server->d_ctx);
ss_free(server->d_ctx);
}
if (server->buf != NULL) {
bfree(server->buf);
ss_free(server->buf);
}
ss_free(server->recv_ctx);
ss_free(server->send_ctx);
ss_free(server);
}
static void
close_and_free_server(SS_P_ server_t *server)
{
if (server != NULL) {
if (server->query != NULL) {
server->query->server = NULL;
server->query = NULL;
}
ss_io_stop(SS_A_ & server->send_ctx->io);
ss_io_stop(SS_A_ & server->recv_ctx->io);
ss_timer_stop(SS_A_ & server->recv_ctx->watcher);
ss_socket_close(server->fd);
free_server(server);
if (verbose) {
server_conn--;
LOGI("close a connection from client, %d opened client connections", server_conn);
}
}
}
static void
signal_cb(SS_P_ ss_signal *w, int revents)
{
if (revents & SS_SIGNAL) {
switch (w->signum) {
#ifndef __MINGW32__
case SIGCHLD:
if (!is_plugin_running()) {
LOGE("plugin service exit unexpectedly");
ret_val = -1;
} else
return;
#endif
case SIGINT:
case SIGTERM:
ss_signal_stop(SS_DEFAULT, &sigint_watcher);
ss_signal_stop(SS_DEFAULT, &sigterm_watcher);
#ifndef __MINGW32__
ss_signal_stop(SS_DEFAULT, &sigchld_watcher);
#else
ss_io_stop(SS_DEFAULT, &plugin_watcher.io);
#endif
ss_unloop(SS_A_ SS_UNLOOP_ALL);
}
}
}
#ifdef __MINGW32__
static void
plugin_watcher_cb(SS_P_ ss_io *w, int revents)
{
char buf[1];
SOCKET fd = ss_accept(plugin_watcher.fd, NULL, NULL);
if (fd == INVALID_SOCKET) {
return;
}
recv(fd, buf, 1, 0);
closesocket(fd);
LOGE("plugin service exit unexpectedly");
ret_val = -1;
ss_signal_stop(SS_DEFAULT, &sigint_watcher);
ss_signal_stop(SS_DEFAULT, &sigterm_watcher);
ss_io_stop(SS_DEFAULT, &plugin_watcher.io);
ss_unloop(SS_A_ SS_UNLOOP_ALL);
}
#endif
static void
accept_cb(SS_P_ ss_io *w, int revents)
{
listen_ctx_t *listener = (listen_ctx_t *)w;
int serverfd = ss_accept(listener->fd, NULL, NULL);
if (serverfd == -1) {
ERROR("accept");
return;
}
char *peer_name = get_peer_name(serverfd);
if (peer_name != NULL) {
if (acl) {
if ((get_acl_mode() == BLACK_LIST && acl_match_host(peer_name) == 1)
|| (get_acl_mode() == WHITE_LIST && acl_match_host(peer_name) >= 0)) {
LOGE("Access denied from %s", peer_name);
ss_socket_close(serverfd);
return;
}
}
}
int opt = 1;
ss_setsockopt(serverfd, SOL_TCP, TCP_NODELAY, &opt, sizeof(opt));
#ifdef SO_NOSIGPIPE
ss_setsockopt(serverfd, SOL_SOCKET, SO_NOSIGPIPE, &opt, sizeof(opt));
#endif
setnonblocking(serverfd);
server_t *server = new_server(serverfd, listener);
ss_io_start(SS_A_ & server->recv_ctx->io);
ss_timer_start(SS_A_ & server->recv_ctx->watcher);
}
int
main(int argc, char **argv)
{
int i, c;
int pid_flags = 0;
int mptcp = 0;
int mtu = 0;
int timeout_secs = 0;
char *user = NULL;
char *password = NULL;
char *key = NULL;
char *timeout = NULL;
char *method = NULL;
char *pid_path = NULL;
char *conf_path = NULL;
char *iface = NULL;
char *server_port = NULL;
char *plugin_opts = NULL;
char *plugin_host = NULL;
char *plugin_port = NULL;
char tmp_port[8];
char *nameservers = NULL;
int server_num = 0;
ss_addr_t server_addr[MAX_REMOTE_NUM];
memset(server_addr, 0, sizeof(ss_addr_t) * MAX_REMOTE_NUM);
memset(&local_addr_v4, 0, sizeof(struct sockaddr_storage));
memset(&local_addr_v6, 0, sizeof(struct sockaddr_storage));
/* [cli_short_s]
\par `-s <server_host>`
Set a server listening hostname or IP address. May be repeated.
[cli_short_s] */
/* [cli_short_p]
\par `-p <server_port>`
Set the server listening port.
[cli_short_p] */
/* [cli_short_b]
\par `-b <local_address>`
Set the local address for outbound connections to destination servers.
[cli_short_b] */
/* [cli_long_config]
\par `--config <config_file>`
Read JSON configuration. See the CLI conventions for precedence and default-value behavior. Short alias: `-c`.
[cli_long_config] */
/* [cli_long_cipher]
\par `--cipher <encrypt_method>`
Select the encryption cipher. See `-m` for supported cipher names and key requirements. Short alias: `-m`.
[cli_long_cipher] */
/* [cli_long_timeout]
\par `--timeout <timeout>`
Same behavior as `-t`; see that option for details. Short alias: `-t`.
[cli_long_timeout] */
/* [cli_long_user]
\par `--user <user_name>`
Same behavior as `-a`; see that option for details. Short alias: `-a`.
[cli_long_user] */
/* [cli_long_pid_file]
\par `--pid-file <pid_file>`
Same behavior as `-f`; see that option for details. Short alias: `-f`.
[cli_long_pid_file] */
/* [cli_long_nofile]
\par `--nofile <number>`
Same behavior as `-n`; see that option for details. Short alias: `-n`.
[cli_long_nofile] */
/* [cli_long_udp]
\par `--udp`
Enable both TCP and UDP relay, overriding the configured mode. Short alias: `-u`.
[cli_long_udp] */
/* [cli_long_udp_only]
\par `--udp-only`
Enable UDP relay only, overriding the configured mode. Short alias: `-U`.
[cli_long_udp_only] */
/* [cli_long_ipv6_first]
\par `--ipv6-first`
Prefer IPv6 DNS results; overrides configuration. This does not restrict connections to IPv6. Short alias: `-6`.
[cli_long_ipv6_first] */
/* [cli_long_verbose]
\par `--verbose`
Same behavior as `-v`; see that option for details. Short alias: `-v`.
[cli_long_verbose] */
/* [cli_long_listen_address]
\par `--listen-address <server_host>`
Set the listening address. Server and manager addresses may be repeated. Short alias: `-s`.
[cli_long_listen_address] */
/* [cli_long_listen_port]
\par `--listen-port <server_port>`
Set the listening port. Short alias: `-p`.
[cli_long_listen_port] */
/* [cli_long_outbound_address]
\par `--outbound-address <local_address>`
Set the source address for connections to destination servers. Short alias: `-b`.
[cli_long_outbound_address] */
/* [cli_long_interface]
\par `--interface <interface>`
Same behavior as `-i`; see that option for details. Short alias: `-i`.
[cli_long_interface] */
/* [cli_long_nameserver]
\par `--nameserver <addr>`
Same behavior as `-d`; see that option for details. Short alias: `-d`.
[cli_long_nameserver] */
/* [cli-options]
\snippet{doc} utils.c cli_short_f
\snippet{doc} server.c cli_short_s
\snippet{doc} server.c cli_short_p
\snippet{doc} utils.c cli_short_k
\snippet{doc} utils.c cli_short_t
\snippet{doc} utils.c cli_short_m
\snippet{doc} server.c cli_short_b
\snippet{doc} utils.c cli_short_c
\snippet{doc} utils.c cli_short_i
\snippet{doc} utils.c cli_short_d
\snippet{doc} utils.c cli_short_a
\snippet{doc} utils.c cli_short_n
\snippet{doc} utils.c cli_short_h
\snippet{doc} utils.c cli_short_u
\snippet{doc} utils.c cli_short_U
\snippet{doc} utils.c cli_short_v
\snippet{doc} utils.c cli_short_6
\snippet{doc} utils.c cli_short_A
\snippet{doc} server.c cli_long_config
\snippet{doc} server.c cli_long_cipher
\snippet{doc} server.c cli_long_timeout
\snippet{doc} server.c cli_long_user
\snippet{doc} server.c cli_long_pid_file
\snippet{doc} server.c cli_long_nofile
\snippet{doc} server.c cli_long_udp
\snippet{doc} server.c cli_long_udp_only
\snippet{doc} server.c cli_long_ipv6_first
\snippet{doc} server.c cli_long_verbose
\snippet{doc} server.c cli_long_listen_address
\snippet{doc} server.c cli_long_listen_port
\snippet{doc} server.c cli_long_outbound_address
\snippet{doc} server.c cli_long_interface
\snippet{doc} server.c cli_long_nameserver
\snippet{doc} utils.c cli_long_version
\snippet{doc} utils.c cli_long_tcp_only
\snippet{doc} utils.c cli_long_ipv4_first
\snippet{doc} utils.c cli_long_fast_open
\snippet{doc} utils.c cli_long_reuse_port
\snippet{doc} utils.c cli_long_tcp_incoming_sndbuf
\snippet{doc} utils.c cli_long_tcp_incoming_rcvbuf
\snippet{doc} utils.c cli_long_tcp_outgoing_sndbuf
\snippet{doc} utils.c cli_long_tcp_outgoing_rcvbuf
\snippet{doc} utils.c cli_long_no_delay
\snippet{doc} utils.c cli_long_acl
\snippet{doc} utils.c cli_long_manager_address
\snippet{doc} utils.c cli_long_mtu
\snippet{doc} utils.c cli_long_help
\snippet{doc} utils.c cli_long_plugin
\snippet{doc} utils.c cli_long_plugin_opts
\snippet{doc} utils.c cli_long_password
\snippet{doc} utils.c cli_long_key
\snippet{doc} utils.c cli_long_mptcp
\snippet{doc} utils.c cli_long_nftables_sets
[cli-options] */
static struct option long_options[] = {
{ "config", required_argument, NULL, 'c' },
{ "cipher", required_argument, NULL, 'm' },
{ "timeout", required_argument, NULL, 't' },
{ "user", required_argument, NULL, 'a' },
{ "pid-file", required_argument, NULL, 'f' },
{ "nofile", required_argument, NULL, 'n' },
{ "udp", no_argument, NULL, 'u' },
{ "udp-only", no_argument, NULL, 'U' },
{ "ipv6-first", no_argument, NULL, '6' },
{ "verbose", no_argument, NULL, 'v' },
{ "listen-address", required_argument, NULL, 's' },
{ "listen-port", required_argument, NULL, 'p' },
{ "outbound-address", required_argument, NULL, 'b' },
{ "interface", required_argument, NULL, 'i' },
{ "nameserver", required_argument, NULL, 'd' },
{ "version", no_argument, NULL, GETOPT_VAL_VERSION },
{ "tcp-only", no_argument, NULL, GETOPT_VAL_TCP_ONLY },
{ "ipv4-first", no_argument, NULL, GETOPT_VAL_IPV4_FIRST },
{ "fast-open", no_argument, NULL, GETOPT_VAL_FAST_OPEN },
{ "reuse-port", no_argument, NULL, GETOPT_VAL_REUSE_PORT },
{ "tcp-incoming-sndbuf", required_argument, NULL, GETOPT_VAL_TCP_INCOMING_SNDBUF },
{ "tcp-incoming-rcvbuf", required_argument, NULL, GETOPT_VAL_TCP_INCOMING_RCVBUF },
{ "tcp-outgoing-sndbuf", required_argument, NULL, GETOPT_VAL_TCP_OUTGOING_SNDBUF },
{ "tcp-outgoing-rcvbuf", required_argument, NULL, GETOPT_VAL_TCP_OUTGOING_RCVBUF },
{ "no-delay", no_argument, NULL, GETOPT_VAL_NODELAY },
{ "acl", required_argument, NULL, GETOPT_VAL_ACL },
{ "manager-address", required_argument, NULL,
GETOPT_VAL_MANAGER_ADDRESS },
{ "mtu", required_argument, NULL, GETOPT_VAL_MTU },
{ "help", no_argument, NULL, GETOPT_VAL_HELP },
{ "plugin", required_argument, NULL, GETOPT_VAL_PLUGIN },
{ "plugin-opts", required_argument, NULL, GETOPT_VAL_PLUGIN_OPTS },
{ "password", required_argument, NULL, GETOPT_VAL_PASSWORD },
{ "key", required_argument, NULL, GETOPT_VAL_KEY },
#ifdef __linux__
{ "mptcp", no_argument, NULL, GETOPT_VAL_MPTCP },
#ifdef USE_NFTABLES
{ "nftables-sets", required_argument, NULL, GETOPT_VAL_NFTABLES_SETS },
#endif
#endif
{ NULL, 0, NULL, 0 }
};
int mode_set = 0;
int ipv6first_set = 0;
opterr = 0;
USE_TTY();
while ((c = getopt_long(argc, argv, ":f:s:p:k:t:m:b:c:i:d:a:n:huUv6A",
long_options, NULL)) != -1) {
switch (c) {
case GETOPT_VAL_VERSION:
cli_version();
exit(EXIT_SUCCESS);
case GETOPT_VAL_TCP_ONLY:
mode = TCP_ONLY;
mode_set = 1;
break;
case GETOPT_VAL_IPV4_FIRST:
ipv6first = 0;
ipv6first_set = 1;
break;
case GETOPT_VAL_FAST_OPEN:
fast_open = 1;
break;
case GETOPT_VAL_NODELAY:
no_delay = 1;
LOGI("enable TCP no-delay");
break;
case GETOPT_VAL_ACL:
LOGI("initializing acl...");
acl = !init_acl(optarg);
break;
case GETOPT_VAL_MANAGER_ADDRESS:
manager_addr = optarg;
break;
case GETOPT_VAL_MTU:
if (ss_parse_int(optarg, 0, INT_MAX, &mtu) == -1) {
cli_error("invalid MTU", c, NULL);
}
LOGI("set MTU to %d", mtu);
break;
case GETOPT_VAL_PLUGIN:
plugin = optarg;
break;
case GETOPT_VAL_PLUGIN_OPTS:
plugin_opts = optarg;
break;
case GETOPT_VAL_MPTCP:
mptcp = get_mptcp(1);
if (mptcp)
LOGI("enable multipath TCP (%s)", mptcp > 0 ? "out-of-tree" : "upstream");
break;
case GETOPT_VAL_KEY:
key = optarg;
break;
case GETOPT_VAL_REUSE_PORT:
reuse_port = 1;
break;
case GETOPT_VAL_TCP_INCOMING_SNDBUF:
if (ss_parse_int(optarg, 0, INT_MAX, &tcp_incoming_sndbuf) == -1) {
cli_error("invalid TCP incoming send buffer size", c, NULL);
}
break;
case GETOPT_VAL_TCP_INCOMING_RCVBUF:
if (ss_parse_int(optarg, 0, INT_MAX, &tcp_incoming_rcvbuf) == -1) {
cli_error("invalid TCP incoming receive buffer size", c, NULL);
}
break;
case GETOPT_VAL_TCP_OUTGOING_SNDBUF:
if (ss_parse_int(optarg, 0, INT_MAX, &tcp_outgoing_sndbuf) == -1) {
cli_error("invalid TCP outgoing send buffer size", c, NULL);
}
break;
case GETOPT_VAL_TCP_OUTGOING_RCVBUF:
if (ss_parse_int(optarg, 0, INT_MAX, &tcp_outgoing_rcvbuf) == -1) {
cli_error("invalid TCP outgoing receive buffer size", c, NULL);
}
break;
#ifdef USE_NFTABLES
case GETOPT_VAL_NFTABLES_SETS:
nftbl_init(optarg);
break;
#endif
case 's':
if (server_num < MAX_REMOTE_NUM) {
parse_addr(optarg, &server_addr[server_num++]);
}
break;
case 'b':
is_bind_local_addr += parse_local_addr(&local_addr_v4, &local_addr_v6, optarg);
break;
case 'p':
{
uint16_t checked_port;
if (ss_parse_uint16_port(optarg, &checked_port) != 0)
cli_error("port must be an integer from 1 to 65535", c, NULL);
}
server_port = optarg;
break;
case GETOPT_VAL_PASSWORD:
case 'k':
password = optarg;
break;
case 'f':
pid_flags = 1;
pid_path = optarg;
break;
case 't':
{
int checked_timeout;
if (ss_parse_int(optarg, 1, INT_MAX, &checked_timeout) != 0)
cli_error("timeout must be a positive integer", c, NULL);
}
timeout = optarg;
break;
case 'm':
method = optarg;
break;
case 'c':
conf_path = optarg;
break;
case 'i':
iface = optarg;
break;
case 'd':
nameservers = optarg;
break;
case 'a':
user = optarg;
break;
#ifdef HAVE_SETRLIMIT
case 'n':
if (ss_parse_int(optarg, 0, INT_MAX, &nofile) == -1) {
cli_error("invalid nofile", c, NULL);
}
break;
#endif
case 'u':
mode_set = 1;
mode = TCP_AND_UDP;
break;
case 'U':
mode_set = 1;
mode = UDP_ONLY;
break;
case 'v':
verbose = 1;
break;
case GETOPT_VAL_HELP:
case 'h':
usage();
exit(EXIT_SUCCESS);
case '6':
ipv6first_set = 1;
ipv6first = 1;
break;
case 'A':
cli_error("one-time authentication was removed; use an AEAD cipher", 'A', NULL);
break;
case ':':
cli_error("missing required argument for option", optopt, argv[optind - 1]);
break;
case '?':
cli_error("unrecognized or invalid option", optopt, argv[optind - 1]);
break;
default:
cli_error("option is unsupported on this platform", c, NULL);
break;
}
}
if (optind < argc) {
cli_error("unexpected positional argument", 0, NULL);
}
if (argc == 1) {
if (conf_path == NULL) {
conf_path = get_default_conf();
}
}
if (conf_path != NULL) {
jconf_t *conf = read_jconf(conf_path);
if (server_num == 0) {
server_num = conf->remote_num;
for (i = 0; i < server_num; i++)
server_addr[i] = conf->remote_addr[i];
}
if (server_port == NULL) {
server_port = conf->remote_port;
}
if (password == NULL) {
password = conf->password;
}
if (key == NULL) {
key = conf->key;
}
if (method == NULL) {
method = conf->method;
}
if (timeout == NULL) {
timeout = conf->timeout;
}
if (user == NULL) {
user = conf->user;
}
if (plugin == NULL) {
plugin = conf->plugin;
}
if (plugin_opts == NULL) {
plugin_opts = conf->plugin_opts;
}
if (!mode_set) {
mode = conf->mode;
}
if (mtu == 0) {
mtu = conf->mtu;
}
if (mptcp == 0) {
mptcp = conf->mptcp;
}
if (no_delay == 0) {
no_delay = conf->no_delay;
}
if (reuse_port == 0) {
reuse_port = conf->reuse_port;
}
if (tcp_incoming_sndbuf == 0) {
tcp_incoming_sndbuf = conf->tcp_incoming_sndbuf;
}
if (tcp_incoming_rcvbuf == 0) {
tcp_incoming_rcvbuf = conf->tcp_incoming_rcvbuf;
}
if (tcp_outgoing_sndbuf == 0) {
tcp_outgoing_sndbuf = conf->tcp_outgoing_sndbuf;
}
if (tcp_outgoing_rcvbuf == 0) {
tcp_outgoing_rcvbuf = conf->tcp_outgoing_rcvbuf;
}
if (fast_open == 0) {
fast_open = conf->fast_open;
}
if (is_bind_local_addr == 0) {
is_bind_local_addr += parse_local_addr(&local_addr_v4, &local_addr_v6, conf->local_addr);
}
if (is_bind_local_addr == 0) {
is_bind_local_addr += parse_local_addr(&local_addr_v4, &local_addr_v6, conf->local_addr_v4);
is_bind_local_addr += parse_local_addr(&local_addr_v4, &local_addr_v6, conf->local_addr_v6);
}
#ifdef HAVE_SETRLIMIT
if (nofile == 0) {
nofile = conf->nofile;
}
#endif
if (nameservers == NULL) {
nameservers = conf->nameserver;
}
if (!ipv6first_set) {
ipv6first = conf->ipv6_first;
}
if (acl == 0 && conf->acl != NULL) {
LOGI("initializing acl...");
acl = !init_acl(conf->acl);
}
}
if (tcp_incoming_sndbuf != 0 && tcp_incoming_sndbuf < SOCKET_BUF_SIZE) {
tcp_incoming_sndbuf = 0;
}
if (tcp_incoming_sndbuf != 0) {
LOGI("set TCP incoming connection send buffer size to %d", tcp_incoming_sndbuf);
}
if (tcp_incoming_rcvbuf != 0 && tcp_incoming_rcvbuf < SOCKET_BUF_SIZE) {
tcp_incoming_rcvbuf = 0;
}
if (tcp_incoming_rcvbuf != 0) {
LOGI("set TCP incoming connection receive buffer size to %d", tcp_incoming_rcvbuf);
}
if (tcp_outgoing_sndbuf != 0 && tcp_outgoing_sndbuf < SOCKET_BUF_SIZE) {
tcp_outgoing_sndbuf = 0;
}
if (tcp_outgoing_sndbuf != 0) {
LOGI("set TCP outgoing connection send buffer size to %d", tcp_outgoing_sndbuf);
}
if (tcp_outgoing_rcvbuf != 0 && tcp_outgoing_rcvbuf < SOCKET_BUF_SIZE) {
tcp_outgoing_rcvbuf = 0;
}
if (tcp_outgoing_rcvbuf != 0) {
LOGI("set TCP outgoing connection receive buffer size to %d", tcp_outgoing_rcvbuf);
}
if (server_num == 0) {
server_addr[server_num++].host = (char *)"0.0.0.0";
}
if (server_num == 0 || server_port == NULL
|| (password == NULL && key == NULL)) {
usage();
exit(EXIT_FAILURE);
}
if (is_ipv6only(server_addr, server_num, ipv6first)) {
plugin_host = (char *)"::1";
} else {
plugin_host = (char *)"127.0.0.1";
}
remote_port = server_port;
#ifdef __MINGW32__
winsock_init();
#endif
if (plugin != NULL) {
uint16_t port = get_local_port();
if (port == 0) {
FATAL("failed to find a free port");
}
snprintf(tmp_port, 8, "%d", port);
plugin_port = server_port;
server_port = tmp_port;
#ifdef __MINGW32__
memset(&plugin_watcher, 0, sizeof(plugin_watcher));
plugin_watcher.port = get_local_port();
if (plugin_watcher.port == 0) {
LOGE("failed to assign a control port for plugin");
}
#endif
}
if (method == NULL) {
method = (char *)"chacha20-ietf-poly1305";
}
if (timeout == NULL) {
timeout = (char *)"60";
}
if (ss_parse_int(timeout, 1, INT_MAX, &timeout_secs) == -1) {
FATAL("invalid timeout");
}
#ifdef HAVE_SETRLIMIT
/*
* no need to check the return value here since we will show
* the user an error message if setrlimit(2) fails
*/
if (nofile > 1024) {
if (verbose) {
LOGI("setting NOFILE to %d", nofile);
}
set_nofile(nofile);
}
#endif
USE_SYSLOG(argv[0], pid_flags);
if (pid_flags) {
daemonize(pid_path);
}
if (ipv6first) {
LOGI("resolving hostname to IPv6 address first");
}
if (fast_open == 1) {
#ifdef TCP_FASTOPEN
LOGI("using tcp fast open");
#else
LOGE("tcp fast open is not supported by this environment");
fast_open = 0;
#endif
}
if (plugin != NULL) {
LOGI("plugin \"%s\" enabled", plugin);
}
if (mode != TCP_ONLY) {
LOGI("UDP relay enabled");
}
if (mode == UDP_ONLY) {
LOGI("TCP relay disabled");
}
if (no_delay) {
LOGI("enable TCP no-delay");
}
#ifndef __MINGW32__
// ignore SIGPIPE
signal(SIGPIPE, SIG_IGN);
signal(SIGABRT, SIG_IGN);
#endif
ss_signal_init(&sigint_watcher, signal_cb, SIGINT);
ss_signal_init(&sigterm_watcher, signal_cb, SIGTERM);
ss_signal_start(SS_DEFAULT, &sigint_watcher);
ss_signal_start(SS_DEFAULT, &sigterm_watcher);
#ifndef __MINGW32__
ss_signal_init(&sigchld_watcher, signal_cb, SIGCHLD);
ss_signal_start(SS_DEFAULT, &sigchld_watcher);
#endif
// setup keys
LOGI("initializing ciphers... %s", method);
crypto = crypto_init(password, key, method);
if (crypto == NULL)
FATAL("failed to initialize ciphers");
// initialize ev loop
struct ss_loop *loop = SS_DEFAULT;
// setup dns
resolv_init(loop, nameservers, ipv6first);
if (nameservers != NULL)
LOGI("using nameserver: %s", nameservers);
#ifdef __MINGW32__
// Listen on plugin control port
if (plugin != NULL && plugin_watcher.port != 0) {
SOCKET fd;
fd = ss_socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (fd != INVALID_SOCKET) {
plugin_watcher.valid = 0;
do {
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
addr.sin_port = htons(plugin_watcher.port);
if (bind(fd, (struct sockaddr *)&addr, sizeof(addr))) {
LOGE("failed to bind plugin control port");
break;
}
if (listen(fd, 1)) {
LOGE("failed to listen on plugin control port");
break;
}
plugin_watcher.fd = fd;
ss_io_init(&plugin_watcher.io, plugin_watcher_cb, fd, SS_READ);
ss_io_start(SS_DEFAULT, &plugin_watcher.io);
plugin_watcher.valid = 1;
} while (0);
if (!plugin_watcher.valid) {
closesocket(fd);
plugin_watcher.port = 0;
}
}
}
#endif
// Start plugin server
if (plugin != NULL) {
int len = 0;
size_t buf_size = 256 * server_num;
char *server_str = (char *)ss_malloc(buf_size);
snprintf(server_str, buf_size, "%s", server_addr[0].host);
len = strlen(server_str);
for (int i = 1; i < server_num; i++) {
snprintf(server_str + len, buf_size - len, "|%s", server_addr[i].host);
len = strlen(server_str);
}
int err = start_plugin(plugin, plugin_opts, server_str,
plugin_port, plugin_host, server_port,
#ifdef __MINGW32__
plugin_watcher.port,
#endif
MODE_SERVER);
if (err) {
ERROR("start_plugin");
FATAL("failed to start the plugin");
}
}
// initialize listen context (server_num never exceeds MAX_REMOTE_NUM, the
// size of server_addr)
listen_ctx_t listen_ctx_list[MAX_REMOTE_NUM];
// bind to each interface
if (mode != UDP_ONLY) {
int num_listen_ctx = 0;
for (int i = 0; i < server_num; i++) {
const char *host = server_addr[i].host;
const char *port = server_addr[i].port ? server_addr[i].port : server_port;
if (plugin != NULL) {
host = plugin_host;
}
if (host && ss_is_ipv6addr(host))
LOGI("tcp server listening at [%s]:%s", host, port);
else
LOGI("tcp server listening at %s:%s", host ? host : "0.0.0.0", port);
// Bind to port
int listenfd;
listenfd = create_and_bind(host, port, mptcp);
if (listenfd == -1) {
continue;
}
if (listen(listenfd, SSMAXCONN) == -1) {
ERROR("listen()");
continue;
}
setfastopen(listenfd);
setnonblocking(listenfd);
listen_ctx_t *listen_ctx = &listen_ctx_list[i];
// Setup proxy context
listen_ctx->timeout = timeout_secs;
listen_ctx->fd = listenfd;
listen_ctx->iface = iface;
listen_ctx->loop = loop;
ss_io_init(&listen_ctx->io, accept_cb, listenfd, SS_READ);
ss_io_start(loop, &listen_ctx->io);
num_listen_ctx++;
if (plugin != NULL)
break;
}
if (num_listen_ctx == 0) {
FATAL("failed to listen on any address");
}
}
if (mode != TCP_ONLY) {
int num_listen_ctx = 0;
for (int i = 0; i < server_num; i++) {
const char *host = server_addr[i].host;
const char *port = server_addr[i].port ? server_addr[i].port : server_port;
if (plugin != NULL) {
port = plugin_port;
}
if (host && ss_is_ipv6addr(host))
LOGI("udp server listening at [%s]:%s", host, port);
else
LOGI("udp server listening at %s:%s", host ? host : "0.0.0.0", port);
// Setup UDP
int err = init_udprelay(host, port, mtu, crypto, timeout_secs, iface);
if (err == -1)
continue;
num_listen_ctx++;
}
if (num_listen_ctx == 0) {
FATAL("failed to listen on any address");
}
}
#ifdef __MINGW32__
if (manager_addr != NULL) {
LOGE("--manager-address is not supported on Windows; ignored");
}
#endif
#ifndef __MINGW32__
if (manager_addr != NULL) {
ss_timer_init(&stat_update_watcher, stat_update_cb, UPDATE_INTERVAL, UPDATE_INTERVAL);
ss_timer_start(SS_DEFAULT, &stat_update_watcher);
}
#endif
#ifndef __MINGW32__
// setuid
if (user != NULL && !run_as(user)) {
FATAL("failed to switch user");
}
if (geteuid() == 0) {
LOGI("running from root user");
}
#endif
// Init connections
ss_list_init(&connections);
// start ev loop
ss_run(loop, 0);
if (verbose) {
LOGI("closed gracefully");
}
#ifndef __MINGW32__
if (manager_addr != NULL) {
ss_timer_stop(SS_DEFAULT, &stat_update_watcher);
}
#endif
if (plugin != NULL) {
stop_plugin();
}
// Clean up
resolv_shutdown(loop);
for (int i = 0; i < server_num; i++) {
listen_ctx_t *listen_ctx = &listen_ctx_list[i];
if (mode != UDP_ONLY) {
ss_io_stop(loop, &listen_ctx->io);
ss_socket_close(listen_ctx->fd);
}
if (plugin != NULL)
break;
}
if (mode != UDP_ONLY) {
free_connections(loop);
}
if (mode != TCP_ONLY) {
free_udprelay();
}
ss_loop_destroy(loop);
#ifdef __MINGW32__
if (plugin_watcher.valid) {
closesocket(plugin_watcher.fd);
}
winsock_cleanup();
#endif
return ret_val;
}