Files
shadowsocks-libev/src/server.c
T
Aven 4bbbe212eb Add support of nftables firewall
The change is to support the 'fail2ban' feature. Instead of
blocking IPs by server itself, server just add malicious IPs
to specified sets of nftables. So admin can configure rules
to deal with those IPs.

Notes: cap_net_admin capability is required.

Example configuration:
 # nft add table ip fail2ban
 # nft add chain ip fail2ban input { type filter hook input priority 0 \; }
 # nft add set ip fail2ban badips { type ipv4_addr \; flags dynamic, timeout \; timeout 1h \; }
 # nft add rule ip fail2ban input ip saddr @badips drop
 # ss-server -c config.json --nftables-sets badips
2021-07-01 12:05:39 +08:00

2432 lines
74 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 <libcork/core.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 "winsock.h"
#include "resolv.h"
#ifndef EAGAIN
#define EAGAIN EWOULDBLOCK
#endif
#ifndef EWOULDBLOCK
#define EWOULDBLOCK EAGAIN
#endif
#ifndef SSMAXCONN
#define SSMAXCONN 1024
#endif
#ifndef MAX_FRAG
#define MAX_FRAG 1
#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(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);
static void server_recv_cb(EV_P_ ev_io *w, int revents);
static void remote_recv_cb(EV_P_ ev_io *w, int revents);
static void remote_send_cb(EV_P_ ev_io *w, int revents);
static void server_timeout_cb(EV_P_ ev_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(EV_P_ struct addrinfo *res,
server_t *server);
static void free_remote(remote_t *remote);
static void close_and_free_remote(EV_P_ remote_t *remote);
static void free_server(server_t *server);
static void close_and_free_server(EV_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__
ev_timer stat_update_watcher;
#endif
static struct ev_signal sigint_watcher;
static struct ev_signal sigterm_watcher;
#ifndef __MINGW32__
static struct ev_signal sigchld_watcher;
#else
static struct plugin_watcher_t {
ev_io io;
SOCKET fd;
uint16_t port;
int valid;
} plugin_watcher;
#endif
static struct cork_dllist connections;
#ifndef __MINGW32__
static void
stat_update_cb(EV_P_ ev_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) {
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", remote_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_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");
close(sfd);
return;
}
unlink(claddr.sun_path);
} else {
struct sockaddr_storage storage;
memset(&storage, 0, sizeof(struct sockaddr_storage));
if (get_sockaddr(ip_addr.host, ip_addr.port, &storage, 0, ipv6first) == -1) {
ERROR("failed to parse the manager addr");
return;
}
sfd = socket(storage.ss_family, SOCK_DGRAM, 0);
if (sfd == -1) {
ERROR("stat_socket");
return;
}
size_t addr_len = get_sockaddr_len((struct sockaddr *)&storage);
if (sendto(sfd, resp, strlen(resp) + 1, 0, (struct sockaddr *)&storage,
addr_len) != msgLen) {
ERROR("stat_sendto");
close(sfd);
return;
}
}
close(sfd);
}
#endif
static void
free_connections(struct ev_loop *loop)
{
struct cork_dllist_item *curr, *next;
cork_dllist_foreach_void(&connections, curr, next) {
server_t *server = cork_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;
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;
inet_ntop(AF_INET6, &s->sin6_addr, peer_name, INET6_ADDRSTRLEN);
}
} else {
return NULL;
}
return peer_name;
}
static void
stop_server(EV_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 = setsockopt(fd, IPPROTO_TCP, TCP_FASTOPEN, &opt, sizeof(opt));
if (s == -1) {
if (errno == EPROTONOSUPPORT || errno == 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;
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) {
listen_sock = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
if (listen_sock == -1) {
continue;
}
if (rp->ai_family == AF_INET6) {
int opt = host ? 1 : 0;
setsockopt(listen_sock, IPPROTO_IPV6, IPV6_V6ONLY, &opt, sizeof(opt));
}
int opt = 1;
setsockopt(listen_sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
#ifdef SO_NOSIGPIPE
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 (mptcp == 1) {
int i = 0;
while ((mptcp = mptcp_enabled_values[i]) > 0) {
int err = setsockopt(listen_sock, IPPROTO_TCP, mptcp, &opt, sizeof(opt));
if (err != -1) {
break;
}
i++;
}
if (mptcp == 0) {
ERROR("failed to enable 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");
}
close(listen_sock);
listen_sock = -1;
}
freeaddrinfo(result);
return listen_sock;
}
static remote_t *
connect_to_remote(EV_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);
if (res->ai_addr->sa_family == AF_INET) {
struct sockaddr_in s;
memcpy(&s, res->ai_addr, sizeof(struct sockaddr_in));
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));
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 = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
if (sockfd == -1) {
ERROR("socket");
close(sockfd);
return NULL;
}
int opt = 1;
setsockopt(sockfd, SOL_TCP, TCP_NODELAY, &opt, sizeof(opt));
#ifdef SO_NOSIGPIPE
setsockopt(sockfd, SOL_SOCKET, SO_NOSIGPIPE, &opt, sizeof(opt));
#endif
setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
if (tcp_outgoing_sndbuf > 0) {
setsockopt(sockfd, SOL_SOCKET, SO_SNDBUF, &tcp_outgoing_sndbuf, sizeof(int));
}
if (tcp_outgoing_rcvbuf > 0) {
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");
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 (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 (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 (errno == 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 (errno == EOPNOTSUPP || errno == EPROTONOSUPPORT ||
errno == 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 && errno != CONNECT_IN_PROGRESS) {
ERROR("connect");
close_and_free_remote(EV_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 (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(errno));
}
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(EV_P_ ev_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
ev_timer_again(EV_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(EV_A_ remote);
close_and_free_server(EV_A_ server);
return;
} else if (r == -1) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
// no data
// continue to wait for recv
return;
} else {
ERROR("server recv");
close_and_free_remote(EV_A_ remote);
close_and_free_server(EV_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(EV_A_ server);
return;
} else if (err == CRYPTO_NEED_MORE) {
if (server->stage != STAGE_STREAM) {
if (server->frag > MAX_FRAG) {
report_addr(server->fd, "malicious fragmentation");
stop_server(EV_A_ server);
return;
}
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 (errno == EAGAIN || errno == EWOULDBLOCK) {
// no data, wait for send
remote->buf->idx = 0;
ev_io_stop(EV_A_ & server_recv_ctx->io);
ev_io_start(EV_A_ & remote->send_ctx->io);
} else {
ERROR("server_recv_send");
close_and_free_remote(EV_A_ remote);
close_and_free_server(EV_A_ server);
}
} else if (s < remote->buf->len) {
remote->buf->len -= s;
remote->buf->idx = s;
ev_io_stop(EV_A_ & server_recv_ctx->io);
ev_io_start(EV_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;
char atyp = server->buf->data[offset++];
char host[255] = { 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);
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(EV_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
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);
offset += name_len + 1;
} else {
report_addr(server->fd, "invalid host name length");
stop_server(EV_A_ server);
return;
}
if (acl && outbound_block_match_host(host) == 1) {
if (verbose)
LOGI("outbound blocked %s", host);
close_and_free_server(EV_A_ server);
return;
}
struct cork_ip ip;
if (cork_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(EV_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);
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(EV_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(EV_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(EV_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(EV_A_ & info, server);
if (remote == NULL) {
LOGE("connect error");
close_and_free_server(EV_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
ev_io_stop(EV_A_ & server_recv_ctx->io);
ev_io_start(EV_A_ & remote->send_ctx->io);
}
} else {
ev_io_stop(EV_A_ & server_recv_ctx->io);
query_t *query = 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(EV_P_ ev_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(EV_A_ server);
return;
}
if (server->buf->len == 0) {
// close and free
close_and_free_remote(EV_A_ remote);
close_and_free_server(EV_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 (errno != EAGAIN && errno != EWOULDBLOCK) {
ERROR("server_send_send");
close_and_free_remote(EV_A_ remote);
close_and_free_server(EV_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;
ev_io_stop(EV_A_ & server_send_ctx->io);
if (remote != NULL) {
ev_io_start(EV_A_ & remote->recv_ctx->io);
return;
} else {
LOGE("invalid remote");
close_and_free_remote(EV_A_ remote);
close_and_free_server(EV_A_ server);
return;
}
}
}
}
static void
server_timeout_cb(EV_P_ ev_timer *watcher, int revents)
{
server_ctx_t *server_ctx
= cork_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(EV_A_ remote);
close_and_free_server(EV_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 ev_loop *loop = server->listen_ctx->loop;
if (addr == NULL) {
LOGE("unable to resolve %s", query->hostname);
close_and_free_server(EV_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(EV_A_ & info, server);
if (remote == NULL) {
close_and_free_server(EV_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
ev_io_start(EV_A_ & remote->send_ctx->io);
}
}
}
static void
remote_recv_cb(EV_P_ ev_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(EV_A_ remote);
return;
}
ev_timer_again(EV_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(EV_A_ remote);
close_and_free_server(EV_A_ server);
return;
} else if (r == -1) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
// no data
// continue to wait for recv
return;
} else {
ERROR("remote recv");
close_and_free_remote(EV_A_ remote);
close_and_free_server(EV_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(EV_A_ remote);
close_and_free_server(EV_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 (errno == EAGAIN || errno == EWOULDBLOCK) {
// no data, wait for send
server->buf->idx = 0;
ev_io_stop(EV_A_ & remote_recv_ctx->io);
ev_io_start(EV_A_ & server->send_ctx->io);
} else {
ERROR("remote_recv_send");
close_and_free_remote(EV_A_ remote);
close_and_free_server(EV_A_ server);
return;
}
} else if (s < server->buf->len) {
server->buf->len -= s;
server->buf->idx = s;
ev_io_stop(EV_A_ & remote_recv_ctx->io);
ev_io_start(EV_A_ & server->send_ctx->io);
}
// Disable TCP_NODELAY after the first response are sent
if (!remote->recv_ctx->connected && !no_delay) {
int opt = 0;
setsockopt(server->fd, SOL_TCP, TCP_NODELAY, &opt, sizeof(opt));
setsockopt(remote->fd, SOL_TCP, TCP_NODELAY, &opt, sizeof(opt));
}
remote->recv_ctx->connected = 1;
}
static void
remote_send_cb(EV_P_ ev_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(EV_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(EV_A_ remote);
close_and_free_server(EV_A_ server);
return;
}
}
// Make getpeername work
if (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;
ev_io_stop(EV_A_ & remote_send_ctx->io);
ev_io_start(EV_A_ & server->recv_ctx->io);
ev_io_start(EV_A_ & remote->recv_ctx->io);
return;
}
} else {
ERROR("getpeername");
// not connected
close_and_free_remote(EV_A_ remote);
close_and_free_server(EV_A_ server);
return;
}
}
if (remote->buf->len == 0) {
// close and free
close_and_free_remote(EV_A_ remote);
close_and_free_server(EV_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 (errno != EAGAIN && errno != EWOULDBLOCK) {
ERROR("remote_send_send");
// close and free
close_and_free_remote(EV_A_ remote);
close_and_free_server(EV_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;
ev_io_stop(EV_A_ & remote_send_ctx->io);
if (server != NULL) {
ev_io_start(EV_A_ & server->recv_ctx->io);
if (server->stage != STAGE_STREAM) {
server->stage = STAGE_STREAM;
ev_io_start(EV_A_ & remote->recv_ctx->io);
}
} else {
LOGE("invalid server");
close_and_free_remote(EV_A_ remote);
close_and_free_server(EV_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 = ss_malloc(sizeof(remote_t));
memset(remote, 0, sizeof(remote_t));
remote->recv_ctx = ss_malloc(sizeof(remote_ctx_t));
remote->send_ctx = ss_malloc(sizeof(remote_ctx_t));
remote->buf = 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;
ev_io_init(&remote->recv_ctx->io, remote_recv_cb, fd, EV_READ);
ev_io_init(&remote->send_ctx->io, remote_send_cb, fd, EV_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(EV_P_ remote_t *remote)
{
if (remote != NULL) {
ev_io_stop(EV_A_ & remote->send_ctx->io);
ev_io_stop(EV_A_ & remote->recv_ctx->io);
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 = ss_malloc(sizeof(server_t));
memset(server, 0, sizeof(server_t));
server->recv_ctx = ss_malloc(sizeof(server_ctx_t));
server->send_ctx = ss_malloc(sizeof(server_ctx_t));
server->buf = 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 = ss_malloc(sizeof(cipher_ctx_t));
server->d_ctx = ss_malloc(sizeof(cipher_ctx_t));
crypto->ctx_init(crypto->cipher, server->e_ctx, 1);
crypto->ctx_init(crypto->cipher, server->d_ctx, 0);
int timeout = max(MIN_TCP_IDLE_TIMEOUT, server->listen_ctx->timeout);
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,
timeout, timeout);
cork_dllist_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
cork_dllist_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(EV_P_ server_t *server)
{
if (server != NULL) {
if (server->query != NULL) {
server->query->server = NULL;
server->query = NULL;
}
ev_io_stop(EV_A_ & server->send_ctx->io);
ev_io_stop(EV_A_ & server->recv_ctx->io);
ev_timer_stop(EV_A_ & server->recv_ctx->watcher);
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(EV_P_ ev_signal *w, int revents)
{
if (revents & EV_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:
ev_signal_stop(EV_DEFAULT, &sigint_watcher);
ev_signal_stop(EV_DEFAULT, &sigterm_watcher);
#ifndef __MINGW32__
ev_signal_stop(EV_DEFAULT, &sigchld_watcher);
#else
ev_io_stop(EV_DEFAULT, &plugin_watcher.io);
#endif
ev_unloop(EV_A_ EVUNLOOP_ALL);
}
}
}
#ifdef __MINGW32__
static void
plugin_watcher_cb(EV_P_ ev_io *w, int revents)
{
char buf[1];
SOCKET fd = 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;
ev_signal_stop(EV_DEFAULT, &sigint_watcher);
ev_signal_stop(EV_DEFAULT, &sigterm_watcher);
ev_io_stop(EV_DEFAULT, &plugin_watcher.io);
ev_unloop(EV_A_ EVUNLOOP_ALL);
}
#endif
static void
accept_cb(EV_P_ ev_io *w, int revents)
{
listen_ctx_t *listener = (listen_ctx_t *)w;
int serverfd = 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);
close(serverfd);
return;
}
}
}
int opt = 1;
setsockopt(serverfd, SOL_TCP, TCP_NODELAY, &opt, sizeof(opt));
#ifdef SO_NOSIGPIPE
setsockopt(serverfd, SOL_SOCKET, SO_NOSIGPIPE, &opt, sizeof(opt));
#endif
if (tcp_incoming_sndbuf > 0) {
setsockopt(serverfd, SOL_SOCKET, SO_SNDBUF, &tcp_incoming_sndbuf, sizeof(int));
}
if (tcp_incoming_rcvbuf > 0) {
setsockopt(serverfd, SOL_SOCKET, SO_RCVBUF, &tcp_incoming_rcvbuf, sizeof(int));
}
setnonblocking(serverfd);
server_t *server = new_server(serverfd, listener);
ev_io_start(EV_A_ & server->recv_ctx->io);
ev_timer_start(EV_A_ & server->recv_ctx->watcher);
}
int
main(int argc, char **argv)
{
int i, c;
int pid_flags = 0;
int mptcp = 0;
int mtu = 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));
static struct option long_options[] = {
{ "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 }
};
opterr = 0;
USE_TTY();
while ((c = getopt_long(argc, argv, "f:s:p:l:k:t:m:b:c:i:d:a:n:huUv6A",
long_options, NULL)) != -1) {
switch (c) {
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:
mtu = atoi(optarg);
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 = 1;
LOGI("enable multipath TCP");
break;
case GETOPT_VAL_KEY:
key = optarg;
break;
case GETOPT_VAL_REUSE_PORT:
reuse_port = 1;
break;
case GETOPT_VAL_TCP_INCOMING_SNDBUF:
tcp_incoming_sndbuf = atoi(optarg);
break;
case GETOPT_VAL_TCP_INCOMING_RCVBUF:
tcp_incoming_rcvbuf = atoi(optarg);
break;
case GETOPT_VAL_TCP_OUTGOING_SNDBUF:
tcp_outgoing_sndbuf = atoi(optarg);
break;
case GETOPT_VAL_TCP_OUTGOING_RCVBUF:
tcp_outgoing_rcvbuf = atoi(optarg);
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':
server_port = optarg;
break;
case GETOPT_VAL_PASSWORD:
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':
nameservers = optarg;
break;
case 'a':
user = optarg;
break;
#ifdef HAVE_SETRLIMIT
case 'n':
nofile = atoi(optarg);
break;
#endif
case 'u':
mode = TCP_AND_UDP;
break;
case 'U':
mode = UDP_ONLY;
break;
case 'v':
verbose = 1;
break;
case GETOPT_VAL_HELP:
case 'h':
usage();
exit(EXIT_SUCCESS);
case '6':
ipv6first = 1;
break;
case 'A':
FATAL("One time auth has been deprecated. Try AEAD ciphers instead.");
break;
case '?':
// The option character is not recognized.
LOGE("Unrecognized option: %s", optarg);
opterr = 1;
break;
}
}
if (opterr) {
usage();
exit(EXIT_FAILURE);
}
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 == TCP_ONLY) {
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 == 0) {
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 = "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 = "::1";
} else {
plugin_host = "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 = "chacha20-ietf-poly1305";
}
if (timeout == NULL) {
timeout = "60";
}
#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
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);
#ifndef __MINGW32__
ev_signal_init(&sigchld_watcher, signal_cb, SIGCHLD);
ev_signal_start(EV_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 ev_loop *loop = EV_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 = 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;
ev_io_init(&plugin_watcher.io, plugin_watcher_cb, fd, EV_READ);
ev_io_start(EV_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 = 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
listen_ctx_t listen_ctx_list[server_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 = atoi(timeout);
listen_ctx->fd = listenfd;
listen_ctx->iface = iface;
listen_ctx->loop = loop;
ev_io_init(&listen_ctx->io, accept_cb, listenfd, EV_READ);
ev_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, atoi(timeout), iface);
if (err == -1)
continue;
num_listen_ctx++;
}
if (num_listen_ctx == 0) {
FATAL("failed to listen on any address");
}
}
#ifndef __MINGW32__
if (manager_addr != NULL) {
ev_timer_init(&stat_update_watcher, stat_update_cb, UPDATE_INTERVAL, UPDATE_INTERVAL);
ev_timer_start(EV_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
cork_dllist_init(&connections);
// start ev loop
ev_run(loop, 0);
if (verbose) {
LOGI("closed gracefully");
}
#ifndef __MINGW32__
if (manager_addr != NULL) {
ev_timer_stop(EV_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) {
ev_io_stop(loop, &listen_ctx->io);
close(listen_ctx->fd);
}
if (plugin != NULL)
break;
}
if (mode != UDP_ONLY) {
free_connections(loop);
}
if (mode != TCP_ONLY) {
free_udprelay();
}
#ifdef __MINGW32__
if (plugin_watcher.valid) {
closesocket(plugin_watcher.fd);
}
winsock_cleanup();
#endif
return ret_val;
}