You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1105 lines
32 KiB
1105 lines
32 KiB
/*
|
|
* relay.c - Define TCP relay's buffers and callbacks
|
|
*
|
|
* 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/>.
|
|
*/
|
|
|
|
#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>
|
|
#ifndef __MINGW32__
|
|
#include <arpa/inet.h>
|
|
#include <errno.h>
|
|
#include <netdb.h>
|
|
#include <netinet/in.h>
|
|
#include <pthread.h>
|
|
#include <sys/un.h>
|
|
#endif
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#endif
|
|
|
|
#if defined(HAVE_SYS_IOCTL_H) && defined(HAVE_NET_IF_H) && defined(__linux__)
|
|
#include <net/if.h>
|
|
#include <sys/ioctl.h>
|
|
#define SET_INTERFACE
|
|
#endif
|
|
|
|
#include <libcork/core.h>
|
|
|
|
#include "utils.h"
|
|
#include "netutils.h"
|
|
#include "winsock.h"
|
|
#include "http.h"
|
|
#include "tls.h"
|
|
#include "acl.h"
|
|
#include "plugin.h"
|
|
|
|
#include "relay.h"
|
|
|
|
extern int acl;
|
|
extern int verbose;
|
|
extern int remote_dns;
|
|
extern int ipv6first;
|
|
|
|
#ifdef __ANDROID__
|
|
extern int vpn;
|
|
#endif
|
|
|
|
#ifdef MODULE_REMOTE
|
|
static int remote_conn = 0;
|
|
static int server_conn = 0;
|
|
static char *manager_addr = NULL;
|
|
static struct cork_dllist listeners;
|
|
extern uint64_t tx, rx;
|
|
#ifndef __MINGW32__
|
|
ev_timer stat_watcher;
|
|
#endif
|
|
#endif
|
|
static struct cork_dllist connections;
|
|
|
|
struct ev_signal sigint_watcher;
|
|
struct ev_signal sigterm_watcher;
|
|
#ifndef __MINGW32__
|
|
struct ev_signal sigchld_watcher;
|
|
#endif
|
|
|
|
static int ret_val = 0;
|
|
|
|
#ifdef MODULE_LOCAL
|
|
remote_t *
|
|
new_remote(server_t *server)
|
|
{
|
|
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->recv_ctx->remote = remote;
|
|
remote->recv_ctx->connected = 0;
|
|
remote->send_ctx->remote = remote;
|
|
remote->send_ctx->connected = 0;
|
|
|
|
server->remote = remote;
|
|
remote->server = server;
|
|
|
|
return remote;
|
|
}
|
|
|
|
server_t *
|
|
new_server(int fd)
|
|
{
|
|
server_t *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));
|
|
server->abuf = ss_malloc(sizeof(buffer_t));
|
|
balloc(server->buf, SOCKET_BUF_SIZE);
|
|
balloc(server->abuf, SOCKET_BUF_SIZE);
|
|
memset(server->recv_ctx, 0, sizeof(server_ctx_t));
|
|
memset(server->send_ctx, 0, sizeof(server_ctx_t));
|
|
server->stage = STAGE_INIT;
|
|
server->fd = fd;
|
|
server->recv_ctx->server = server;
|
|
server->recv_ctx->connected = 0;
|
|
server->send_ctx->server = server;
|
|
server->send_ctx->connected = 0;
|
|
|
|
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);
|
|
|
|
cork_dllist_add(&connections, &server->entries);
|
|
|
|
return server;
|
|
}
|
|
|
|
void
|
|
remote_timeout_cb(EV_P_ ev_timer *watcher, int revents)
|
|
{
|
|
remote_ctx_t *remote_ctx
|
|
= cork_container_of(watcher, remote_ctx_t, watcher);
|
|
|
|
remote_t *remote = remote_ctx->remote;
|
|
server_t *server = remote->server;
|
|
|
|
if (verbose) {
|
|
LOGI("TCP connection timed out");
|
|
}
|
|
|
|
close_and_free_remote(EV_A_ remote);
|
|
close_and_free_server(EV_A_ server);
|
|
}
|
|
|
|
int
|
|
create_remote(EV_P_ remote_t *remote, buffer_t *buf,
|
|
ssocks_addr_t *destaddr, int acl_enabled)
|
|
{
|
|
server_t *server = remote->server;
|
|
listen_ctx_t *listen_ctx = server->listen_ctx;
|
|
|
|
if (buf != NULL && remote_dns && !destaddr->dname) {
|
|
switch (port_service(destaddr->port)) {
|
|
case PORT_HTTP_SERVICE: {
|
|
destaddr->dname_len =
|
|
http_protocol->parse_packet(buf->data, buf->len, &destaddr->dname);
|
|
} break;
|
|
case PORT_HTTPS_SERVICE: {
|
|
destaddr->dname_len =
|
|
tls_protocol->parse_packet(buf->data, buf->len, &destaddr->dname);
|
|
} break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (destaddr->dname_len == -1) {
|
|
return -1;
|
|
} else if (destaddr->dname_len <= 0 ||
|
|
!validate_hostname(destaddr->dname, destaddr->dname_len))
|
|
{
|
|
destaddr->dname = NULL;
|
|
}
|
|
|
|
dname_t dname = { destaddr->dname_len, destaddr->dname };
|
|
|
|
int direct = acl_enabled ?
|
|
destaddr->dname ? search_acl(ACL_ATYP_DOMAIN, &dname, ACL_UNSPCLIST):
|
|
destaddr->addr ? search_acl(ACL_ATYP_IP, destaddr->addr, ACL_UNSPCLIST):
|
|
0 : 0;
|
|
|
|
if (verbose) {
|
|
LOGI("%s %s", direct ? "bypassing" : "connecting to",
|
|
destaddr->dname ? hostname_readable(destaddr->dname, destaddr->port)
|
|
: sockaddr_readable("%a:%p", destaddr->addr));
|
|
}
|
|
|
|
remote->direct = direct;
|
|
|
|
if (!remote->direct)
|
|
bailed: {
|
|
int remote_idx = acl_enabled ?
|
|
destaddr->dname ? search_acl(ACL_ATYP_DOMAIN, &dname, ACL_DELEGATION):
|
|
destaddr->addr ? search_acl(ACL_ATYP_IP, destaddr->addr, ACL_DELEGATION):
|
|
-1 : -1;
|
|
if (remote_idx < 0)
|
|
remote_idx = rand() % listen_ctx->remote_num;
|
|
create_ssocks_header(server->abuf, destaddr);
|
|
return init_remote(EV_A_ remote, listen_ctx->remotes[remote_idx]);
|
|
} else {
|
|
if (destaddr->dname && !destaddr->addr &&
|
|
(destaddr->addr = ss_calloc(1, sizeof(*destaddr->addr))) &&
|
|
get_sockaddr_r(destaddr->dname, NULL,
|
|
destaddr->port, destaddr->addr, 1, ipv6first) == -1)
|
|
{
|
|
remote->direct = 0;
|
|
LOGE("failed to resolve %s", destaddr->dname);
|
|
goto bailed;
|
|
}
|
|
|
|
return init_remote(EV_A_ remote, &(remote_cnf_t) { .addr = destaddr->addr, .iface = listen_ctx->iface });
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
init_remote(EV_P_ remote_t *remote, remote_cnf_t *conf)
|
|
{
|
|
server_t *server = remote->server;
|
|
listen_ctx_t *listen_ctx = server->listen_ctx;
|
|
struct sockaddr_storage *remote_addr = conf->addr;
|
|
//cara rm
|
|
LOGI("destaddr %s", sockaddr_readable("%a:%p", remote_addr));
|
|
|
|
int remotefd = socket(remote_addr->ss_family, SOCK_STREAM, IPPROTO_TCP);
|
|
if (remotefd == -1) {
|
|
ERROR("socket");
|
|
return -1;
|
|
}
|
|
|
|
int opt = 1;
|
|
setsockopt(remotefd, SOL_TCP, TCP_NODELAY, &opt, sizeof(opt));
|
|
#ifdef SO_NOSIGPIPE
|
|
setsockopt(remotefd, SOL_SOCKET, SO_NOSIGPIPE, &opt, sizeof(opt));
|
|
#endif
|
|
|
|
// Enable TCP keepalive
|
|
setsockopt(remotefd, SOL_SOCKET, SO_KEEPALIVE, (void *)&opt, sizeof(opt));
|
|
|
|
// Set non blocking
|
|
setnonblocking(remotefd);
|
|
|
|
if (listen_ctx->tos >= 0) {
|
|
if (setsockopt(remotefd, IPPROTO_IP, IP_TOS,
|
|
&listen_ctx->tos, sizeof(listen_ctx->tos)) != 0) {
|
|
ERROR("setsockopt IP_TOS");
|
|
}
|
|
}
|
|
|
|
// Enable MPTCP
|
|
if (listen_ctx->mptcp) {
|
|
set_mptcp(remotefd);
|
|
}
|
|
|
|
#ifdef __ANDROID__
|
|
if (vpn
|
|
&& !is_addr_loopback((struct sockaddr *)remote_addr)
|
|
&& protect_socket(remotefd) == -1)
|
|
{
|
|
ERROR("protect_socket");
|
|
close(remotefd);
|
|
return -1;
|
|
}
|
|
#endif
|
|
|
|
if (!remote->direct && conf->crypto) {
|
|
crypto_t *crypto = conf->crypto;
|
|
remote->crypto = crypto;
|
|
remote->e_ctx = ss_malloc(sizeof(cipher_ctx_t));
|
|
remote->d_ctx = ss_malloc(sizeof(cipher_ctx_t));
|
|
|
|
crypto->ctx_init(crypto->cipher, remote->e_ctx, 1);
|
|
crypto->ctx_init(crypto->cipher, remote->d_ctx, 0);
|
|
}
|
|
|
|
remote->fd = remotefd;
|
|
remote->addr = remote_addr;
|
|
|
|
ev_io_init(&remote->recv_ctx->io, remote_recv_cb, remotefd, EV_READ);
|
|
ev_io_init(&remote->send_ctx->io, remote_send_cb, remotefd, EV_WRITE);
|
|
ev_timer_init(&remote->send_ctx->watcher, remote_timeout_cb,
|
|
min(MAX_CONNECT_TIMEOUT, listen_ctx->timeout), 0);
|
|
|
|
return 0;
|
|
}
|
|
|
|
#elif defined MODULE_REMOTE
|
|
remote_t *
|
|
new_remote(int fd)
|
|
{
|
|
if (verbose)
|
|
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;
|
|
}
|
|
|
|
server_t *
|
|
new_server(int fd, listen_ctx_t *listener)
|
|
{
|
|
if (verbose)
|
|
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->listen_ctx = listener;
|
|
server->remote = NULL;
|
|
|
|
crypto_t *crypto = listener->crypto;
|
|
server->crypto = crypto;
|
|
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 request_timeout = min(MAX_REQUEST_TIMEOUT, listener->timeout)
|
|
+ rand() % MAX_REQUEST_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,
|
|
request_timeout, 0);
|
|
|
|
cork_dllist_add(&connections, &server->entries);
|
|
|
|
return server;
|
|
}
|
|
|
|
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 timed out");
|
|
}
|
|
|
|
close_and_free_remote(EV_A_ remote);
|
|
close_and_free_server(EV_A_ server);
|
|
}
|
|
|
|
#ifndef __MINGW32__
|
|
static void
|
|
stat_update_cb(EV_P_ ev_timer *watcher, int revents)
|
|
{
|
|
listen_ctx_t *listen_ctx
|
|
= cork_container_of(watcher, listen_ctx_t, stat_watcher);
|
|
struct sockaddr_storage addr;
|
|
|
|
if (getsockname(listen_ctx->fd,
|
|
(struct sockaddr *)&addr, NULL) != 0) {
|
|
return;
|
|
}
|
|
|
|
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 "}", sockaddr_readable("%p", &addr), 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", sockaddr_readable("%p", &addr));
|
|
|
|
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, 1, 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_listeners(struct ev_loop *loop)
|
|
{
|
|
listen_ctx_t *listener = NULL;
|
|
struct cork_dllist_item *curr, *next;
|
|
cork_dllist_foreach(&listeners, curr, next,
|
|
listen_ctx_t, listener, entries) {
|
|
if (listener != NULL) {
|
|
#ifndef __MINGW32__
|
|
if (manager_addr != NULL)
|
|
ev_timer_stop(EV_A_ & stat_watcher);
|
|
#endif
|
|
ev_io_stop(EV_A_ & listener->io);
|
|
close(listener->fd);
|
|
}
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
void
|
|
free_remote(remote_t *remote)
|
|
{
|
|
#ifdef MODULE_REMOTE
|
|
if (verbose)
|
|
remote_conn--;
|
|
#elif defined MODULE_LOCAL
|
|
if (remote->e_ctx != NULL) {
|
|
remote->crypto->ctx_release(remote->e_ctx);
|
|
ss_free(remote->e_ctx);
|
|
}
|
|
if (remote->d_ctx != NULL) {
|
|
remote->crypto->ctx_release(remote->d_ctx);
|
|
ss_free(remote->d_ctx);
|
|
}
|
|
#endif
|
|
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);
|
|
}
|
|
|
|
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);
|
|
#ifdef MODULE_LOCAL
|
|
ev_timer_stop(EV_A_ & remote->send_ctx->watcher);
|
|
#endif
|
|
close(remote->fd);
|
|
free_remote(remote);
|
|
}
|
|
}
|
|
|
|
void
|
|
free_server(server_t *server)
|
|
{
|
|
#ifdef MODULE_REMOTE
|
|
if (verbose) {
|
|
server_conn--;
|
|
LOGI("current server connection: %d", server_conn);
|
|
}
|
|
if (server->e_ctx != NULL) {
|
|
server->crypto->ctx_release(server->e_ctx);
|
|
ss_free(server->e_ctx);
|
|
}
|
|
if (server->d_ctx != NULL) {
|
|
server->crypto->ctx_release(server->d_ctx);
|
|
ss_free(server->d_ctx);
|
|
}
|
|
#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
|
|
#endif
|
|
cork_dllist_remove(&server->entries);
|
|
|
|
if (server->remote != NULL) {
|
|
server->remote->server = NULL;
|
|
}
|
|
if (server->buf != NULL) {
|
|
bfree(server->buf);
|
|
ss_free(server->buf);
|
|
}
|
|
if (server->abuf != NULL) {
|
|
bfree(server->abuf);
|
|
ss_free(server->abuf);
|
|
}
|
|
ss_free(server->recv_ctx);
|
|
ss_free(server->send_ctx);
|
|
ss_free(server);
|
|
}
|
|
|
|
void
|
|
close_and_free_server(EV_P_ server_t *server)
|
|
{
|
|
if (server != NULL) {
|
|
ev_io_stop(EV_A_ & server->send_ctx->io);
|
|
ev_io_stop(EV_A_ & server->recv_ctx->io);
|
|
#ifdef MODULE_REMOTE
|
|
ev_timer_stop(EV_A_ & server->recv_ctx->watcher);
|
|
#endif
|
|
close(server->fd);
|
|
free_server(server);
|
|
}
|
|
}
|
|
|
|
void
|
|
free_connections(struct ev_loop *loop)
|
|
{
|
|
server_t *server = NULL;
|
|
struct cork_dllist_item *curr, *next;
|
|
cork_dllist_foreach(&connections, curr, next,
|
|
server_t, server, entries) {
|
|
if (server != NULL) {
|
|
close_and_free_server(loop, server);
|
|
close_and_free_remote(loop, server->remote);
|
|
}
|
|
}
|
|
}
|
|
|
|
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);
|
|
#endif
|
|
ev_unloop(EV_A_ EVUNLOOP_ALL);
|
|
}
|
|
}
|
|
}
|
|
|
|
int
|
|
start_relay(jconf_t *conf,
|
|
ss_callback_t callback, void *data)
|
|
{
|
|
int plugin_enabled = 0;
|
|
|
|
if (!(conf->remotes != NULL &&
|
|
conf->remote_num > 0)) {
|
|
LOGE("at least one server should be specified");
|
|
return -1;
|
|
}
|
|
|
|
if (conf->log) {
|
|
USE_LOGFILE(conf->log);
|
|
LOGI("enabled %slogging %s", conf->verbose ? "verbose " : "", conf->log);
|
|
}
|
|
|
|
if (conf->mtu > 0) {
|
|
LOGI("setting MTU to %d", conf->mtu);
|
|
}
|
|
|
|
if (conf->mptcp) {
|
|
LOGI("enabled multipath TCP");
|
|
}
|
|
|
|
if (conf->no_delay) {
|
|
LOGI("enabled TCP no-delay");
|
|
}
|
|
|
|
if (conf->ipv6_first) {
|
|
LOGI("prioritized IPv6 addresses in domain resolution");
|
|
}
|
|
|
|
|
|
#ifndef MODULE_TUNNEL
|
|
if (conf->acl != NULL) {
|
|
LOGI("initializing acl...");
|
|
acl = !init_acl(conf);
|
|
}
|
|
#endif
|
|
|
|
#ifdef HAVE_SETRLIMIT
|
|
/*
|
|
* No need to check the return value here.
|
|
* We will show users an error message if setrlimit(2) fails.
|
|
*/
|
|
if (conf->nofile > 1024) {
|
|
if (conf->verbose) {
|
|
LOGI("setting NOFILE to %d", conf->nofile);
|
|
}
|
|
set_nofile(conf->nofile);
|
|
}
|
|
#endif
|
|
|
|
if (conf->fast_open) {
|
|
#ifdef TCP_FASTOPEN
|
|
LOGI("using tcp fast open");
|
|
#else
|
|
LOGE("tcp fast open is not supported by this environment");
|
|
conf->fast_open = 0;
|
|
#endif
|
|
}
|
|
|
|
#ifdef __MINGW32__
|
|
winsock_init();
|
|
#endif
|
|
|
|
#ifndef __MINGW32__
|
|
// ignore SIGPIPE
|
|
signal(SIGPIPE, SIG_IGN);
|
|
signal(SIGABRT, SIG_IGN);
|
|
#endif
|
|
|
|
// Setup signal handler
|
|
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 proxy context
|
|
struct ev_loop *loop = EV_DEFAULT;
|
|
#ifdef MODULE_LOCAL
|
|
|
|
#ifndef HAVE_LAUNCHD
|
|
if (conf->local_port == NULL) {
|
|
conf->local_port = "0";
|
|
LOGE("warning: random local port will be assigned");
|
|
}
|
|
#endif
|
|
|
|
if (!conf->remote_dns) {
|
|
LOGI("disabled remote domain resolution");
|
|
}
|
|
|
|
listen_ctx_t listen_ctx = {
|
|
.mtu = conf->mtu,
|
|
.mptcp = conf->mptcp,
|
|
.reuse_port = conf->reuse_port,
|
|
.remote_num = conf->remote_num,
|
|
.remotes = ss_calloc(conf->remote_num, sizeof(remote_cnf_t *)),
|
|
.timeout = atoi(conf->timeout),
|
|
};
|
|
|
|
#ifdef MODULE_TUNNEL
|
|
ss_addr_t *tunnel_addr = &conf->tunnel_addr;
|
|
if (tunnel_addr->host == NULL ||
|
|
tunnel_addr->port == NULL) {
|
|
FATAL("tunnel address either undefined or invalid");
|
|
}
|
|
|
|
ssocks_addr_t *destaddr = &listen_ctx.destaddr;
|
|
destaddr->addr = ss_calloc(1, sizeof(struct sockaddr_storage));
|
|
if (get_sockaddr(tunnel_addr->host, tunnel_addr->port,
|
|
destaddr->addr, !conf->remote_dns, conf->ipv6_first) == -1)
|
|
{
|
|
destaddr->dname = tunnel_addr->host;
|
|
destaddr->dname_len = strlen(tunnel_addr->host);
|
|
destaddr->port = htons(atoi(tunnel_addr->port));
|
|
}
|
|
#endif
|
|
port_service_init();
|
|
|
|
for (int i = 0; i < conf->remote_num; i++) {
|
|
ss_remote_t *r = conf->remotes[i];
|
|
|
|
char *host = r->addr,
|
|
*port = elvis(r->port, conf->remote_port),
|
|
*password = elvis(r->password, conf->password),
|
|
*key = elvis(r->key, conf->key),
|
|
*method = elvis(r->method, conf->method),
|
|
*iface = elvis(r->iface, conf->iface),
|
|
*plugin = elvis(r->plugin, conf->plugin),
|
|
*plugin_opts
|
|
= elvis(r->plugin_opts, conf->plugin_opts);
|
|
|
|
if (host == NULL || port == NULL ||
|
|
(password == NULL && key == NULL))
|
|
{
|
|
usage();
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
LOGI("[%d/%d] server %s %s:%s",
|
|
i + 1, conf->remote_num, elvis(r->tag, "-"), host, port);
|
|
|
|
LOGI("initializing ciphers... %s", method);
|
|
crypto_t *crypto = crypto_init(password, key, method);
|
|
if (crypto == NULL)
|
|
FATAL("failed to initialize ciphers");
|
|
|
|
struct sockaddr_storage *storage
|
|
= ss_calloc(1, sizeof(struct sockaddr_storage));
|
|
if (get_sockaddr(host, port, storage, 1, conf->ipv6_first) == -1) {
|
|
FATAL("failed to resolve %s", host);
|
|
}
|
|
|
|
if (plugin != NULL) {
|
|
if (!plugin_enabled) {
|
|
init_plugin(MODE_CLIENT);
|
|
plugin_enabled = 1;
|
|
}
|
|
|
|
uint16_t plugin_port = get_local_port();
|
|
switch (storage->ss_family) {
|
|
case AF_INET: {
|
|
*(struct sockaddr_in *)storage = (struct sockaddr_in) {
|
|
.sin_addr =
|
|
(struct in_addr) { htonl(INADDR_LOOPBACK) },
|
|
.sin_port = plugin_port
|
|
};
|
|
} break;
|
|
case AF_INET6: {
|
|
*(struct sockaddr_in6 *)storage = (struct sockaddr_in6) {
|
|
.sin6_addr = in6addr_loopback,
|
|
.sin6_port = plugin_port
|
|
};
|
|
} break;
|
|
}
|
|
|
|
if (plugin_port == 0)
|
|
FATAL("failed to find a free port");
|
|
|
|
LOGI("plugin \"%s\" enabled", plugin);
|
|
|
|
int err = start_plugin(plugin, plugin_opts, // user-defined plugin options
|
|
host, port, // user-defined destination address
|
|
sockaddr_readable("%a", storage),
|
|
sockaddr_readable("%p", storage));
|
|
if (err)
|
|
FATAL("failed to start plugin %s", plugin);
|
|
}
|
|
|
|
remote_cnf_t *remote_cnf
|
|
= ss_calloc(1, sizeof(*remote_cnf));
|
|
remote_cnf->iface = iface;
|
|
remote_cnf->addr = storage;
|
|
remote_cnf->crypto = crypto;
|
|
|
|
listen_ctx.remotes[i] = remote_cnf;
|
|
}
|
|
|
|
ss_dscp_t **dscp = conf->dscp;
|
|
char *local_addr = conf->local_addr,
|
|
*local_port = conf->local_port;
|
|
|
|
listen_ctx_t listen_ctx_current = listen_ctx;
|
|
do {
|
|
struct sockaddr_storage *storage = &(struct sockaddr_storage) {};
|
|
if (get_sockaddr(local_addr, local_port,
|
|
storage, 1, conf->ipv6_first) == -1)
|
|
{
|
|
FATAL("failed to resolve %s", local_addr);
|
|
}
|
|
|
|
if (listen_ctx_current.tos) {
|
|
LOGI("listening on %s (TOS 0x%x)",
|
|
sockaddr_readable("%a:%p", storage), listen_ctx_current.tos);
|
|
} else {
|
|
LOGI("listening on %s",
|
|
sockaddr_readable("%a:%p", storage));
|
|
}
|
|
|
|
int socket = -1, socket_u = -1;
|
|
if (conf->mode != UDP_ONLY) {
|
|
// Setup socket
|
|
socket =
|
|
#ifdef HAVE_LAUNCHD
|
|
launch_or_create(storage, &listen_ctx_current);
|
|
#else
|
|
bind_and_listen(storage, IPPROTO_TCP, &listen_ctx_current);
|
|
#endif
|
|
if (socket != -1) {
|
|
if (conf->fast_open)
|
|
set_fastopen_passive(socket);
|
|
ev_io_init(&listen_ctx_current.io, accept_cb, listen_ctx_current.fd, EV_READ);
|
|
ev_io_start(EV_A_ & listen_ctx_current.io);
|
|
}
|
|
}
|
|
|
|
// Setup UDP
|
|
if (conf->mode != TCP_ONLY) {
|
|
listen_ctx_t listen_ctx_dgram = listen_ctx_current;
|
|
int socket_u = bind_and_listen(storage, IPPROTO_UDP, &listen_ctx_dgram);
|
|
if ((listen_ctx_dgram.fd = socket_u) != -1) {
|
|
init_udprelay(EV_A_ & listen_ctx_dgram);
|
|
}
|
|
}
|
|
|
|
if (callback != NULL) {
|
|
callback(socket, socket_u, data);
|
|
}
|
|
|
|
if (conf->mode == UDP_ONLY) {
|
|
LOGI("TCP relay disabled");
|
|
}
|
|
|
|
// Handle additional TOS/DSCP listening ports
|
|
if (*dscp != NULL) {
|
|
listen_ctx_current = listen_ctx;
|
|
local_port = (*dscp)->port;
|
|
listen_ctx_current.tos = (*dscp)->dscp << 2;
|
|
}
|
|
} while (*(dscp++) != NULL);
|
|
|
|
#elif MODULE_REMOTE
|
|
resolv_init(EV_A_ conf->nameserver, conf->ipv6_first);
|
|
if (conf->nameserver != NULL)
|
|
LOGI("using nameserver: %s", conf->nameserver);
|
|
port_service_init();
|
|
|
|
cork_dllist_init(&listeners);
|
|
|
|
for (int i = 0; i < conf->remote_num; i++) {
|
|
ss_remote_t *r = conf->remotes[i];
|
|
char *host = r->addr,
|
|
*port = elvis(r->port, conf->remote_port),
|
|
*password = elvis(r->password, conf->password),
|
|
*key = elvis(r->key, conf->key),
|
|
*method = elvis(r->method, conf->method),
|
|
*iface = elvis(r->iface, conf->iface),
|
|
*plugin = elvis(r->plugin, conf->plugin),
|
|
*plugin_opts
|
|
= elvis(r->plugin_opts, conf->plugin_opts);
|
|
|
|
if (port == NULL || method == NULL ||
|
|
(password == NULL && key == NULL))
|
|
{
|
|
usage();
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
LOGI("[%d/%d] listening on %s:%s", i + 1, conf->remote_num, host, port);
|
|
|
|
LOGI("initializing ciphers... %s", method);
|
|
crypto_t *crypto = crypto_init(password, key, method);
|
|
if (crypto == NULL)
|
|
FATAL("failed to initialize ciphers");
|
|
|
|
/**
|
|
* "If node(host) is NULL, then the network address
|
|
* will be set to the loopback interface address," meaning that
|
|
* by default the server will bind to `lo' instead of 0.0.0.0.
|
|
*
|
|
* On Linux, when 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,
|
|
* unless `bindv6only' is enabled.
|
|
*/
|
|
struct sockaddr_storage *storage =
|
|
ss_calloc(1, sizeof(struct sockaddr_storage));
|
|
if (get_sockaddr(host, port, storage, 1, conf->ipv6_first) == -1) {
|
|
FATAL("failed to resolve %s", host);
|
|
}
|
|
|
|
if (plugin != NULL) {
|
|
if (!plugin_enabled) {
|
|
init_plugin(MODE_SERVER);
|
|
plugin_enabled = 1;
|
|
}
|
|
|
|
uint16_t plugin_port = get_local_port();
|
|
switch (storage->ss_family) {
|
|
case AF_INET: {
|
|
*(struct sockaddr_in *)storage = (struct sockaddr_in) {
|
|
.sin_addr =
|
|
(struct in_addr) { htonl(INADDR_LOOPBACK) },
|
|
.sin_port = plugin_port
|
|
};
|
|
} break;
|
|
case AF_INET6: {
|
|
*(struct sockaddr_in6 *)storage = (struct sockaddr_in6) {
|
|
.sin6_addr = in6addr_loopback,
|
|
.sin6_port = plugin_port
|
|
};
|
|
} break;
|
|
}
|
|
|
|
if (plugin_port == 0)
|
|
FATAL("failed to find a free port");
|
|
|
|
LOGI("plugin \"%s\" enabled", plugin);
|
|
|
|
int err = start_plugin(plugin, plugin_opts, // user-defined plugin options
|
|
sockaddr_readable("%a", storage),
|
|
sockaddr_readable("%p", storage), // plugin destination override
|
|
host, port);
|
|
if (err)
|
|
FATAL("failed to start the plugin");
|
|
}
|
|
|
|
listen_ctx_t listen_ctx = {
|
|
.iface = iface,
|
|
.addr = storage,
|
|
.crypto = crypto,
|
|
.timeout = atoi(conf->timeout),
|
|
.loop = loop
|
|
};
|
|
|
|
#ifndef __MINGW32__
|
|
manager_addr = conf->manager_addr;
|
|
if (conf->manager_addr != NULL) {
|
|
ev_timer_init(&listen_ctx.stat_watcher, stat_update_cb,
|
|
UPDATE_INTERVAL, UPDATE_INTERVAL);
|
|
ev_timer_start(EV_DEFAULT, &listen_ctx.stat_watcher);
|
|
}
|
|
#endif
|
|
|
|
int socket = -1, socket_u = -1;
|
|
if (conf->mode != UDP_ONLY) {
|
|
int socket = bind_and_listen(storage, IPPROTO_TCP, &listen_ctx);
|
|
if (socket != -1) {
|
|
if (conf->fast_open)
|
|
set_fastopen_passive(socket);
|
|
ev_io_init(&listen_ctx.io, accept_cb, listen_ctx.fd, EV_READ);
|
|
ev_io_start(EV_A_ & listen_ctx.io);
|
|
cork_dllist_add(&listeners, &listen_ctx.entries);
|
|
}
|
|
}
|
|
|
|
// Setup UDP
|
|
if (conf->mode != TCP_ONLY) {
|
|
listen_ctx_t listen_ctx_dgram = listen_ctx;
|
|
int socket_u = bind_and_listen(storage, IPPROTO_UDP, &listen_ctx_dgram);
|
|
if ((listen_ctx_dgram.fd = socket_u) != -1) {
|
|
init_udprelay(EV_A_ & listen_ctx_dgram);
|
|
}
|
|
}
|
|
|
|
if (callback != NULL) {
|
|
callback(socket, socket_u, data);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
// Init connections
|
|
cork_dllist_init(&connections);
|
|
|
|
// start ev loop
|
|
ev_run(EV_A_ 0);
|
|
|
|
if (conf->verbose) {
|
|
LOGI("closed gracefully");
|
|
}
|
|
|
|
#ifdef MODULE_LOCAL
|
|
if (conf->mode != UDP_ONLY) {
|
|
ev_io_stop(EV_A_ & listen_ctx.io);
|
|
free_connections(loop);
|
|
}
|
|
|
|
if (listen_ctx.remotes != NULL) {
|
|
for (int i = 0; i < listen_ctx.remote_num; i++) {
|
|
remote_cnf_t *remote_cnf = listen_ctx.remotes[i];
|
|
if (remote_cnf != NULL) {
|
|
if (remote_cnf->iface)
|
|
ss_free(remote_cnf->iface);
|
|
// TODO: hey yo! free() the whole struct, pls
|
|
if (remote_cnf->crypto)
|
|
ss_free(remote_cnf->crypto);
|
|
if (remote_cnf->addr)
|
|
ss_free(remote_cnf->addr);
|
|
ss_free(remote_cnf);
|
|
}
|
|
}
|
|
ss_free(listen_ctx.remotes);
|
|
}
|
|
|
|
#elif MODULE_REMOTE
|
|
#ifndef __MINGW32__
|
|
if (conf->manager_addr) {
|
|
ev_timer_stop(EV_A_ & stat_watcher);
|
|
}
|
|
#endif
|
|
resolv_shutdown(loop);
|
|
if (conf->mode != UDP_ONLY) {
|
|
free_listeners(loop);
|
|
free_connections(loop);
|
|
}
|
|
#endif
|
|
|
|
if (conf->mode != TCP_ONLY) {
|
|
free_udprelay(loop);
|
|
}
|
|
|
|
if (plugin_enabled)
|
|
stop_plugin();
|
|
|
|
if (conf->log)
|
|
CLOSE_LOGFILE();
|
|
|
|
#ifdef __MINGW32__
|
|
winsock_cleanup();
|
|
#endif
|
|
|
|
return ret_val;
|
|
}
|