mirror of
https://github.com/hufrea/byedpi.git
synced 2024-11-22 06:55:22 +03:00
598 lines
14 KiB
C
598 lines
14 KiB
C
#include "extend.h"
|
|
|
|
#ifdef _WIN32
|
|
#include <ws2tcpip.h>
|
|
|
|
#ifndef TCP_MAXRT
|
|
#define TCP_MAXRT 5
|
|
#endif
|
|
#else
|
|
#include <arpa/inet.h>
|
|
#include <netinet/tcp.h>
|
|
#include <sys/un.h>
|
|
#include <sys/time.h>
|
|
#endif
|
|
|
|
#include <string.h>
|
|
#include <assert.h>
|
|
|
|
#include "proxy.h"
|
|
#include "error.h"
|
|
#include "params.h"
|
|
|
|
#include "desync.h"
|
|
#include "packets.h"
|
|
|
|
#define KEY_SIZE sizeof(struct sockaddr_ina)
|
|
|
|
|
|
static int set_timeout(int fd, unsigned int s)
|
|
{
|
|
#ifdef __linux__
|
|
if (setsockopt(fd, IPPROTO_TCP,
|
|
TCP_USER_TIMEOUT, (char *)&s, sizeof(s))) {
|
|
uniperror("setsockopt TCP_USER_TIMEOUT");
|
|
return -1;
|
|
}
|
|
#else
|
|
#ifdef _WIN32
|
|
if (setsockopt(fd, IPPROTO_TCP,
|
|
TCP_MAXRT, (char *)&s, sizeof(s))) {
|
|
uniperror("setsockopt TCP_MAXRT");
|
|
return -1;
|
|
}
|
|
#endif
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
|
|
static ssize_t serialize_addr(const struct sockaddr_ina *dst,
|
|
uint8_t *const out, const size_t out_len)
|
|
{
|
|
#define serialize(raw, field, len, counter){ \
|
|
const size_t size = sizeof(field); \
|
|
if ((counter + size) <= len) { \
|
|
memcpy(raw + counter, &(field), size); \
|
|
counter += size; \
|
|
} else return 0; \
|
|
}
|
|
size_t c = 0;
|
|
serialize(out, dst->in.sin_port, out_len, c);
|
|
serialize(out, dst->sa.sa_family, out_len, c);
|
|
|
|
if (dst->sa.sa_family == AF_INET) {
|
|
serialize(out, dst->in.sin_addr, out_len, c);
|
|
} else {
|
|
serialize(out, dst->in6.sin6_addr, out_len, c);
|
|
}
|
|
#undef serialize
|
|
|
|
return c;
|
|
}
|
|
|
|
|
|
static int cache_get(struct sockaddr_ina *dst)
|
|
{
|
|
uint8_t key[KEY_SIZE] = { 0 };
|
|
int len = serialize_addr(dst, key, sizeof(key));
|
|
|
|
struct elem *val = mem_get(params.mempool, (char *)key, len);
|
|
if (!val) {
|
|
return -1;
|
|
}
|
|
time_t t = time(0);
|
|
if (t > val->time + params.cache_ttl) {
|
|
LOG(LOG_S, "time=%jd, now=%jd, ignore\n", (intmax_t)val->time, (intmax_t)t);
|
|
return 0;
|
|
}
|
|
return val->m;
|
|
}
|
|
|
|
|
|
static int cache_add(struct sockaddr_ina *dst, int m)
|
|
{
|
|
assert(m >= 0 && m < params.dp_count);
|
|
|
|
uint8_t key[KEY_SIZE] = { 0 };
|
|
int len = serialize_addr(dst, key, sizeof(key));
|
|
|
|
INIT_ADDR_STR((*dst));
|
|
if (m == 0) {
|
|
LOG(LOG_S, "delete ip: %s\n", ADDR_STR);
|
|
mem_delete(params.mempool, (char *)key, len);
|
|
return 0;
|
|
}
|
|
LOG(LOG_S, "save ip: %s, m=%d\n", ADDR_STR, m);
|
|
time_t t = time(0);
|
|
|
|
struct elem *val = mem_add(params.mempool, (char *)key, len);
|
|
if (!val) {
|
|
uniperror("mem_add");
|
|
return -1;
|
|
}
|
|
val->m = m;
|
|
val->time = t;
|
|
return 0;
|
|
}
|
|
|
|
|
|
static inline bool check_port(uint16_t *p, struct sockaddr_in6 *dst)
|
|
{
|
|
return (dst->sin6_port >= p[0]
|
|
&& dst->sin6_port <= p[1]);
|
|
}
|
|
|
|
|
|
int connect_hook(struct poolhd *pool, struct eval *val,
|
|
struct sockaddr_ina *dst, int next)
|
|
{
|
|
int m = cache_get(dst);
|
|
val->cache = (m == 0);
|
|
val->attempt = m < 0 ? 0 : m;
|
|
|
|
return create_conn(pool, val, dst, next);
|
|
}
|
|
|
|
|
|
int socket_mod(int fd, struct sockaddr *dst)
|
|
{
|
|
if (params.custom_ttl) {
|
|
if (setttl(fd, params.def_ttl) < 0) {
|
|
return -1;
|
|
}
|
|
}
|
|
if (params.protect_path) {
|
|
return protect(fd, params.protect_path);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int reconnect(struct poolhd *pool, struct eval *val, int m)
|
|
{
|
|
assert(val->flag == FLAG_CONN);
|
|
|
|
struct eval *client = val->pair;
|
|
|
|
if (create_conn(pool, client,
|
|
(struct sockaddr_ina *)&val->in6, EV_FIRST_TUNNEL)) {
|
|
return -1;
|
|
}
|
|
val->pair = 0;
|
|
del_event(pool, val);
|
|
|
|
//client->type = EV_IGNORE;
|
|
client->attempt = m;
|
|
client->cache = 1;
|
|
client->buff.offset = 0;
|
|
client->round_sent = 0;
|
|
return 0;
|
|
}
|
|
|
|
|
|
static bool check_host(struct mphdr *hosts, char *buffer, ssize_t n)
|
|
{
|
|
char *host = 0;
|
|
int len;
|
|
if (!(len = parse_tls(buffer, n, &host))) {
|
|
len = parse_http(buffer, n, &host, 0);
|
|
}
|
|
assert(len == 0 || host != 0);
|
|
if (len <= 0) {
|
|
return 0;
|
|
}
|
|
char *e = host + len;
|
|
for (; host < e; host++) {
|
|
if (mem_get(hosts, host, e - host)) {
|
|
return 1;
|
|
}
|
|
if (!(host = memchr(host, '.', e - host))) {
|
|
return 0;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
static bool check_proto_tcp(int proto, char *buffer, ssize_t n)
|
|
{
|
|
if (proto & IS_TCP) {
|
|
return 1;
|
|
}
|
|
else if ((proto & IS_HTTP) &&
|
|
is_http(buffer, n)) {
|
|
return 1;
|
|
}
|
|
else if ((proto & IS_HTTPS) &&
|
|
is_tls_chello(buffer, n)) {
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
static bool check_round(int nr[2], int r)
|
|
{
|
|
return (!nr[1] && r <= 1) || (r >= nr[0] && r <= nr[1]);
|
|
}
|
|
|
|
|
|
static int on_trigger(int type, struct poolhd *pool, struct eval *val)
|
|
{
|
|
int m = val->pair->attempt + 1;
|
|
|
|
bool can_reconn = (
|
|
val->pair->buff.data && !val->recv_count
|
|
&& params.auto_level > AUTO_NOBUFF
|
|
);
|
|
if (!can_reconn && params.auto_level <= AUTO_NOSAVE) {
|
|
return -1;
|
|
}
|
|
for (; m < params.dp_count; m++) {
|
|
struct desync_params *dp = ¶ms.dp[m];
|
|
if (!dp->detect) {
|
|
break;
|
|
}
|
|
if (!(dp->detect & type)) {
|
|
continue;
|
|
}
|
|
if (can_reconn) {
|
|
return reconnect(pool, val, m);
|
|
}
|
|
cache_add(
|
|
(struct sockaddr_ina *)&val->in6, m);
|
|
break;
|
|
}
|
|
if (m >= params.dp_count && m > 1) {
|
|
cache_add(
|
|
(struct sockaddr_ina *)&val->in6, 0);
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
|
|
static int on_torst(struct poolhd *pool, struct eval *val)
|
|
{
|
|
if (on_trigger(DETECT_TORST, pool, val) == 0) {
|
|
return 0;
|
|
}
|
|
struct linger l = { .l_onoff = 1 };
|
|
if (setsockopt(val->pair->fd, SOL_SOCKET,
|
|
SO_LINGER, (char *)&l, sizeof(l)) < 0) {
|
|
uniperror("setsockopt SO_LINGER");
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
|
|
static int on_fin(struct poolhd *pool, struct eval *val)
|
|
{
|
|
if (!(val->pair->mark && val->round_count <= 1)) {
|
|
return -1;
|
|
}
|
|
if (on_trigger(DETECT_TLS_ERR, pool, val) == 0) {
|
|
return 0;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
|
|
static int on_response(struct poolhd *pool, struct eval *val,
|
|
char *resp, ssize_t sn)
|
|
{
|
|
int m = val->pair->attempt + 1;
|
|
|
|
char *req = val->pair->buff.data;
|
|
ssize_t qn = val->pair->buff.size;
|
|
|
|
for (; m < params.dp_count; m++) {
|
|
struct desync_params *dp = ¶ms.dp[m];
|
|
if (!dp->detect) {
|
|
return -1;
|
|
}
|
|
if ((dp->detect & DETECT_HTTP_LOCAT)
|
|
&& is_http_redirect(req, qn, resp, sn)) {
|
|
break;
|
|
}
|
|
else if ((dp->detect & DETECT_TLS_ERR)
|
|
&& ((is_tls_chello(req, qn) && !is_tls_shello(resp, sn))
|
|
|| neq_tls_sid(req, qn, resp, sn))) {
|
|
break;
|
|
}
|
|
}
|
|
if (m < params.dp_count) {
|
|
return reconnect(pool, val, m);
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
|
|
static inline void free_first_req(struct eval *client)
|
|
{
|
|
client->type = EV_TUNNEL;
|
|
client->pair->type = EV_TUNNEL;
|
|
|
|
assert(client->buff.data);
|
|
free(client->buff.data);
|
|
memset(&client->buff, 0, sizeof(client->buff));
|
|
}
|
|
|
|
|
|
static int setup_conn(struct eval *client, char *buffer, ssize_t n)
|
|
{
|
|
int m = client->attempt;
|
|
|
|
if (!m) for (; m < params.dp_count; m++) {
|
|
struct desync_params *dp = ¶ms.dp[m];
|
|
if (!dp->detect &&
|
|
(!dp->pf[0] || check_port(dp->pf, &client->pair->in6)) &&
|
|
(!dp->proto || check_proto_tcp(dp->proto, buffer, n)) &&
|
|
(!dp->hosts || check_host(dp->hosts, buffer, n))) {
|
|
break;
|
|
}
|
|
}
|
|
if (m >= params.dp_count) {
|
|
LOG(LOG_E, "drop connection (m=%d)\n", m);
|
|
return -1;
|
|
}
|
|
if (params.auto_level > AUTO_NOBUFF && params.dp_count > 1) {
|
|
client->mark = is_tls_chello(buffer, n);
|
|
}
|
|
client->attempt = m;
|
|
|
|
if (params.timeout
|
|
&& set_timeout(client->pair->fd, params.timeout)) {
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int cancel_setup(struct eval *remote)
|
|
{
|
|
if (params.timeout && params.auto_level <= AUTO_NOSAVE &&
|
|
set_timeout(remote->fd, 0)) {
|
|
return -1;
|
|
}
|
|
if (post_desync(remote->fd, remote->pair->attempt)) {
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
int send_saved_req(struct poolhd *pool,
|
|
struct eval *client, char *buffer, ssize_t bfsize)
|
|
{
|
|
ssize_t offset = client->buff.offset;
|
|
ssize_t n = client->buff.size - offset;
|
|
assert(bfsize >= n);
|
|
memcpy(buffer, client->buff.data + offset, n);
|
|
|
|
ssize_t sn = tcp_send_hook(client->pair, buffer, bfsize, n);
|
|
if (sn < 0) {
|
|
return -1;
|
|
}
|
|
client->buff.offset += sn;
|
|
if (sn < n) {
|
|
if (mod_etype(pool, client->pair, POLLOUT) ||
|
|
mod_etype(pool, client, 0)) {
|
|
uniperror("mod_etype");
|
|
return -1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
int on_first_tunnel(struct poolhd *pool,
|
|
struct eval *val, char *buffer, ssize_t bfsize, int etype)
|
|
{
|
|
if ((etype & POLLOUT) && val->flag == FLAG_CONN) {
|
|
if (mod_etype(pool, val, POLLIN) ||
|
|
mod_etype(pool, val->pair, POLLIN)) {
|
|
uniperror("mod_etype");
|
|
return -1;
|
|
}
|
|
return send_saved_req(pool, val->pair, buffer, bfsize);
|
|
}
|
|
ssize_t n = tcp_recv_hook(pool, val, buffer, bfsize);
|
|
if (n < 1) {
|
|
return n;
|
|
}
|
|
if (val->flag != FLAG_CONN) {
|
|
val->buff.size += n;
|
|
|
|
if (val->buff.size >= bfsize) {
|
|
free_first_req(val);
|
|
}
|
|
else {
|
|
val->buff.data = realloc(val->buff.data, val->buff.size);
|
|
|
|
if (val->buff.data == 0) {
|
|
uniperror("realloc");
|
|
return -1;
|
|
}
|
|
memcpy(val->buff.data + val->buff.size - n, buffer, n);
|
|
return send_saved_req(pool, val, buffer, bfsize);
|
|
}
|
|
}
|
|
else {
|
|
if (on_response(pool, val, buffer, n) == 0) {
|
|
return 0;
|
|
}
|
|
free_first_req(val->pair);
|
|
int m = val->pair->attempt;
|
|
|
|
if (val->pair->cache &&
|
|
cache_add((struct sockaddr_ina *)&val->in6, m) < 0) {
|
|
return -1;
|
|
}
|
|
}
|
|
if (tcp_send_hook(val->pair, buffer, bfsize, n) < n) {
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
ssize_t tcp_send_hook(struct eval *remote,
|
|
char *buffer, size_t bfsize, ssize_t n)
|
|
{
|
|
ssize_t sn = -1;
|
|
int skip = remote->flag != FLAG_CONN;
|
|
|
|
if (!skip) {
|
|
struct eval *client = remote->pair;
|
|
|
|
if (client->recv_count == n
|
|
&& setup_conn(client, buffer, n) < 0) {
|
|
return -1;
|
|
}
|
|
int m = client->attempt, r = client->round_count;
|
|
if (!check_round(params.dp[m].rounds, r)) {
|
|
skip = 1;
|
|
}
|
|
else {
|
|
LOG((m ? LOG_S : LOG_L), "desync TCP, m=%d, r=%d\n", m, r);
|
|
|
|
ssize_t offset = remote->pair->round_sent;
|
|
if (!offset && remote->round_count) offset = -1;
|
|
|
|
sn = desync(remote->fd, buffer, bfsize, n,
|
|
offset, (struct sockaddr *)&remote->in6, m);
|
|
}
|
|
}
|
|
if (skip) {
|
|
sn = send(remote->fd, buffer, n, 0);
|
|
if (sn < 0 && get_e() == EAGAIN) {
|
|
return 0;
|
|
}
|
|
}
|
|
remote->pair->round_sent += sn;
|
|
return sn;
|
|
}
|
|
|
|
|
|
ssize_t tcp_recv_hook(struct poolhd *pool, struct eval *val,
|
|
char *buffer, size_t bfsize)
|
|
{
|
|
ssize_t n = recv(val->fd, buffer, bfsize, 0);
|
|
if (n < 1) {
|
|
if (!n) {
|
|
if (val->flag != FLAG_CONN) {
|
|
val = val->pair;
|
|
}
|
|
return on_fin(pool, val);
|
|
}
|
|
if (get_e() == EAGAIN) {
|
|
return 0;
|
|
}
|
|
uniperror("recv");
|
|
switch (get_e()) {
|
|
case ECONNRESET:
|
|
case ECONNREFUSED:
|
|
case ETIMEDOUT:
|
|
if (val->flag == FLAG_CONN)
|
|
return on_torst(pool, val);
|
|
else
|
|
return on_fin(pool, val->pair);
|
|
}
|
|
return -1;
|
|
}
|
|
val->recv_count += n;
|
|
if (val->round_sent == 0) {
|
|
val->round_count++;
|
|
val->pair->round_sent = 0;
|
|
}
|
|
if (val->flag == FLAG_CONN
|
|
&& check_round(
|
|
params.dp[val->pair->attempt].rounds, val->round_count)
|
|
&& cancel_setup(val)) {
|
|
return -1;
|
|
}
|
|
return n;
|
|
}
|
|
|
|
|
|
ssize_t udp_hook(struct eval *val,
|
|
char *buffer, size_t bfsize, ssize_t n, struct sockaddr_ina *dst)
|
|
{
|
|
struct eval *pair = val->pair->pair;
|
|
|
|
int m = pair->attempt, r = pair->round_count;
|
|
if (!m) {
|
|
for (; m < params.dp_count; m++) {
|
|
struct desync_params *dp = ¶ms.dp[m];
|
|
if (!dp->detect &&
|
|
(!dp->proto || (dp->proto & IS_UDP)) &&
|
|
(!dp->pf[0] || check_port(dp->pf, &dst->in6))) {
|
|
break;
|
|
}
|
|
}
|
|
if (m >= params.dp_count) {
|
|
return -1;
|
|
}
|
|
pair->attempt = m;
|
|
}
|
|
if (!check_round(params.dp[m].rounds, r)) {
|
|
return send(val->fd, buffer, n, 0);
|
|
}
|
|
LOG(LOG_S, "desync UDP, m=%d, r=%d\n", m, r);
|
|
return desync_udp(val->fd, buffer, bfsize, n, &dst->sa, m);
|
|
}
|
|
|
|
|
|
#ifdef __linux__
|
|
int protect(int conn_fd, const char *path)
|
|
{
|
|
struct sockaddr_un sa;
|
|
sa.sun_family = AF_UNIX;
|
|
strcpy(sa.sun_path, path);
|
|
|
|
int fd = socket(AF_UNIX, SOCK_STREAM, 0);
|
|
if (fd < 0) {
|
|
uniperror("socket");
|
|
return -1;
|
|
}
|
|
struct timeval tv = { .tv_sec = 1 };
|
|
setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
|
|
setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv));
|
|
|
|
int err = connect(fd, (struct sockaddr *)&sa, sizeof(sa));
|
|
if (err) {
|
|
uniperror("connect");
|
|
close(fd);
|
|
return -1;
|
|
}
|
|
char buf[CMSG_SPACE(sizeof(fd))] = {};
|
|
struct iovec io = { .iov_base = "1", .iov_len = 1 };
|
|
struct msghdr msg = { .msg_iov = &io };
|
|
|
|
msg.msg_iovlen = 1;
|
|
msg.msg_control = buf;
|
|
msg.msg_controllen = sizeof(buf);
|
|
|
|
struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);
|
|
cmsg->cmsg_level = SOL_SOCKET;
|
|
cmsg->cmsg_type = SCM_RIGHTS;
|
|
cmsg->cmsg_len = CMSG_LEN(sizeof(conn_fd));
|
|
|
|
*((int *)CMSG_DATA(cmsg)) = conn_fd;
|
|
msg.msg_controllen = CMSG_SPACE(sizeof(conn_fd));
|
|
|
|
if (sendmsg(fd, &msg, 0) < 0) {
|
|
uniperror("sendmsg");
|
|
close(fd);
|
|
return -1;
|
|
}
|
|
if (recv(fd, buf, 1, 0) < 1) {
|
|
uniperror("recv");
|
|
close(fd);
|
|
return -1;
|
|
}
|
|
close(fd);
|
|
return 0;
|
|
}
|
|
#endif
|