mirror of
https://github.com/shadowsocks/shadowsocks-libev.git
synced 2026-10-28 10:44:43 +08:00
* Make the event loop, UDP relay and programs compile as C++
Cast allocator results and void* callback data explicitly, cast string
literals stored in char * variables, replace compound literals in
ss_event.c with zeroing plus member assignment, and give ss-server's
listener array its MAX_REMOTE_NUM bound instead of a VLA.
Where a goto CLEAN_UP jumps over an initialised declaration (C++
rejects that), split the declaration from its initialisation in place.
The variables were already indeterminate at the label in C, and the
cleanup code does not read them. Checked for every MODULE_* variant of
udprelay.c, plus LIB_ONLY for local.c. Still valid C.
* Compile the event loop, UDP relay and programs as C++
Rename utils, ss_event, udprelay, winsock, local, server, tunnel, redir
and manager from .c to .cpp. Inside the moved files only the
\snippet{doc} file references and header comments change; snippet
references in doc/*.md, tests/test_cli_docs.py, the build files and
CONTRIBUTION.md follow the new names.
Also cast the Windows-only string literal returned by
get_default_conf(), found by the zig x86_64-windows-gnu build.
Every module of src/ except the unbuilt android.c and the vendored
blake3 is now compiled as C++20. The new .cpp files join the no-heap
ratchet baseline.
* Add the two casts ss-redir needs as C++
ss-redir only builds on Linux, so the macOS conversion checks never
compiled it: getsockname() wants a struct sockaddr *, and memcpy's
void * result needs a cast.
1620 lines
49 KiB
C++
1620 lines
49 KiB
C++
/*
|
|
* udprelay.cpp - Setup UDP relay for both client and server
|
|
*
|
|
* 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>
|
|
#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 "core.h"
|
|
|
|
#include "utils.h"
|
|
#include "netutils.h"
|
|
#include "cache.h"
|
|
#include "udprelay.h"
|
|
#include "ss_windows.h"
|
|
|
|
#ifdef MODULE_REMOTE
|
|
#define MAX_UDP_CONN_NUM 512
|
|
#else
|
|
#define MAX_UDP_CONN_NUM 256
|
|
#endif
|
|
|
|
#ifdef MODULE_REMOTE
|
|
#ifdef MODULE_
|
|
#error "MODULE_REMOTE and MODULE_LOCAL should not be both defined"
|
|
#endif
|
|
#endif
|
|
|
|
#ifndef EAGAIN
|
|
#define EAGAIN EWOULDBLOCK
|
|
#endif
|
|
|
|
#ifndef EWOULDBLOCK
|
|
#define EWOULDBLOCK EAGAIN
|
|
#endif
|
|
|
|
static void server_recv_cb(SS_P_ ss_io *w, int revents);
|
|
static void remote_recv_cb(SS_P_ ss_io *w, int revents);
|
|
static void remote_timeout_cb(SS_P_ ss_timer *watcher, int revents);
|
|
|
|
static char *hash_key(const int af, const struct sockaddr_storage *addr);
|
|
#ifdef MODULE_REMOTE
|
|
static void resolv_free_cb(void *data);
|
|
static void resolv_cb(struct sockaddr *addr, void *data);
|
|
#endif
|
|
static void close_and_free_remote(SS_P_ remote_ctx_t *ctx);
|
|
static remote_ctx_t *new_remote(int fd, server_ctx_t *server_ctx);
|
|
|
|
#ifdef __ANDROID__
|
|
extern uint64_t tx;
|
|
extern uint64_t rx;
|
|
extern int vpn;
|
|
extern void stat_update_cb();
|
|
#endif
|
|
|
|
extern int verbose;
|
|
extern int reuse_port;
|
|
#ifdef MODULE_REMOTE
|
|
extern uint64_t tx;
|
|
extern uint64_t rx;
|
|
|
|
extern int is_bind_local_addr;
|
|
extern struct sockaddr_storage local_addr_v4;
|
|
extern struct sockaddr_storage local_addr_v6;
|
|
#endif
|
|
|
|
static int packet_size = DEFAULT_PACKET_SIZE;
|
|
/* UDP preserves datagram boundaries. MTU is a transmission hint, not a
|
|
* receive limit: a smaller buffer silently truncates valid fragmented packets
|
|
* and makes authentication fail. Covers the full IPv4/IPv6 UDP datagram. */
|
|
static const int buf_size = 65536;
|
|
static int server_num = 0;
|
|
static server_ctx_t *server_ctx_list[MAX_REMOTE_NUM] = { NULL };
|
|
|
|
const char *s_port = NULL;
|
|
|
|
#ifndef __MINGW32__
|
|
static 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
|
|
|
|
#if defined(MODULE_REMOTE) && defined(SO_BROADCAST)
|
|
static int
|
|
set_broadcast(int socket_fd)
|
|
{
|
|
int opt = 1;
|
|
return ss_setsockopt(socket_fd, SOL_SOCKET, SO_BROADCAST, &opt, sizeof(opt));
|
|
}
|
|
|
|
#endif
|
|
|
|
#ifdef SO_NOSIGPIPE
|
|
static int
|
|
set_nosigpipe(int socket_fd)
|
|
{
|
|
int opt = 1;
|
|
return ss_setsockopt(socket_fd, SOL_SOCKET, SO_NOSIGPIPE, &opt, sizeof(opt));
|
|
}
|
|
|
|
#endif
|
|
|
|
#ifdef MODULE_REDIR
|
|
|
|
#ifndef IP_TRANSPARENT
|
|
#define IP_TRANSPARENT 19
|
|
#endif
|
|
|
|
#ifndef IPV6_TRANSPARENT
|
|
#define IPV6_TRANSPARENT 75
|
|
#endif
|
|
|
|
#ifndef IP_RECVORIGDSTADDR
|
|
#ifdef IP_ORIGDSTADDR
|
|
#define IP_RECVORIGDSTADDR IP_ORIGDSTADDR
|
|
#else
|
|
#define IP_RECVORIGDSTADDR 20
|
|
#endif
|
|
#endif
|
|
|
|
#ifndef IPV6_RECVORIGDSTADDR
|
|
#ifdef IPV6_ORIGDSTADDR
|
|
#define IPV6_RECVORIGDSTADDR IPV6_ORIGDSTADDR
|
|
#else
|
|
#define IPV6_RECVORIGDSTADDR 74
|
|
#endif
|
|
#endif
|
|
|
|
static int
|
|
get_dstaddr(struct msghdr *msg, struct sockaddr_storage *dstaddr)
|
|
{
|
|
struct cmsghdr *cmsg;
|
|
|
|
for (cmsg = CMSG_FIRSTHDR(msg); cmsg; cmsg = CMSG_NXTHDR(msg, cmsg)) {
|
|
if (cmsg->cmsg_level == SOL_IP && cmsg->cmsg_type == IP_RECVORIGDSTADDR) {
|
|
memcpy(dstaddr, CMSG_DATA(cmsg), sizeof(struct sockaddr_in));
|
|
dstaddr->ss_family = AF_INET;
|
|
return 0;
|
|
} else if (cmsg->cmsg_level == SOL_IPV6 && cmsg->cmsg_type == IPV6_RECVORIGDSTADDR) {
|
|
memcpy(dstaddr, CMSG_DATA(cmsg), sizeof(struct sockaddr_in6));
|
|
dstaddr->ss_family = AF_INET6;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
#endif
|
|
|
|
#define HASH_KEY_LEN sizeof(struct sockaddr_storage) + sizeof(int)
|
|
static char *
|
|
hash_key(const int af, const struct sockaddr_storage *addr)
|
|
{
|
|
size_t addr_len = sizeof(struct sockaddr_storage);
|
|
static char key[HASH_KEY_LEN];
|
|
|
|
memset(key, 0, HASH_KEY_LEN);
|
|
memcpy(key, &af, sizeof(int));
|
|
memcpy(key + sizeof(int), (const uint8_t *)addr, addr_len);
|
|
|
|
return key;
|
|
}
|
|
|
|
#if defined(MODULE_REDIR) || defined(MODULE_REMOTE)
|
|
|
|
static int
|
|
construct_udprelay_header(const struct sockaddr_storage *in_addr,
|
|
char *addr_header)
|
|
{
|
|
int addr_header_len = 0;
|
|
|
|
if (in_addr->ss_family == AF_INET) {
|
|
struct sockaddr_in *addr = (struct sockaddr_in *)in_addr;
|
|
size_t addr_len = sizeof(struct in_addr);
|
|
|
|
addr_header[addr_header_len++] = 1;
|
|
memcpy(addr_header + addr_header_len, &addr->sin_addr, addr_len);
|
|
addr_header_len += addr_len;
|
|
memcpy(addr_header + addr_header_len, &addr->sin_port, 2);
|
|
addr_header_len += 2;
|
|
} else if (in_addr->ss_family == AF_INET6) {
|
|
struct sockaddr_in6 *addr = (struct sockaddr_in6 *)in_addr;
|
|
size_t addr_len = sizeof(struct in6_addr);
|
|
|
|
addr_header[addr_header_len++] = 4;
|
|
memcpy(addr_header + addr_header_len, &addr->sin6_addr, addr_len);
|
|
addr_header_len += addr_len;
|
|
memcpy(addr_header + addr_header_len, &addr->sin6_port, 2);
|
|
addr_header_len += 2;
|
|
} else {
|
|
return 0;
|
|
}
|
|
|
|
return addr_header_len;
|
|
}
|
|
|
|
#endif
|
|
|
|
static int
|
|
parse_udprelay_header(const char *buf, const size_t buf_len,
|
|
char *host, char *port, struct sockaddr_storage *storage)
|
|
{
|
|
if (buf_len < 1) {
|
|
return 0;
|
|
}
|
|
|
|
const uint8_t atyp = *(uint8_t *)buf;
|
|
int offset = 1;
|
|
|
|
// get remote addr and port
|
|
if ((atyp & ADDRTYPE_MASK) == 1) {
|
|
// IP V4
|
|
size_t in_addr_len = sizeof(struct in_addr);
|
|
if (buf_len >= in_addr_len + 3) {
|
|
if (storage != NULL) {
|
|
struct sockaddr_in *addr = (struct sockaddr_in *)storage;
|
|
addr->sin_family = AF_INET;
|
|
memcpy(&addr->sin_addr, buf + offset, sizeof(struct in_addr));
|
|
memcpy(&addr->sin_port, buf + offset + in_addr_len, sizeof(uint16_t));
|
|
}
|
|
if (host != NULL) {
|
|
ss_inet_ntop(AF_INET, (const void *)(buf + offset),
|
|
host, INET_ADDRSTRLEN);
|
|
}
|
|
offset += in_addr_len;
|
|
}
|
|
} else if ((atyp & ADDRTYPE_MASK) == 3) {
|
|
// Domain name
|
|
if (buf_len < offset + 1) {
|
|
return 0;
|
|
}
|
|
uint8_t name_len = *(uint8_t *)(buf + offset);
|
|
if (name_len + 4 <= buf_len) {
|
|
if (storage != NULL) {
|
|
char tmp[MAX_HOSTNAME_LEN] = { 0 };
|
|
struct ss_ip ip;
|
|
memcpy(tmp, buf + offset + 1, name_len);
|
|
tmp[name_len] = '\0';
|
|
if (ss_ip_init(&ip, tmp) != -1) {
|
|
if (ip.version == 4) {
|
|
struct sockaddr_in *addr = (struct sockaddr_in *)storage;
|
|
inet_pton(AF_INET, tmp, &(addr->sin_addr));
|
|
memcpy(&addr->sin_port, buf + offset + 1 + name_len, sizeof(uint16_t));
|
|
addr->sin_family = AF_INET;
|
|
} else if (ip.version == 6) {
|
|
struct sockaddr_in6 *addr = (struct sockaddr_in6 *)storage;
|
|
inet_pton(AF_INET6, tmp, &(addr->sin6_addr));
|
|
memcpy(&addr->sin6_port, buf + offset + 1 + name_len, sizeof(uint16_t));
|
|
addr->sin6_family = AF_INET6;
|
|
}
|
|
} else if (!validate_hostname(tmp, name_len)) {
|
|
LOGE("[udp] invalid host name");
|
|
return 0;
|
|
}
|
|
}
|
|
if (host != NULL) {
|
|
memcpy(host, buf + offset + 1, name_len);
|
|
host[name_len] = '\0';
|
|
}
|
|
offset += 1 + name_len;
|
|
}
|
|
} else if ((atyp & ADDRTYPE_MASK) == 4) {
|
|
// IP V6
|
|
size_t in6_addr_len = sizeof(struct in6_addr);
|
|
if (buf_len >= in6_addr_len + 3) {
|
|
if (storage != NULL) {
|
|
struct sockaddr_in6 *addr = (struct sockaddr_in6 *)storage;
|
|
addr->sin6_family = AF_INET6;
|
|
memcpy(&addr->sin6_addr, buf + offset, sizeof(struct in6_addr));
|
|
memcpy(&addr->sin6_port, buf + offset + in6_addr_len, sizeof(uint16_t));
|
|
}
|
|
if (host != NULL) {
|
|
ss_inet_ntop(AF_INET6, (const void *)(buf + offset),
|
|
host, INET6_ADDRSTRLEN);
|
|
}
|
|
offset += in6_addr_len;
|
|
}
|
|
}
|
|
|
|
if (offset == 1) {
|
|
LOGE("[udp] invalid header with addr type %d", atyp);
|
|
return 0;
|
|
}
|
|
|
|
if (port != NULL) {
|
|
snprintf(port, MAX_PORT_STR_LEN, "%d", load16_be(buf + offset));
|
|
}
|
|
offset += 2;
|
|
|
|
return offset;
|
|
}
|
|
|
|
static char *
|
|
get_addr_str(const struct sockaddr *sa, bool has_port)
|
|
{
|
|
static char s[SS_ADDRSTRLEN];
|
|
memset(s, 0, SS_ADDRSTRLEN);
|
|
char addr[INET6_ADDRSTRLEN] = { 0 };
|
|
char port[PORTSTRLEN] = { 0 };
|
|
uint16_t p;
|
|
struct sockaddr_in sa_in;
|
|
struct sockaddr_in6 sa_in6;
|
|
|
|
switch (sa->sa_family) {
|
|
case AF_INET:
|
|
memcpy(&sa_in, sa, sizeof(struct sockaddr_in));
|
|
ss_inet_ntop(AF_INET, &sa_in.sin_addr, addr, INET_ADDRSTRLEN);
|
|
p = ntohs(sa_in.sin_port);
|
|
snprintf(port, sizeof(port), "%d", p);
|
|
break;
|
|
|
|
case AF_INET6:
|
|
memcpy(&sa_in6, sa, sizeof(struct sockaddr_in6));
|
|
ss_inet_ntop(AF_INET6, &sa_in6.sin6_addr, addr, INET6_ADDRSTRLEN);
|
|
p = ntohs(sa_in6.sin6_port);
|
|
snprintf(port, sizeof(port), "%d", p);
|
|
break;
|
|
|
|
default:
|
|
strncpy(s, "Unknown AF", SS_ADDRSTRLEN);
|
|
}
|
|
|
|
int addr_len = strlen(addr);
|
|
int port_len = strlen(port);
|
|
// s is a zeroed static buffer large enough for addr + ':' + port
|
|
memcpy(s, addr, addr_len); // NOLINT(bugprone-not-null-terminated-result)
|
|
|
|
if (has_port) {
|
|
memcpy(s + addr_len + 1, port, port_len); // NOLINT(bugprone-not-null-terminated-result)
|
|
s[addr_len] = ':';
|
|
}
|
|
|
|
return s;
|
|
}
|
|
|
|
int
|
|
create_remote_socket(int ipv6)
|
|
{
|
|
int remote_sock;
|
|
|
|
if (ipv6) {
|
|
// Try to bind IPv6 first
|
|
struct sockaddr_in6 addr;
|
|
memset(&addr, 0, sizeof(struct sockaddr_in6));
|
|
addr.sin6_family = AF_INET6;
|
|
addr.sin6_addr = in6addr_any;
|
|
addr.sin6_port = 0;
|
|
remote_sock = ss_socket(AF_INET6, SOCK_DGRAM, 0);
|
|
if (remote_sock == -1) {
|
|
ERROR("[udp] cannot create socket");
|
|
return -1;
|
|
}
|
|
#ifdef MODULE_REMOTE
|
|
if (is_bind_local_addr) {
|
|
if (local_addr_v6.ss_family == AF_INET6) {
|
|
if (bind_to_addr(&local_addr_v6, remote_sock) == -1) {
|
|
ERROR("bind_to_addr");
|
|
FATAL("[udp] cannot bind socket");
|
|
return -1;
|
|
}
|
|
}
|
|
} else {
|
|
#endif
|
|
if (bind(remote_sock, (struct sockaddr *)&addr, sizeof(addr)) != 0) {
|
|
FATAL("[udp] cannot bind socket");
|
|
return -1;
|
|
}
|
|
#ifdef MODULE_REMOTE
|
|
}
|
|
#endif
|
|
} else {
|
|
// Or else bind to IPv4
|
|
struct sockaddr_in addr;
|
|
memset(&addr, 0, sizeof(struct sockaddr_in));
|
|
addr.sin_family = AF_INET;
|
|
addr.sin_addr.s_addr = INADDR_ANY;
|
|
addr.sin_port = 0;
|
|
remote_sock = ss_socket(AF_INET, SOCK_DGRAM, 0);
|
|
if (remote_sock == -1) {
|
|
ERROR("[udp] cannot create socket");
|
|
return -1;
|
|
}
|
|
#ifdef MODULE_REMOTE
|
|
if (is_bind_local_addr) {
|
|
if (local_addr_v4.ss_family == AF_INET) {
|
|
if (bind_to_addr(&local_addr_v4, remote_sock) == -1) {
|
|
ERROR("bind_to_addr");
|
|
FATAL("[udp] cannot bind socket");
|
|
return -1;
|
|
}
|
|
}
|
|
} else {
|
|
#endif
|
|
if (bind(remote_sock, (struct sockaddr *)&addr, sizeof(addr)) != 0) {
|
|
FATAL("[udp] cannot bind remote");
|
|
return -1;
|
|
}
|
|
#ifdef MODULE_REMOTE
|
|
}
|
|
#endif
|
|
}
|
|
|
|
#if defined(__linux__)
|
|
// Disable fragmentation
|
|
int val = IP_PMTUDISC_DO;
|
|
ss_setsockopt(remote_sock, IPPROTO_IP, IP_MTU_DISCOVER, &val, sizeof(val));
|
|
#endif
|
|
|
|
return remote_sock;
|
|
}
|
|
|
|
int
|
|
create_server_socket(const char *host, const char *port)
|
|
{
|
|
struct addrinfo hints;
|
|
struct addrinfo *result, *rp, *ipv4v6bindall;
|
|
int s, server_sock = -1;
|
|
|
|
memset(&hints, 0, sizeof(struct addrinfo));
|
|
hints.ai_family = AF_UNSPEC; /* Return IPv4 and IPv6 choices */
|
|
hints.ai_socktype = SOCK_DGRAM; /* We want a UDP socket */
|
|
hints.ai_flags = AI_PASSIVE | AI_ADDRCONFIG; /* For wildcard IP address */
|
|
hints.ai_protocol = IPPROTO_UDP;
|
|
|
|
s = getaddrinfo(host, port, &hints, &result);
|
|
if (s != 0) {
|
|
LOGE("[udp] getaddrinfo: %s", gai_strerror(s));
|
|
return -1;
|
|
}
|
|
|
|
if (result == NULL) {
|
|
LOGE("[udp] 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) {
|
|
server_sock = ss_socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
|
|
if (server_sock == -1) {
|
|
continue;
|
|
}
|
|
|
|
if (rp->ai_family == AF_INET6) {
|
|
int ipv6only = host ? 1 : 0;
|
|
ss_setsockopt(server_sock, IPPROTO_IPV6, IPV6_V6ONLY, &ipv6only, sizeof(ipv6only));
|
|
}
|
|
|
|
int opt = 1;
|
|
ss_setsockopt(server_sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
|
|
#ifdef SO_NOSIGPIPE
|
|
set_nosigpipe(server_sock);
|
|
#endif
|
|
if (reuse_port) {
|
|
int err = set_reuseport(server_sock);
|
|
if (err == 0) {
|
|
LOGI("udp port reuse enabled");
|
|
}
|
|
}
|
|
#ifdef IP_TOS
|
|
// Set QoS flag
|
|
int tos = 46 << 2;
|
|
int rc = ss_setsockopt(server_sock, IPPROTO_IP, IP_TOS, &tos, sizeof(tos));
|
|
if (rc < 0 && ss_socket_error() != ENOPROTOOPT) {
|
|
LOGE("setting ipv4 dscp failed: %d", ss_socket_error());
|
|
}
|
|
#ifdef IPV6_TCLASS
|
|
rc = ss_setsockopt(server_sock, IPPROTO_IPV6, IPV6_TCLASS, &tos, sizeof(tos));
|
|
if (rc < 0 && ss_socket_error() != ENOPROTOOPT) {
|
|
LOGE("setting ipv6 dscp failed: %d", ss_socket_error());
|
|
}
|
|
#endif
|
|
#endif
|
|
|
|
#ifdef MODULE_REDIR
|
|
int sol = rp->ai_family == AF_INET ? SOL_IP : SOL_IPV6;
|
|
int flag_t = rp->ai_family == AF_INET ? IP_TRANSPARENT : IPV6_TRANSPARENT;
|
|
int flag_r = rp->ai_family == AF_INET ? IP_RECVORIGDSTADDR : IPV6_RECVORIGDSTADDR;
|
|
|
|
if (ss_setsockopt(server_sock, sol, flag_t, &opt, sizeof(opt))) {
|
|
ERROR("[udp] setsockopt IP_TRANSPARENT");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
if (ss_setsockopt(server_sock, sol, flag_r, &opt, sizeof(opt))) {
|
|
FATAL("[udp] setsockopt IP_RECVORIGDSTADDR");
|
|
}
|
|
#endif
|
|
|
|
s = bind(server_sock, rp->ai_addr, rp->ai_addrlen);
|
|
if (s == 0) {
|
|
/* We managed to bind successfully! */
|
|
break;
|
|
} else {
|
|
ERROR("[udp] bind");
|
|
}
|
|
|
|
ss_socket_close(server_sock);
|
|
server_sock = -1;
|
|
}
|
|
|
|
freeaddrinfo(result);
|
|
|
|
#if defined(__linux__)
|
|
// Disable fragmentation
|
|
int val = IP_PMTUDISC_DO;
|
|
ss_setsockopt(server_sock, IPPROTO_IP, IP_MTU_DISCOVER, &val, sizeof(val));
|
|
#endif
|
|
|
|
return server_sock;
|
|
}
|
|
|
|
remote_ctx_t *
|
|
new_remote(int fd, server_ctx_t *server_ctx)
|
|
{
|
|
remote_ctx_t *ctx = (remote_ctx_t *)ss_malloc(sizeof(remote_ctx_t));
|
|
memset(ctx, 0, sizeof(remote_ctx_t));
|
|
|
|
ctx->fd = fd;
|
|
ctx->server_ctx = server_ctx;
|
|
ctx->af = AF_UNSPEC;
|
|
|
|
ss_io_init(&ctx->io, remote_recv_cb, fd, SS_READ);
|
|
ss_timer_init(&ctx->watcher, remote_timeout_cb, server_ctx->timeout,
|
|
server_ctx->timeout);
|
|
|
|
return ctx;
|
|
}
|
|
|
|
server_ctx_t *
|
|
new_server_ctx(int fd)
|
|
{
|
|
server_ctx_t *ctx = (server_ctx_t *)ss_malloc(sizeof(server_ctx_t));
|
|
memset(ctx, 0, sizeof(server_ctx_t));
|
|
|
|
ctx->fd = fd;
|
|
|
|
ss_io_init(&ctx->io, server_recv_cb, fd, SS_READ);
|
|
|
|
return ctx;
|
|
}
|
|
|
|
#ifdef MODULE_REMOTE
|
|
struct query_ctx *
|
|
new_query_ctx(char *buf, size_t len)
|
|
{
|
|
struct query_ctx *ctx = (struct query_ctx *)ss_malloc(sizeof(struct query_ctx));
|
|
memset(ctx, 0, sizeof(struct query_ctx));
|
|
ctx->buf = (buffer_t *)ss_malloc(sizeof(buffer_t));
|
|
balloc(ctx->buf, len);
|
|
memcpy(ctx->buf->data, buf, len);
|
|
ctx->buf->len = len;
|
|
return ctx;
|
|
}
|
|
|
|
void
|
|
close_and_free_query(SS_P_ struct query_ctx *ctx)
|
|
{
|
|
if (ctx != NULL) {
|
|
if (ctx->buf != NULL) {
|
|
bfree(ctx->buf);
|
|
ss_free(ctx->buf);
|
|
}
|
|
ss_free(ctx);
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
void
|
|
close_and_free_remote(SS_P_ remote_ctx_t *ctx)
|
|
{
|
|
if (ctx != NULL) {
|
|
ss_timer_stop(SS_A_ & ctx->watcher);
|
|
ss_io_stop(SS_A_ & ctx->io);
|
|
ss_socket_close(ctx->fd);
|
|
if (ctx->udp_session != NULL
|
|
&& ctx->server_ctx != NULL
|
|
&& ctx->server_ctx->crypto->udp_session_release != NULL) {
|
|
ctx->server_ctx->crypto->udp_session_release(ctx->udp_session);
|
|
}
|
|
ss_free(ctx);
|
|
}
|
|
}
|
|
|
|
static void
|
|
remote_timeout_cb(SS_P_ ss_timer *watcher, int revents)
|
|
{
|
|
remote_ctx_t *remote_ctx
|
|
= ss_container_of(watcher, remote_ctx_t, watcher);
|
|
|
|
if (verbose) {
|
|
LOGI("[udp] connection timeout");
|
|
}
|
|
|
|
char *key = hash_key(remote_ctx->af, &remote_ctx->src_addr);
|
|
cache_remove(remote_ctx->server_ctx->conn_cache, key, HASH_KEY_LEN);
|
|
}
|
|
|
|
#ifdef MODULE_REMOTE
|
|
static void
|
|
resolv_free_cb(void *data)
|
|
{
|
|
struct query_ctx *ctx = (struct query_ctx *)data;
|
|
if (ctx->buf != NULL) {
|
|
bfree(ctx->buf);
|
|
ss_free(ctx->buf);
|
|
}
|
|
ss_free(ctx);
|
|
}
|
|
|
|
static void
|
|
resolv_cb(struct sockaddr *addr, void *data)
|
|
{
|
|
struct query_ctx *query_ctx = (struct query_ctx *)data;
|
|
struct ss_loop *loop = query_ctx->server_ctx->loop;
|
|
|
|
if (addr == NULL) {
|
|
LOGE("[udp] unable to resolve");
|
|
} else {
|
|
remote_ctx_t *remote_ctx = query_ctx->remote_ctx;
|
|
int cache_hit = 0;
|
|
|
|
// Lookup in the conn cache
|
|
if (remote_ctx == NULL) {
|
|
char *key = hash_key(AF_UNSPEC, &query_ctx->src_addr);
|
|
cache_lookup(query_ctx->server_ctx->conn_cache, key, HASH_KEY_LEN, (void *)&remote_ctx);
|
|
}
|
|
|
|
if (remote_ctx == NULL) {
|
|
int remotefd = create_remote_socket(addr->sa_family == AF_INET6);
|
|
if (remotefd != -1) {
|
|
setnonblocking(remotefd);
|
|
#ifdef SO_BROADCAST
|
|
set_broadcast(remotefd);
|
|
#endif
|
|
#ifdef SO_NOSIGPIPE
|
|
set_nosigpipe(remotefd);
|
|
#endif
|
|
#ifdef IP_TOS
|
|
// Set QoS flag
|
|
int tos = 46 << 2;
|
|
int rc = ss_setsockopt(remotefd, IPPROTO_IP, IP_TOS, &tos, sizeof(tos));
|
|
if (rc < 0 && ss_socket_error() != ENOPROTOOPT) {
|
|
LOGE("setting ipv4 dscp failed: %d", ss_socket_error());
|
|
}
|
|
#ifdef IPV6_TCLASS
|
|
rc = ss_setsockopt(remotefd, IPPROTO_IPV6, IPV6_TCLASS, &tos, sizeof(tos));
|
|
if (rc < 0 && ss_socket_error() != ENOPROTOOPT) {
|
|
LOGE("setting ipv6 dscp failed: %d", ss_socket_error());
|
|
}
|
|
#endif
|
|
#endif
|
|
#ifdef SET_INTERFACE
|
|
if (query_ctx->server_ctx->iface) {
|
|
if (setinterface(remotefd, query_ctx->server_ctx->iface) == -1)
|
|
ERROR("setinterface");
|
|
}
|
|
#endif
|
|
remote_ctx = new_remote(remotefd, query_ctx->server_ctx);
|
|
remote_ctx->src_addr = query_ctx->src_addr;
|
|
remote_ctx->server_ctx = query_ctx->server_ctx;
|
|
} else {
|
|
ERROR("[udp] bind() error");
|
|
}
|
|
} else {
|
|
cache_hit = 1;
|
|
}
|
|
|
|
if (remote_ctx != NULL) {
|
|
if (addr->sa_family == AF_INET)
|
|
memcpy(&remote_ctx->dst_addr, addr, sizeof(struct sockaddr_in));
|
|
else
|
|
memcpy(&remote_ctx->dst_addr, addr, sizeof(struct sockaddr_in6));
|
|
|
|
size_t addr_len = get_sockaddr_len(addr);
|
|
int s = sendto(remote_ctx->fd, query_ctx->buf->data, query_ctx->buf->len,
|
|
0, addr, addr_len);
|
|
|
|
if (s == -1) {
|
|
ERROR("[udp] sendto_remote");
|
|
if (!cache_hit) {
|
|
close_and_free_remote(SS_A_ remote_ctx);
|
|
}
|
|
} else {
|
|
if (!cache_hit) {
|
|
// Add to conn cache
|
|
char *key = hash_key(AF_UNSPEC, &remote_ctx->src_addr);
|
|
cache_insert(query_ctx->server_ctx->conn_cache, key, HASH_KEY_LEN, (void *)remote_ctx);
|
|
ss_io_start(SS_A_ & remote_ctx->io);
|
|
ss_timer_start(SS_A_ & remote_ctx->watcher);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
void convert_ipv4_mapped_ipv6(struct sockaddr_storage* addr) {
|
|
if (addr->ss_family == AF_INET) {
|
|
struct sockaddr_in* ipv4_addr = (struct sockaddr_in*)addr;
|
|
|
|
struct sockaddr_storage mapped_addr;
|
|
memset(&mapped_addr, 0, sizeof(mapped_addr));
|
|
|
|
struct sockaddr_in6* mapped_ipv6_addr = (struct sockaddr_in6*)&mapped_addr;
|
|
mapped_ipv6_addr->sin6_family = AF_INET6;
|
|
mapped_ipv6_addr->sin6_port = ipv4_addr->sin_port;
|
|
uint8_t* ipv6_raw_addr = mapped_ipv6_addr->sin6_addr.s6_addr;
|
|
ipv6_raw_addr[10] = 0xff;
|
|
ipv6_raw_addr[11] = 0xff;
|
|
uint32_t ipv4_raw_addr = ntohl(ipv4_addr->sin_addr.s_addr);
|
|
ipv6_raw_addr[12] = (ipv4_raw_addr >> 24) & 0xff;
|
|
ipv6_raw_addr[13] = (ipv4_raw_addr >> 16) & 0xff;
|
|
ipv6_raw_addr[14] = (ipv4_raw_addr >> 8) & 0xff;
|
|
ipv6_raw_addr[15] = ipv4_raw_addr & 0xff;
|
|
|
|
memcpy(addr, &mapped_addr, sizeof(mapped_addr));
|
|
}
|
|
}
|
|
|
|
static void
|
|
remote_recv_cb(SS_P_ ss_io *w, int revents)
|
|
{
|
|
ssize_t r;
|
|
remote_ctx_t *remote_ctx = (remote_ctx_t *)w;
|
|
server_ctx_t *server_ctx = remote_ctx->server_ctx;
|
|
|
|
// server has been closed
|
|
if (server_ctx == NULL) {
|
|
LOGE("[udp] invalid server");
|
|
close_and_free_remote(SS_A_ remote_ctx);
|
|
return;
|
|
}
|
|
|
|
if (verbose) {
|
|
LOGI("[udp] remote receive a packet");
|
|
}
|
|
|
|
struct sockaddr_storage src_addr;
|
|
socklen_t src_addr_len = sizeof(struct sockaddr_storage);
|
|
memset(&src_addr, 0, src_addr_len);
|
|
|
|
buffer_t *buf = (buffer_t *)ss_malloc(sizeof(buffer_t));
|
|
balloc(buf, buf_size);
|
|
|
|
// recv
|
|
r = recvfrom(remote_ctx->fd, buf->data, buf_size, 0, (struct sockaddr *)&src_addr, &src_addr_len);
|
|
|
|
if (r == -1) {
|
|
// error on recv
|
|
// simply drop that packet
|
|
ERROR("[udp] remote_recv_recvfrom");
|
|
goto CLEAN_UP;
|
|
} else if (r > packet_size) {
|
|
if (verbose) {
|
|
LOGI("[udp] remote_recv_recvfrom fragmentation, MTU at least be: " SSIZE_FMT, r + PACKET_HEADER_SIZE);
|
|
}
|
|
}
|
|
|
|
buf->len = r;
|
|
|
|
#ifdef MODULE_LOCAL
|
|
int err;
|
|
err = server_ctx->crypto->decrypt_udp != NULL
|
|
? server_ctx->crypto->decrypt_udp(buf, server_ctx->crypto->cipher,
|
|
buf_size, &remote_ctx->udp_session)
|
|
: server_ctx->crypto->decrypt_all(buf, server_ctx->crypto->cipher, buf_size);
|
|
if (err) {
|
|
LOGE("failed to handshake with %s: %s",
|
|
get_addr_str((struct sockaddr *)&src_addr, false), "suspicious UDP packet");
|
|
// drop the packet silently
|
|
goto CLEAN_UP;
|
|
}
|
|
|
|
#ifdef MODULE_REDIR
|
|
struct sockaddr_storage dst_addr;
|
|
memset(&dst_addr, 0, sizeof(struct sockaddr_storage));
|
|
int len;
|
|
len = parse_udprelay_header(buf->data, buf->len, NULL, NULL, &dst_addr);
|
|
|
|
if (dst_addr.ss_family != AF_INET && dst_addr.ss_family != AF_INET6) {
|
|
LOGI("[udp] ss-redir does not support domain name");
|
|
goto CLEAN_UP;
|
|
}
|
|
#else
|
|
int len;
|
|
len = parse_udprelay_header(buf->data, buf->len, NULL, NULL, NULL);
|
|
#endif
|
|
|
|
if (len == 0) {
|
|
// error when parsing header
|
|
LOGE("[udp] error in parse header");
|
|
goto CLEAN_UP;
|
|
}
|
|
|
|
#if defined(MODULE_TUNNEL) || defined(MODULE_REDIR)
|
|
// Construct packet
|
|
buf->len -= len;
|
|
memmove(buf->data, buf->data + len, buf->len);
|
|
#else
|
|
#ifdef __ANDROID__
|
|
rx += buf->len;
|
|
stat_update_cb();
|
|
#endif
|
|
// Construct packet
|
|
brealloc(buf, buf->len + 3, buf_size);
|
|
memmove(buf->data + 3, buf->data, buf->len);
|
|
memset(buf->data, 0, 3);
|
|
buf->len += 3;
|
|
#endif
|
|
|
|
#endif
|
|
|
|
#ifdef MODULE_REMOTE
|
|
|
|
rx += buf->len;
|
|
|
|
// Reconstruct UDP response header
|
|
char addr_header[MAX_ADDR_HEADER_SIZE];
|
|
int addr_header_len;
|
|
addr_header_len = construct_udprelay_header(&src_addr, addr_header);
|
|
|
|
// Construct packet
|
|
brealloc(buf, buf->len + addr_header_len, buf_size);
|
|
memmove(buf->data + addr_header_len, buf->data, buf->len);
|
|
memcpy(buf->data, addr_header, addr_header_len);
|
|
buf->len += addr_header_len;
|
|
|
|
int err;
|
|
err = server_ctx->crypto->encrypt_udp != NULL
|
|
? server_ctx->crypto->encrypt_udp(buf, server_ctx->crypto->cipher,
|
|
buf_size, &remote_ctx->udp_session)
|
|
: server_ctx->crypto->encrypt_all(buf, server_ctx->crypto->cipher, buf_size);
|
|
if (err) {
|
|
// drop the packet silently
|
|
goto CLEAN_UP;
|
|
}
|
|
|
|
#endif
|
|
|
|
if (buf->len > packet_size) {
|
|
if (verbose) {
|
|
LOGI("[udp] remote_recv_sendto fragmentation, MTU at least be: " SIZE_FMT, buf->len + PACKET_HEADER_SIZE);
|
|
}
|
|
}
|
|
|
|
|
|
#ifdef MODULE_REDIR
|
|
convert_ipv4_mapped_ipv6(&dst_addr);
|
|
|
|
size_t remote_dst_addr_len;
|
|
remote_dst_addr_len = get_sockaddr_len((struct sockaddr *)&dst_addr);
|
|
|
|
int src_fd;
|
|
src_fd = ss_socket(AF_INET6, SOCK_DGRAM, 0);
|
|
if (src_fd < 0) {
|
|
ERROR("[udp] remote_recv_socket");
|
|
goto CLEAN_UP;
|
|
}
|
|
int opt;
|
|
opt = 1;
|
|
if (ss_setsockopt(src_fd, SOL_IPV6, IPV6_TRANSPARENT, &opt, sizeof(opt))) {
|
|
ERROR("[udp] remote_recv_setsockopt");
|
|
ss_socket_close(src_fd);
|
|
goto CLEAN_UP;
|
|
}
|
|
if (ss_setsockopt(src_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt))) {
|
|
ERROR("[udp] remote_recv_setsockopt");
|
|
ss_socket_close(src_fd);
|
|
goto CLEAN_UP;
|
|
}
|
|
if (reuse_port) {
|
|
if (set_reuseport(src_fd) != 0) {
|
|
ERROR("[udp] remote_recv port_reuse");
|
|
}
|
|
}
|
|
#ifdef IP_TOS
|
|
// Set QoS flag
|
|
int tos;
|
|
tos = 46 << 2;
|
|
int rc;
|
|
rc = ss_setsockopt(src_fd, IPPROTO_IP, IP_TOS, &tos, sizeof(tos));
|
|
if (rc < 0 && ss_socket_error() != ENOPROTOOPT) {
|
|
LOGE("setting ipv4 dscp failed: %d", ss_socket_error());
|
|
}
|
|
#ifdef IPV6_TCLASS
|
|
rc = ss_setsockopt(src_fd, IPPROTO_IPV6, IPV6_TCLASS, &tos, sizeof(tos));
|
|
if (rc < 0 && ss_socket_error() != ENOPROTOOPT) {
|
|
LOGE("setting ipv6 dscp failed: %d", ss_socket_error());
|
|
}
|
|
#endif
|
|
#endif
|
|
if (bind(src_fd, (struct sockaddr *)&dst_addr, remote_dst_addr_len) != 0) {
|
|
ERROR("[udp] remote_recv_bind");
|
|
ss_socket_close(src_fd);
|
|
goto CLEAN_UP;
|
|
}
|
|
|
|
struct sockaddr_storage mapped_src_addr;
|
|
memcpy(&mapped_src_addr, &remote_ctx->src_addr, sizeof(remote_ctx->src_addr));
|
|
convert_ipv4_mapped_ipv6(&mapped_src_addr);
|
|
|
|
size_t remote_src_addr_len;
|
|
remote_src_addr_len = get_sockaddr_len((struct sockaddr *)&mapped_src_addr);
|
|
|
|
int s;
|
|
s = sendto(src_fd, buf->data, buf->len, 0,
|
|
(struct sockaddr *)&mapped_src_addr, remote_src_addr_len);
|
|
if (s == -1 && !(ss_socket_error() == EAGAIN || ss_socket_error() == EWOULDBLOCK)) {
|
|
ERROR("[udp] remote_recv_sendto");
|
|
ss_socket_close(src_fd);
|
|
goto CLEAN_UP;
|
|
}
|
|
ss_socket_close(src_fd);
|
|
|
|
#else
|
|
size_t remote_src_addr_len;
|
|
remote_src_addr_len = get_sockaddr_len((struct sockaddr *)&remote_ctx->src_addr);
|
|
|
|
int s;
|
|
s = sendto(server_ctx->fd, buf->data, buf->len, 0,
|
|
(struct sockaddr *)&remote_ctx->src_addr, remote_src_addr_len);
|
|
if (s == -1 && !(ss_socket_error() == EAGAIN || ss_socket_error() == EWOULDBLOCK)) {
|
|
ERROR("[udp] remote_recv_sendto");
|
|
goto CLEAN_UP;
|
|
}
|
|
|
|
#endif
|
|
|
|
// handle the UDP packet successfully,
|
|
// triger the timer
|
|
ss_timer_again(SS_A_ & remote_ctx->watcher);
|
|
|
|
CLEAN_UP:
|
|
|
|
bfree(buf);
|
|
ss_free(buf);
|
|
}
|
|
|
|
static void
|
|
server_recv_cb(SS_P_ ss_io *w, int revents)
|
|
{
|
|
server_ctx_t *server_ctx = (server_ctx_t *)w;
|
|
struct sockaddr_storage src_addr;
|
|
memset(&src_addr, 0, sizeof(struct sockaddr_storage));
|
|
|
|
buffer_t *buf = (buffer_t *)ss_malloc(sizeof(buffer_t));
|
|
balloc(buf, buf_size);
|
|
|
|
socklen_t src_addr_len = sizeof(struct sockaddr_storage);
|
|
unsigned int offset = 0;
|
|
|
|
#ifdef MODULE_REDIR
|
|
char control_buffer[64] = { 0 };
|
|
struct msghdr msg;
|
|
memset(&msg, 0, sizeof(struct msghdr));
|
|
struct iovec iov[1];
|
|
struct sockaddr_storage dst_addr;
|
|
memset(&dst_addr, 0, sizeof(struct sockaddr_storage));
|
|
|
|
msg.msg_name = &src_addr;
|
|
msg.msg_namelen = src_addr_len;
|
|
msg.msg_control = control_buffer;
|
|
msg.msg_controllen = sizeof(control_buffer);
|
|
|
|
iov[0].iov_base = buf->data;
|
|
iov[0].iov_len = buf_size;
|
|
msg.msg_iov = iov;
|
|
msg.msg_iovlen = 1;
|
|
|
|
buf->len = recvmsg(server_ctx->fd, &msg, 0);
|
|
if (buf->len == -1) {
|
|
ERROR("[udp] server_recvmsg");
|
|
goto CLEAN_UP;
|
|
} else if (buf->len > packet_size) {
|
|
if (verbose) {
|
|
LOGI("[udp] UDP server_recv_recvmsg fragmentation, MTU at least be: " SIZE_FMT,
|
|
buf->len + PACKET_HEADER_SIZE);
|
|
}
|
|
}
|
|
|
|
if (get_dstaddr(&msg, &dst_addr)) {
|
|
LOGE("[udp] unable to get dest addr");
|
|
goto CLEAN_UP;
|
|
}
|
|
#else
|
|
ssize_t r;
|
|
r = recvfrom(server_ctx->fd, buf->data, buf_size,
|
|
0, (struct sockaddr *)&src_addr, &src_addr_len);
|
|
|
|
if (r == -1) {
|
|
// error on recv
|
|
// simply drop that packet
|
|
ERROR("[udp] server_recv_recvfrom");
|
|
goto CLEAN_UP;
|
|
} else if (r > packet_size) {
|
|
if (verbose) {
|
|
LOGI("[udp] server_recv_recvfrom fragmentation, MTU at least be: " SSIZE_FMT, r + PACKET_HEADER_SIZE);
|
|
}
|
|
}
|
|
|
|
buf->len = r;
|
|
#endif
|
|
|
|
if (verbose) {
|
|
LOGI("[udp] server receive a packet");
|
|
}
|
|
|
|
#ifdef MODULE_REMOTE
|
|
tx += buf->len;
|
|
|
|
/*
|
|
* AEAD-2022 routes by client session ID, which is only known after
|
|
* decryption, so the crypto layer resolves the session itself and hands
|
|
* it back here to be attached to the relay context.
|
|
*/
|
|
void *udp_session;
|
|
udp_session = NULL;
|
|
int err;
|
|
err = server_ctx->crypto->decrypt_udp != NULL
|
|
? server_ctx->crypto->decrypt_udp(buf, server_ctx->crypto->cipher,
|
|
buf_size, &udp_session)
|
|
: server_ctx->crypto->decrypt_all(buf, server_ctx->crypto->cipher, buf_size);
|
|
if (err) {
|
|
LOGE("failed to handshake with %s: %s",
|
|
get_addr_str((struct sockaddr *)&src_addr, false), "suspicious UDP packet");
|
|
// drop the packet silently
|
|
goto CLEAN_UP;
|
|
}
|
|
#endif
|
|
|
|
#ifdef MODULE_LOCAL
|
|
#if !defined(MODULE_TUNNEL) && !defined(MODULE_REDIR)
|
|
#ifdef __ANDROID__
|
|
tx += buf->len;
|
|
#endif
|
|
if (buf->len < 3) {
|
|
LOGE("[udp] invalid SOCKS5 UDP header");
|
|
goto CLEAN_UP;
|
|
}
|
|
uint8_t frag;
|
|
frag = *(uint8_t *)(buf->data + 2);
|
|
offset += 3;
|
|
#endif
|
|
#endif
|
|
|
|
/*
|
|
*
|
|
* SOCKS5 UDP Request
|
|
* +----+------+------+----------+----------+----------+
|
|
* |RSV | FRAG | ATYP | DST.ADDR | DST.PORT | DATA |
|
|
* +----+------+------+----------+----------+----------+
|
|
* | 2 | 1 | 1 | Variable | 2 | Variable |
|
|
* +----+------+------+----------+----------+----------+
|
|
*
|
|
* SOCKS5 UDP Response
|
|
* +----+------+------+----------+----------+----------+
|
|
* |RSV | FRAG | ATYP | DST.ADDR | DST.PORT | DATA |
|
|
* +----+------+------+----------+----------+----------+
|
|
* | 2 | 1 | 1 | Variable | 2 | Variable |
|
|
* +----+------+------+----------+----------+----------+
|
|
*
|
|
* shadowsocks UDP Request (before encrypted)
|
|
* +------+----------+----------+----------+
|
|
* | ATYP | DST.ADDR | DST.PORT | DATA |
|
|
* +------+----------+----------+----------+
|
|
* | 1 | Variable | 2 | Variable |
|
|
* +------+----------+----------+----------+
|
|
*
|
|
* shadowsocks UDP Response (before encrypted)
|
|
* +------+----------+----------+----------+
|
|
* | ATYP | DST.ADDR | DST.PORT | DATA |
|
|
* +------+----------+----------+----------+
|
|
* | 1 | Variable | 2 | Variable |
|
|
* +------+----------+----------+----------+
|
|
*
|
|
* shadowsocks UDP Request and Response (after encrypted)
|
|
* +-------+--------------+
|
|
* | IV | PAYLOAD |
|
|
* +-------+--------------+
|
|
* | Fixed | Variable |
|
|
* +-------+--------------+
|
|
*
|
|
*/
|
|
|
|
#ifdef MODULE_REDIR
|
|
char addr_header[MAX_ADDR_HEADER_SIZE];
|
|
memset(addr_header, 0, sizeof(addr_header));
|
|
int addr_header_len;
|
|
addr_header_len = construct_udprelay_header(&dst_addr, addr_header);
|
|
|
|
if (addr_header_len == 0) {
|
|
LOGE("[udp] failed to parse tproxy addr");
|
|
goto CLEAN_UP;
|
|
}
|
|
|
|
// reconstruct the buffer
|
|
brealloc(buf, buf->len + addr_header_len, buf_size);
|
|
memmove(buf->data + addr_header_len, buf->data, buf->len);
|
|
memcpy(buf->data, addr_header, addr_header_len);
|
|
buf->len += addr_header_len;
|
|
|
|
#elif MODULE_TUNNEL
|
|
|
|
char addr_header[MAX_ADDR_HEADER_SIZE];
|
|
memset(addr_header, 0, sizeof(addr_header));
|
|
char *host;
|
|
host = server_ctx->tunnel_addr.host;
|
|
char *port;
|
|
port = server_ctx->tunnel_addr.port;
|
|
uint16_t port_num;
|
|
port_num = 0;
|
|
if (ss_parse_uint16_port(port, &port_num) == -1) {
|
|
LOGE("[udp] invalid tunnel port");
|
|
goto CLEAN_UP;
|
|
}
|
|
uint16_t port_net_num;
|
|
port_net_num = htons(port_num);
|
|
int addr_header_len;
|
|
addr_header_len = 0;
|
|
|
|
struct ss_ip ip;
|
|
if (ss_ip_init(&ip, host) != -1) {
|
|
if (ip.version == 4) {
|
|
// send as IPv4
|
|
struct in_addr host_addr;
|
|
memset(&host_addr, 0, sizeof(struct in_addr));
|
|
int host_len = sizeof(struct in_addr);
|
|
|
|
if (inet_pton(AF_INET, host, &host_addr) == -1) {
|
|
FATAL("IP parser error");
|
|
}
|
|
addr_header[addr_header_len++] = 1;
|
|
memcpy(addr_header + addr_header_len, &host_addr, host_len);
|
|
addr_header_len += host_len;
|
|
} else if (ip.version == 6) {
|
|
// send as IPv6
|
|
struct in6_addr host_addr;
|
|
memset(&host_addr, 0, sizeof(struct in6_addr));
|
|
int host_len = sizeof(struct in6_addr);
|
|
|
|
if (inet_pton(AF_INET6, host, &host_addr) == -1) {
|
|
FATAL("IP parser error");
|
|
}
|
|
addr_header[addr_header_len++] = 4;
|
|
memcpy(addr_header + addr_header_len, &host_addr, host_len);
|
|
addr_header_len += host_len;
|
|
} else {
|
|
FATAL("IP parser error");
|
|
}
|
|
} else {
|
|
// send as domain
|
|
int host_len = strlen(host);
|
|
|
|
if (host_len > UINT8_MAX) {
|
|
LOGE("[udp] tunnel host name is too long");
|
|
goto CLEAN_UP;
|
|
}
|
|
addr_header[addr_header_len++] = 3;
|
|
addr_header[addr_header_len++] = host_len;
|
|
// addr_header is a binary protocol buffer, not a C string
|
|
memcpy(addr_header + addr_header_len, host, host_len); // NOLINT(bugprone-not-null-terminated-result)
|
|
addr_header_len += host_len;
|
|
}
|
|
memcpy(addr_header + addr_header_len, &port_net_num, 2);
|
|
addr_header_len += 2;
|
|
|
|
// reconstruct the buffer
|
|
brealloc(buf, buf->len + addr_header_len, buf_size);
|
|
memmove(buf->data + addr_header_len, buf->data, buf->len);
|
|
memcpy(buf->data, addr_header, addr_header_len);
|
|
buf->len += addr_header_len;
|
|
|
|
#else
|
|
|
|
char host[MAX_HOSTNAME_LEN];
|
|
char port[MAX_PORT_STR_LEN];
|
|
memset(host, 0, sizeof(host));
|
|
memset(port, 0, sizeof(port));
|
|
struct sockaddr_storage dst_addr;
|
|
memset(&dst_addr, 0, sizeof(struct sockaddr_storage));
|
|
|
|
int addr_header_len;
|
|
addr_header_len = parse_udprelay_header(buf->data + offset, buf->len - offset,
|
|
host, port, &dst_addr);
|
|
if (addr_header_len == 0) {
|
|
// error in parse header
|
|
goto CLEAN_UP;
|
|
}
|
|
|
|
#endif
|
|
|
|
char *key;
|
|
#ifdef MODULE_LOCAL
|
|
key = hash_key(server_ctx->remote_addr->sa_family, &src_addr);
|
|
#else
|
|
key = hash_key(dst_addr.ss_family, &src_addr);
|
|
#endif
|
|
|
|
struct cache *conn_cache;
|
|
conn_cache = server_ctx->conn_cache;
|
|
|
|
remote_ctx_t *remote_ctx;
|
|
remote_ctx = NULL;
|
|
cache_lookup(conn_cache, key, HASH_KEY_LEN, (void *)&remote_ctx);
|
|
|
|
if (remote_ctx != NULL) {
|
|
if (sockaddr_cmp(&src_addr, &remote_ctx->src_addr, sizeof(src_addr))) {
|
|
remote_ctx = NULL;
|
|
}
|
|
}
|
|
|
|
// reset the timer
|
|
if (remote_ctx != NULL) {
|
|
ss_timer_again(SS_A_ & remote_ctx->watcher);
|
|
}
|
|
|
|
if (remote_ctx == NULL) {
|
|
if (verbose) {
|
|
#ifdef MODULE_REDIR
|
|
char src[SS_ADDRSTRLEN];
|
|
char dst[SS_ADDRSTRLEN];
|
|
strcpy(src, get_addr_str((struct sockaddr *)&src_addr, true));
|
|
strcpy(dst, get_addr_str((struct sockaddr *)&dst_addr, true));
|
|
LOGI("[%s] [udp] cache miss: %s <-> %s", s_port, dst, src);
|
|
#else
|
|
LOGI("[%s] [udp] cache miss: %s:%s <-> %s", s_port, host, port,
|
|
get_addr_str((struct sockaddr *)&src_addr, true));
|
|
#endif
|
|
}
|
|
} else {
|
|
if (verbose) {
|
|
#ifdef MODULE_REDIR
|
|
char src[SS_ADDRSTRLEN];
|
|
char dst[SS_ADDRSTRLEN];
|
|
strcpy(src, get_addr_str((struct sockaddr *)&src_addr, true));
|
|
strcpy(dst, get_addr_str((struct sockaddr *)&dst_addr, true));
|
|
LOGI("[%s] [udp] cache hit: %s <-> %s", s_port, dst, src);
|
|
#else
|
|
LOGI("[%s] [udp] cache hit: %s:%s <-> %s", s_port, host, port,
|
|
get_addr_str((struct sockaddr *)&src_addr, true));
|
|
#endif
|
|
}
|
|
}
|
|
|
|
#ifdef MODULE_LOCAL
|
|
|
|
#if !defined(MODULE_TUNNEL) && !defined(MODULE_REDIR)
|
|
if (frag) {
|
|
LOGE("[udp] drop a message since frag is not 0, but %d", frag);
|
|
goto CLEAN_UP;
|
|
}
|
|
#endif
|
|
|
|
const struct sockaddr *remote_addr;
|
|
int remote_addr_len;
|
|
remote_addr = server_ctx->remote_addr;
|
|
remote_addr_len = server_ctx->remote_addr_len;
|
|
|
|
if (remote_ctx == NULL) {
|
|
// Bind to any port
|
|
int remotefd = create_remote_socket(remote_addr->sa_family == AF_INET6);
|
|
if (remotefd < 0) {
|
|
ERROR("[udp] udprelay bind() error");
|
|
goto CLEAN_UP;
|
|
}
|
|
setnonblocking(remotefd);
|
|
|
|
#ifdef SO_NOSIGPIPE
|
|
set_nosigpipe(remotefd);
|
|
#endif
|
|
#ifdef IP_TOS
|
|
// Set QoS flag
|
|
int tos = 46 << 2;
|
|
int rc = ss_setsockopt(remotefd, IPPROTO_IP, IP_TOS, &tos, sizeof(tos));
|
|
if (rc < 0 && ss_socket_error() != ENOPROTOOPT) {
|
|
LOGE("setting ipv4 dscp failed: %d", ss_socket_error());
|
|
}
|
|
#ifdef IPV6_TCLASS
|
|
rc = ss_setsockopt(remotefd, IPPROTO_IPV6, IPV6_TCLASS, &tos, sizeof(tos));
|
|
if (rc < 0 && ss_socket_error() != ENOPROTOOPT) {
|
|
LOGE("setting ipv6 dscp failed: %d", ss_socket_error());
|
|
}
|
|
#endif
|
|
#endif
|
|
#ifdef SET_INTERFACE
|
|
if (server_ctx->iface) {
|
|
if (setinterface(remotefd, server_ctx->iface) == -1)
|
|
ERROR("setinterface");
|
|
}
|
|
#endif
|
|
|
|
#ifdef __ANDROID__
|
|
if (vpn) {
|
|
if (protect_socket(remotefd) == -1) {
|
|
ERROR("protect_socket");
|
|
ss_socket_close(remotefd);
|
|
goto CLEAN_UP;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
// Init remote_ctx
|
|
remote_ctx = new_remote(remotefd, server_ctx);
|
|
remote_ctx->src_addr = src_addr;
|
|
remote_ctx->af = remote_addr->sa_family;
|
|
|
|
// Add to conn cache
|
|
cache_insert(conn_cache, key, HASH_KEY_LEN, (void *)remote_ctx);
|
|
|
|
// Start remote io
|
|
ss_io_start(SS_A_ & remote_ctx->io);
|
|
ss_timer_start(SS_A_ & remote_ctx->watcher);
|
|
}
|
|
|
|
if (offset > 0) {
|
|
buf->len -= offset;
|
|
memmove(buf->data, buf->data + offset, buf->len);
|
|
}
|
|
|
|
int err;
|
|
err = server_ctx->crypto->encrypt_udp != NULL
|
|
? server_ctx->crypto->encrypt_udp(buf, server_ctx->crypto->cipher,
|
|
buf_size, &remote_ctx->udp_session)
|
|
: server_ctx->crypto->encrypt_all(buf, server_ctx->crypto->cipher, buf_size);
|
|
|
|
if (err) {
|
|
// drop the packet silently
|
|
goto CLEAN_UP;
|
|
}
|
|
|
|
if (buf->len > packet_size) {
|
|
if (verbose) {
|
|
LOGI("[udp] server_recv_sendto fragmentation, MTU at least be: " SIZE_FMT, buf->len + PACKET_HEADER_SIZE);
|
|
}
|
|
}
|
|
|
|
int s;
|
|
s = sendto(remote_ctx->fd, buf->data, buf->len, 0, remote_addr, remote_addr_len);
|
|
|
|
if (s == -1) {
|
|
ERROR("[udp] server_recv_sendto");
|
|
}
|
|
|
|
#else
|
|
|
|
int cache_hit;
|
|
cache_hit = 0;
|
|
int need_query;
|
|
need_query = 0;
|
|
|
|
char *addr_header;
|
|
addr_header = buf->data + offset;
|
|
|
|
if (buf->len - addr_header_len > packet_size) {
|
|
if (verbose) {
|
|
LOGI("[udp] server_recv_sendto fragmentation, MTU at least be: " SIZE_FMT,
|
|
buf->len - addr_header_len + PACKET_HEADER_SIZE);
|
|
}
|
|
}
|
|
|
|
if (remote_ctx != NULL) {
|
|
cache_hit = 1;
|
|
if (dst_addr.ss_family != AF_INET && dst_addr.ss_family != AF_INET6) {
|
|
need_query = 1;
|
|
}
|
|
} else {
|
|
if (dst_addr.ss_family == AF_INET || dst_addr.ss_family == AF_INET6) {
|
|
int remotefd = create_remote_socket(dst_addr.ss_family == AF_INET6);
|
|
if (remotefd != -1) {
|
|
setnonblocking(remotefd);
|
|
#ifdef SO_BROADCAST
|
|
set_broadcast(remotefd);
|
|
#endif
|
|
#ifdef SO_NOSIGPIPE
|
|
set_nosigpipe(remotefd);
|
|
#endif
|
|
#ifdef IP_TOS
|
|
// Set QoS flag
|
|
int tos = 46 << 2;
|
|
int rc = ss_setsockopt(remotefd, IPPROTO_IP, IP_TOS, &tos, sizeof(tos));
|
|
if (rc < 0 && ss_socket_error() != ENOPROTOOPT) {
|
|
LOGE("setting ipv4 dscp failed: %d", ss_socket_error());
|
|
}
|
|
#ifdef IPV6_TCLASS
|
|
rc = ss_setsockopt(remotefd, IPPROTO_IPV6, IPV6_TCLASS, &tos, sizeof(tos));
|
|
if (rc < 0 && ss_socket_error() != ENOPROTOOPT) {
|
|
LOGE("setting ipv6 dscp failed: %d", ss_socket_error());
|
|
}
|
|
#endif
|
|
#endif
|
|
#ifdef SET_INTERFACE
|
|
if (server_ctx->iface) {
|
|
if (setinterface(remotefd, server_ctx->iface) == -1)
|
|
ERROR("setinterface");
|
|
}
|
|
#endif
|
|
remote_ctx = new_remote(remotefd, server_ctx);
|
|
remote_ctx->src_addr = src_addr;
|
|
remote_ctx->server_ctx = server_ctx;
|
|
memcpy(&remote_ctx->dst_addr, &dst_addr, sizeof(struct sockaddr_storage));
|
|
} else {
|
|
ERROR("[udp] bind() error");
|
|
goto CLEAN_UP;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (remote_ctx != NULL) {
|
|
// Attach the AEAD-2022 session so replies are encrypted with it
|
|
remote_ctx->udp_session = udp_session;
|
|
}
|
|
|
|
if (remote_ctx != NULL && !need_query) {
|
|
size_t addr_len = get_sockaddr_len((struct sockaddr *)&dst_addr);
|
|
int s = sendto(remote_ctx->fd, buf->data + addr_header_len,
|
|
buf->len - addr_header_len, 0,
|
|
(struct sockaddr *)&dst_addr, addr_len);
|
|
|
|
if (s == -1) {
|
|
ERROR("[udp] sendto_remote");
|
|
if (!cache_hit) {
|
|
close_and_free_remote(SS_A_ remote_ctx);
|
|
}
|
|
} else {
|
|
if (!cache_hit) {
|
|
// Add to conn cache
|
|
remote_ctx->af = dst_addr.ss_family;
|
|
char *key = hash_key(remote_ctx->af, &remote_ctx->src_addr);
|
|
cache_insert(server_ctx->conn_cache, key, HASH_KEY_LEN, (void *)remote_ctx);
|
|
|
|
ss_io_start(SS_A_ & remote_ctx->io);
|
|
ss_timer_start(SS_A_ & remote_ctx->watcher);
|
|
}
|
|
}
|
|
} else {
|
|
struct query_ctx *query_ctx = new_query_ctx(buf->data + addr_header_len,
|
|
buf->len - addr_header_len);
|
|
query_ctx->server_ctx = server_ctx;
|
|
query_ctx->addr_header_len = addr_header_len;
|
|
query_ctx->src_addr = src_addr;
|
|
memcpy(query_ctx->addr_header, addr_header, addr_header_len);
|
|
|
|
if (need_query) {
|
|
query_ctx->remote_ctx = remote_ctx;
|
|
}
|
|
|
|
int port_num = 0;
|
|
if (ss_parse_int(port, 0, UINT16_MAX, &port_num) == -1) {
|
|
LOGE("[udp] invalid target port");
|
|
resolv_free_cb(query_ctx);
|
|
goto CLEAN_UP;
|
|
}
|
|
resolv_start(host, htons((uint16_t)port_num), resolv_cb, resolv_free_cb, query_ctx);
|
|
}
|
|
#endif
|
|
|
|
CLEAN_UP:
|
|
bfree(buf);
|
|
ss_free(buf);
|
|
}
|
|
|
|
void
|
|
free_cb(void *key, void *element)
|
|
{
|
|
remote_ctx_t *remote_ctx = (remote_ctx_t *)element;
|
|
|
|
if (verbose) {
|
|
LOGI("[udp] one connection freed");
|
|
}
|
|
|
|
close_and_free_remote(SS_DEFAULT, remote_ctx);
|
|
}
|
|
|
|
int
|
|
init_udprelay(const char *server_host, const char *server_port,
|
|
#ifdef MODULE_LOCAL
|
|
const struct sockaddr *remote_addr, const int remote_addr_len,
|
|
#ifdef MODULE_TUNNEL
|
|
const ss_addr_t tunnel_addr,
|
|
#endif
|
|
#endif
|
|
int mtu, crypto_t *crypto, int timeout, const char *iface)
|
|
{
|
|
s_port = server_port;
|
|
// Initialize ev loop
|
|
struct ss_loop *loop = SS_DEFAULT;
|
|
|
|
// Initialize MTU
|
|
if (mtu > 0) {
|
|
if (mtu <= PACKET_HEADER_SIZE) {
|
|
LOGE("MTU must be greater than %d", PACKET_HEADER_SIZE);
|
|
return -1;
|
|
}
|
|
packet_size = mtu - PACKET_HEADER_SIZE;
|
|
}
|
|
|
|
// ////////////////////////////////////////////////
|
|
// Setup server context
|
|
|
|
// Bind to port
|
|
int serverfd = create_server_socket(server_host, server_port);
|
|
if (serverfd < 0) {
|
|
return -1;
|
|
}
|
|
setnonblocking(serverfd);
|
|
|
|
// Initialize cache
|
|
struct cache *conn_cache;
|
|
cache_create(&conn_cache, MAX_UDP_CONN_NUM, free_cb);
|
|
|
|
server_ctx_t *server_ctx = new_server_ctx(serverfd);
|
|
#ifdef MODULE_REMOTE
|
|
server_ctx->loop = loop;
|
|
#endif
|
|
server_ctx->timeout = max(timeout, MIN_UDP_TIMEOUT);
|
|
server_ctx->crypto = crypto;
|
|
server_ctx->iface = iface;
|
|
server_ctx->conn_cache = conn_cache;
|
|
#ifdef MODULE_LOCAL
|
|
server_ctx->remote_addr = remote_addr;
|
|
server_ctx->remote_addr_len = remote_addr_len;
|
|
#ifdef MODULE_TUNNEL
|
|
server_ctx->tunnel_addr = tunnel_addr;
|
|
#endif
|
|
#endif
|
|
|
|
ss_io_start(loop, &server_ctx->io);
|
|
|
|
server_ctx_list[server_num++] = server_ctx;
|
|
|
|
return serverfd;
|
|
}
|
|
|
|
void
|
|
free_udprelay()
|
|
{
|
|
struct ss_loop *loop = SS_DEFAULT;
|
|
while (server_num > 0) {
|
|
server_ctx_t *server_ctx = server_ctx_list[--server_num];
|
|
ss_io_stop(loop, &server_ctx->io);
|
|
ss_socket_close(server_ctx->fd);
|
|
cache_delete(server_ctx->conn_cache, 0);
|
|
#ifdef MODULE_LOCAL
|
|
free((char*) server_ctx->remote_addr);
|
|
#endif
|
|
ss_free(server_ctx);
|
|
server_ctx_list[server_num] = NULL;
|
|
}
|
|
}
|