mirror of
https://github.com/shadowsocks/shadowsocks-libev.git
synced 2026-10-20 21:43:41 +00:00
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
2432 lines
74 KiB
C
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;
|
|
}
|