mirror of
https://github.com/shadowsocks/shadowsocks-libev.git
synced 2026-10-22 23:56:01 +00:00
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.
2669 lines
83 KiB
C
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;
|
|
}
|