Truncated history

This commit is contained in:
bol-van
2024-10-28 09:32:24 +03:00
commit 2aaa2f7cf3
300 changed files with 43184 additions and 0 deletions

12
tpws/BSDmakefile Normal file
View File

@@ -0,0 +1,12 @@
CC ?= cc
CFLAGS += -std=gnu99 -s -O3
LIBS = -lz -lpthread
SRC_FILES = *.c
all: tpws
tpws: $(SRC_FILES)
$(CC) $(CFLAGS) -Iepoll-shim/include -o $@ $(SRC_FILES) epoll-shim/src/*.c $(LDFLAGS) $(LIBS)
clean:
rm -f tpws *.o

23
tpws/Makefile Normal file
View File

@@ -0,0 +1,23 @@
CC ?= gcc
CFLAGS += -std=gnu99 -O3
CFLAGS_BSD = -Wno-address-of-packed-member
LIBS = -lz -lpthread
SRC_FILES = *.c
all: tpws
tpws: $(SRC_FILES)
$(CC) -s $(CFLAGS) -o $@ $(SRC_FILES) $(LDFLAGS) $(LIBS)
bsd: $(SRC_FILES)
$(CC) -s $(CFLAGS) $(CFLAGS_BSD) -Iepoll-shim/include -o tpws $(SRC_FILES) epoll-shim/src/*.c $(LDFLAGS) $(LIBS)
mac: $(SRC_FILES)
$(CC) $(CFLAGS) $(CFLAGS_BSD) -Iepoll-shim/include -Imacos -o tpwsa -target arm64-apple-macos10.8 $(SRC_FILES) epoll-shim/src/*.c $(LDFLAGS) $(LIBS)
$(CC) $(CFLAGS) $(CFLAGS_BSD) -Iepoll-shim/include -Imacos -o tpwsx -target x86_64-apple-macos10.8 $(SRC_FILES) epoll-shim/src/*.c $(LDFLAGS) $(LIBS)
strip tpwsa tpwsx
lipo -create -output tpws tpwsx tpwsa
rm -f tpwsx tpwsa
clean:
rm -f tpws *.o

View File

@@ -0,0 +1,80 @@
#ifndef SHIM_SYS_EPOLL_H
#define SHIM_SYS_EPOLL_H
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
#include <sys/types.h>
#include <fcntl.h>
#if defined(__NetBSD__)
#include <sys/sigtypes.h>
#elif defined(__OpenBSD__) || defined(__DragonFly__) || defined(__FreeBSD__) || defined(__APPLE__)
#include <sys/signal.h>
#endif
#define EPOLL_CLOEXEC O_CLOEXEC
#define EPOLL_NONBLOCK O_NONBLOCK
enum EPOLL_EVENTS { __EPOLL_DUMMY };
#define EPOLLIN 0x001
#define EPOLLPRI 0x002
#define EPOLLOUT 0x004
#define EPOLLRDNORM 0x040
#define EPOLLNVAL 0x020
#define EPOLLRDBAND 0x080
#define EPOLLWRNORM 0x100
#define EPOLLWRBAND 0x200
#define EPOLLMSG 0x400
#define EPOLLERR 0x008
#define EPOLLHUP 0x010
#define EPOLLRDHUP 0x2000
#define EPOLLEXCLUSIVE (1U<<28)
#define EPOLLWAKEUP (1U<<29)
#define EPOLLONESHOT (1U<<30)
#define EPOLLET (1U<<31)
#define EPOLL_CTL_ADD 1
#define EPOLL_CTL_DEL 2
#define EPOLL_CTL_MOD 3
typedef union epoll_data {
void *ptr;
int fd;
uint32_t u32;
uint64_t u64;
} epoll_data_t;
struct epoll_event {
uint32_t events;
epoll_data_t data;
}
#ifdef __x86_64__
__attribute__ ((__packed__))
#endif
;
int epoll_create(int);
int epoll_create1(int);
int epoll_ctl(int, int, int, struct epoll_event *);
int epoll_wait(int, struct epoll_event *, int, int);
int epoll_pwait(int, struct epoll_event *, int, int, const sigset_t *);
#ifndef SHIM_SYS_SHIM_HELPERS
#define SHIM_SYS_SHIM_HELPERS
#include <unistd.h> /* IWYU pragma: keep */
extern int epoll_shim_close(int);
#define close epoll_shim_close
#endif
#ifdef __cplusplus
}
#endif
#endif /* sys/epoll.h */

305
tpws/epoll-shim/src/epoll.c Normal file
View File

@@ -0,0 +1,305 @@
#include <sys/epoll.h>
#include <sys/event.h>
#include <sys/param.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <assert.h>
#include <errno.h>
#include <poll.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include "epoll_shim_ctx.h"
#ifdef __NetBSD__
#define ppoll pollts
#endif
// TODO(jan): Remove this once the definition is exposed in <sys/time.h> in
// all supported FreeBSD versions.
#ifndef timespecsub
#define timespecsub(tsp, usp, vsp) \
do { \
(vsp)->tv_sec = (tsp)->tv_sec - (usp)->tv_sec; \
(vsp)->tv_nsec = (tsp)->tv_nsec - (usp)->tv_nsec; \
if ((vsp)->tv_nsec < 0) { \
(vsp)->tv_sec--; \
(vsp)->tv_nsec += 1000000000L; \
} \
} while (0)
#endif
static errno_t
epollfd_close(FDContextMapNode *node)
{
return epollfd_ctx_terminate(&node->ctx.epollfd);
}
static FDContextVTable const epollfd_vtable = {
.read_fun = fd_context_default_read,
.write_fun = fd_context_default_write,
.close_fun = epollfd_close,
};
static FDContextMapNode *
epoll_create_impl(errno_t *ec)
{
FDContextMapNode *node;
node = epoll_shim_ctx_create_node(&epoll_shim_ctx, ec);
if (!node) {
return NULL;
}
node->flags = 0;
if ((*ec = epollfd_ctx_init(&node->ctx.epollfd, /**/
node->fd)) != 0) {
goto fail;
}
node->vtable = &epollfd_vtable;
return node;
fail:
epoll_shim_ctx_remove_node_explicit(&epoll_shim_ctx, node);
(void)fd_context_map_node_destroy(node);
return NULL;
}
static int
epoll_create_common(void)
{
FDContextMapNode *node;
errno_t ec;
node = epoll_create_impl(&ec);
if (!node) {
errno = ec;
return -1;
}
return node->fd;
}
int
epoll_create(int size)
{
if (size <= 0) {
errno = EINVAL;
return -1;
}
return epoll_create_common();
}
int
epoll_create1(int flags)
{
if (flags & ~EPOLL_CLOEXEC) {
errno = EINVAL;
return -1;
}
return epoll_create_common();
}
static errno_t
epoll_ctl_impl(int fd, int op, int fd2, struct epoll_event *ev)
{
if (!ev && op != EPOLL_CTL_DEL) {
return EFAULT;
}
FDContextMapNode *node = epoll_shim_ctx_find_node(&epoll_shim_ctx, fd);
if (!node || node->vtable != &epollfd_vtable) {
struct stat sb;
return (fd < 0 || fstat(fd, &sb) < 0) ? EBADF : EINVAL;
}
return epollfd_ctx_ctl(&node->ctx.epollfd, op, fd2, ev);
}
int
epoll_ctl(int fd, int op, int fd2, struct epoll_event *ev)
{
errno_t ec = epoll_ctl_impl(fd, op, fd2, ev);
if (ec != 0) {
errno = ec;
return -1;
}
return 0;
}
static bool
is_no_wait_deadline(struct timespec const *deadline)
{
return (deadline && deadline->tv_sec == 0 && deadline->tv_nsec == 0);
}
static errno_t
epollfd_ctx_wait_or_block(EpollFDCtx *epollfd, struct epoll_event *ev, int cnt,
int *actual_cnt, struct timespec const *deadline, sigset_t const *sigs)
{
errno_t ec;
for (;;) {
if ((ec = epollfd_ctx_wait(epollfd, /**/
ev, cnt, actual_cnt)) != 0) {
return ec;
}
if (*actual_cnt || is_no_wait_deadline(deadline)) {
return 0;
}
struct timespec timeout;
if (deadline) {
struct timespec current_time;
if (clock_gettime(CLOCK_MONOTONIC, /**/
&current_time) < 0) {
return errno;
}
timespecsub(deadline, &current_time, &timeout);
if (timeout.tv_sec < 0 ||
is_no_wait_deadline(&timeout)) {
return 0;
}
}
(void)pthread_mutex_lock(&epollfd->mutex);
nfds_t nfds = (nfds_t)(1 + epollfd->poll_fds_size);
size_t size;
if (__builtin_mul_overflow(nfds, sizeof(struct pollfd),
&size)) {
ec = ENOMEM;
(void)pthread_mutex_unlock(&epollfd->mutex);
return ec;
}
struct pollfd *pfds = malloc(size);
if (!pfds) {
ec = errno;
(void)pthread_mutex_unlock(&epollfd->mutex);
return ec;
}
epollfd_ctx_fill_pollfds(epollfd, pfds);
(void)pthread_mutex_lock(&epollfd->nr_polling_threads_mutex);
++epollfd->nr_polling_threads;
(void)pthread_mutex_unlock(&epollfd->nr_polling_threads_mutex);
(void)pthread_mutex_unlock(&epollfd->mutex);
/*
* This surfaced a race condition when
* registering/unregistering poll-only fds. The tests should
* still succeed if this is enabled.
*/
#if 0
usleep(500000);
#endif
int n = ppoll(pfds, nfds, deadline ? &timeout : NULL, sigs);
if (n < 0) {
ec = errno;
}
free(pfds);
(void)pthread_mutex_lock(&epollfd->nr_polling_threads_mutex);
--epollfd->nr_polling_threads;
if (epollfd->nr_polling_threads == 0) {
(void)pthread_cond_signal(
&epollfd->nr_polling_threads_cond);
}
(void)pthread_mutex_unlock(&epollfd->nr_polling_threads_mutex);
if (n < 0) {
return ec;
}
}
}
static errno_t
timeout_to_deadline(struct timespec *deadline, int to)
{
assert(to >= 0);
if (to == 0) {
*deadline = (struct timespec){0, 0};
} else if (to > 0) {
if (clock_gettime(CLOCK_MONOTONIC, deadline) < 0) {
return errno;
}
if (__builtin_add_overflow(deadline->tv_sec, to / 1000 + 1,
&deadline->tv_sec)) {
return EINVAL;
}
deadline->tv_sec -= 1;
deadline->tv_nsec += (to % 1000) * 1000000L;
if (deadline->tv_nsec >= 1000000000) {
deadline->tv_nsec -= 1000000000;
deadline->tv_sec += 1;
}
}
return 0;
}
static errno_t
epoll_pwait_impl(int fd, struct epoll_event *ev, int cnt, int to,
sigset_t const *sigs, int *actual_cnt)
{
if (cnt < 1 || cnt > (int)(INT_MAX / sizeof(struct epoll_event))) {
return EINVAL;
}
FDContextMapNode *node = epoll_shim_ctx_find_node(&epoll_shim_ctx, fd);
if (!node || node->vtable != &epollfd_vtable) {
struct stat sb;
return (fd < 0 || fstat(fd, &sb) < 0) ? EBADF : EINVAL;
}
struct timespec deadline;
errno_t ec;
if (to >= 0 && (ec = timeout_to_deadline(&deadline, to)) != 0) {
return ec;
}
return epollfd_ctx_wait_or_block(&node->ctx.epollfd, ev, cnt,
actual_cnt, (to >= 0) ? &deadline : NULL, sigs);
}
int
epoll_pwait(int fd, struct epoll_event *ev, int cnt, int to,
sigset_t const *sigs)
{
int actual_cnt;
errno_t ec = epoll_pwait_impl(fd, ev, cnt, to, sigs, &actual_cnt);
if (ec != 0) {
errno = ec;
return -1;
}
return actual_cnt;
}
int
epoll_wait(int fd, struct epoll_event *ev, int cnt, int to)
{
return epoll_pwait(fd, ev, cnt, to, NULL);
}

View File

@@ -0,0 +1,281 @@
#include "epoll_shim_ctx.h"
#include <sys/event.h>
#include <assert.h>
#include <errno.h>
#include <limits.h>
#include <stdlib.h>
static void
fd_context_map_node_init(FDContextMapNode *node, int kq)
{
node->fd = kq;
node->vtable = NULL;
}
static FDContextMapNode *
fd_context_map_node_create(int kq, errno_t *ec)
{
FDContextMapNode *node;
node = malloc(sizeof(FDContextMapNode));
if (!node) {
*ec = errno;
return NULL;
}
fd_context_map_node_init(node, kq);
return node;
}
static errno_t
fd_context_map_node_terminate(FDContextMapNode *node, bool close_fd)
{
errno_t ec = node->vtable ? node->vtable->close_fun(node) : 0;
if (close_fd && close(node->fd) < 0) {
ec = ec ? ec : errno;
}
return ec;
}
errno_t
fd_context_map_node_destroy(FDContextMapNode *node)
{
errno_t ec = fd_context_map_node_terminate(node, true);
free(node);
return ec;
}
/**/
errno_t
fd_context_default_read(FDContextMapNode *node, /**/
void *buf, size_t nbytes, size_t *bytes_transferred)
{
(void)node;
(void)buf;
(void)nbytes;
(void)bytes_transferred;
return EINVAL;
}
errno_t
fd_context_default_write(FDContextMapNode *node, /**/
void const *buf, size_t nbytes, size_t *bytes_transferred)
{
(void)node;
(void)buf;
(void)nbytes;
(void)bytes_transferred;
return EINVAL;
}
/**/
static int
fd_context_map_node_cmp(FDContextMapNode *e1, FDContextMapNode *e2)
{
return (e1->fd < e2->fd) ? -1 : (e1->fd > e2->fd);
}
RB_PROTOTYPE_STATIC(fd_context_map_, fd_context_map_node_, entry,
fd_context_map_node_cmp);
RB_GENERATE_STATIC(fd_context_map_, fd_context_map_node_, entry,
fd_context_map_node_cmp);
EpollShimCtx epoll_shim_ctx = {
.fd_context_map = RB_INITIALIZER(&fd_context_map),
.mutex = PTHREAD_MUTEX_INITIALIZER,
};
static FDContextMapNode *
epoll_shim_ctx_create_node_impl(EpollShimCtx *epoll_shim_ctx, int kq,
errno_t *ec)
{
FDContextMapNode *node;
{
FDContextMapNode find;
find.fd = kq;
node = RB_FIND(fd_context_map_, /**/
&epoll_shim_ctx->fd_context_map, &find);
}
if (node) {
/*
* If we get here, someone must have already closed the old fd
* with a normal 'close()' call, i.e. not with our
* 'epoll_shim_close()' wrapper. The fd inside the node
* refers now to the new kq we are currently creating. We
* must not close it, but we must clean up the old context
* object!
*/
(void)fd_context_map_node_terminate(node, false);
fd_context_map_node_init(node, kq);
} else {
node = fd_context_map_node_create(kq, ec);
if (!node) {
return NULL;
}
void *colliding_node = RB_INSERT(fd_context_map_,
&epoll_shim_ctx->fd_context_map, node);
(void)colliding_node;
assert(colliding_node == NULL);
}
return node;
}
FDContextMapNode *
epoll_shim_ctx_create_node(EpollShimCtx *epoll_shim_ctx, errno_t *ec)
{
FDContextMapNode *node;
int kq = kqueue();
if (kq < 0) {
*ec = errno;
return NULL;
}
(void)pthread_mutex_lock(&epoll_shim_ctx->mutex);
node = epoll_shim_ctx_create_node_impl(epoll_shim_ctx, kq, ec);
(void)pthread_mutex_unlock(&epoll_shim_ctx->mutex);
if (!node) {
close(kq);
}
return node;
}
static FDContextMapNode *
epoll_shim_ctx_find_node_impl(EpollShimCtx *epoll_shim_ctx, int fd)
{
FDContextMapNode *node;
FDContextMapNode find;
find.fd = fd;
node = RB_FIND(fd_context_map_, /**/
&epoll_shim_ctx->fd_context_map, &find);
return node;
}
FDContextMapNode *
epoll_shim_ctx_find_node(EpollShimCtx *epoll_shim_ctx, int fd)
{
FDContextMapNode *node;
(void)pthread_mutex_lock(&epoll_shim_ctx->mutex);
node = epoll_shim_ctx_find_node_impl(epoll_shim_ctx, fd);
(void)pthread_mutex_unlock(&epoll_shim_ctx->mutex);
return node;
}
FDContextMapNode *
epoll_shim_ctx_remove_node(EpollShimCtx *epoll_shim_ctx, int fd)
{
FDContextMapNode *node;
(void)pthread_mutex_lock(&epoll_shim_ctx->mutex);
node = epoll_shim_ctx_find_node_impl(epoll_shim_ctx, fd);
if (node) {
RB_REMOVE(fd_context_map_, /**/
&epoll_shim_ctx->fd_context_map, node);
}
(void)pthread_mutex_unlock(&epoll_shim_ctx->mutex);
return node;
}
void
epoll_shim_ctx_remove_node_explicit(EpollShimCtx *epoll_shim_ctx,
FDContextMapNode *node)
{
(void)pthread_mutex_lock(&epoll_shim_ctx->mutex);
RB_REMOVE(fd_context_map_, /**/
&epoll_shim_ctx->fd_context_map, node);
(void)pthread_mutex_unlock(&epoll_shim_ctx->mutex);
}
/**/
int
epoll_shim_close(int fd)
{
FDContextMapNode *node;
node = epoll_shim_ctx_remove_node(&epoll_shim_ctx, fd);
if (!node) {
return close(fd);
}
errno_t ec = fd_context_map_node_destroy(node);
if (ec != 0) {
errno = ec;
return -1;
}
return 0;
}
ssize_t
epoll_shim_read(int fd, void *buf, size_t nbytes)
{
FDContextMapNode *node;
node = epoll_shim_ctx_find_node(&epoll_shim_ctx, fd);
if (!node) {
return read(fd, buf, nbytes);
}
if (nbytes > SSIZE_MAX) {
errno = EINVAL;
return -1;
}
size_t bytes_transferred;
errno_t ec = node->vtable->read_fun(node, /**/
buf, nbytes, &bytes_transferred);
if (ec != 0) {
errno = ec;
return -1;
}
return (ssize_t)bytes_transferred;
}
ssize_t
epoll_shim_write(int fd, void const *buf, size_t nbytes)
{
FDContextMapNode *node;
node = epoll_shim_ctx_find_node(&epoll_shim_ctx, fd);
if (!node) {
return write(fd, buf, nbytes);
}
if (nbytes > SSIZE_MAX) {
errno = EINVAL;
return -1;
}
size_t bytes_transferred;
errno_t ec = node->vtable->write_fun(node, /**/
buf, nbytes, &bytes_transferred);
if (ec != 0) {
errno = ec;
return -1;
}
return (ssize_t)bytes_transferred;
}

View File

@@ -0,0 +1,76 @@
#ifndef EPOLL_SHIM_CTX_H_
#define EPOLL_SHIM_CTX_H_
#include "fix.h"
#include <sys/tree.h>
#include <unistd.h>
#include "epollfd_ctx.h"
#include "eventfd_ctx.h"
#include "signalfd_ctx.h"
#include "timerfd_ctx.h"
struct fd_context_map_node_;
typedef struct fd_context_map_node_ FDContextMapNode;
typedef errno_t (*fd_context_read_fun)(FDContextMapNode *node, /**/
void *buf, size_t nbytes, size_t *bytes_transferred);
typedef errno_t (*fd_context_write_fun)(FDContextMapNode *node, /**/
const void *buf, size_t nbytes, size_t *bytes_transferred);
typedef errno_t (*fd_context_close_fun)(FDContextMapNode *node);
typedef struct {
fd_context_read_fun read_fun;
fd_context_write_fun write_fun;
fd_context_close_fun close_fun;
} FDContextVTable;
errno_t fd_context_default_read(FDContextMapNode *node, /**/
void *buf, size_t nbytes, size_t *bytes_transferred);
errno_t fd_context_default_write(FDContextMapNode *node, /**/
void const *buf, size_t nbytes, size_t *bytes_transferred);
struct fd_context_map_node_ {
RB_ENTRY(fd_context_map_node_) entry;
int fd;
int flags;
union {
EpollFDCtx epollfd;
EventFDCtx eventfd;
TimerFDCtx timerfd;
SignalFDCtx signalfd;
} ctx;
FDContextVTable const *vtable;
};
errno_t fd_context_map_node_destroy(FDContextMapNode *node);
/**/
typedef RB_HEAD(fd_context_map_, fd_context_map_node_) FDContextMap;
typedef struct {
FDContextMap fd_context_map;
pthread_mutex_t mutex;
} EpollShimCtx;
extern EpollShimCtx epoll_shim_ctx;
FDContextMapNode *epoll_shim_ctx_create_node(EpollShimCtx *epoll_shim_ctx,
errno_t *ec);
FDContextMapNode *epoll_shim_ctx_find_node(EpollShimCtx *epoll_shim_ctx,
int fd);
FDContextMapNode *epoll_shim_ctx_remove_node(EpollShimCtx *epoll_shim_ctx,
int fd);
void epoll_shim_ctx_remove_node_explicit(EpollShimCtx *epoll_shim_ctx,
FDContextMapNode *node);
/**/
int epoll_shim_close(int fd);
ssize_t epoll_shim_read(int fd, void *buf, size_t nbytes);
ssize_t epoll_shim_write(int fd, void const *buf, size_t nbytes);
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,108 @@
#ifndef EPOLLFD_CTX_H_
#define EPOLLFD_CTX_H_
#include "fix.h"
#define SHIM_SYS_SHIM_HELPERS
#include <sys/epoll.h>
#include <sys/queue.h>
#include <sys/tree.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#include <poll.h>
#include <pthread.h>
struct registered_fds_node_;
typedef struct registered_fds_node_ RegisteredFDsNode;
typedef enum {
EOF_STATE_READ_EOF = 0x01,
EOF_STATE_WRITE_EOF = 0x02,
} EOFState;
typedef enum {
NODE_TYPE_FIFO = 1,
NODE_TYPE_SOCKET = 2,
NODE_TYPE_KQUEUE = 3,
NODE_TYPE_OTHER = 4,
NODE_TYPE_POLL = 5,
} NodeType;
struct registered_fds_node_ {
RB_ENTRY(registered_fds_node_) entry;
TAILQ_ENTRY(registered_fds_node_) pollfd_list_entry;
int fd;
epoll_data_t data;
bool is_registered;
bool has_evfilt_read;
bool has_evfilt_write;
bool has_evfilt_except;
bool got_evfilt_read;
bool got_evfilt_write;
bool got_evfilt_except;
NodeType node_type;
union {
struct {
bool readable;
bool writable;
} fifo;
} node_data;
int eof_state;
bool pollpri_active;
uint16_t events;
uint32_t revents;
bool is_edge_triggered;
bool is_oneshot;
bool is_on_pollfd_list;
int self_pipe[2];
};
typedef TAILQ_HEAD(pollfds_list_, registered_fds_node_) PollFDList;
typedef RB_HEAD(registered_fds_set_, registered_fds_node_) RegisteredFDsSet;
typedef struct {
int kq; // non owning
pthread_mutex_t mutex;
PollFDList poll_fds;
size_t poll_fds_size;
RegisteredFDsSet registered_fds;
size_t registered_fds_size;
struct kevent *kevs;
size_t kevs_length;
struct pollfd *pfds;
size_t pfds_length;
pthread_mutex_t nr_polling_threads_mutex;
pthread_cond_t nr_polling_threads_cond;
unsigned long nr_polling_threads;
int self_pipe[2];
} EpollFDCtx;
errno_t epollfd_ctx_init(EpollFDCtx *epollfd, int kq);
errno_t epollfd_ctx_terminate(EpollFDCtx *epollfd);
void epollfd_ctx_fill_pollfds(EpollFDCtx *epollfd, struct pollfd *pfds);
errno_t epollfd_ctx_ctl(EpollFDCtx *epollfd, int op, int fd2,
struct epoll_event *ev);
errno_t epollfd_ctx_wait(EpollFDCtx *epollfd, struct epoll_event *ev, int cnt,
int *actual_cnt);
#endif

View File

@@ -0,0 +1,31 @@
#ifndef EVENTFD_CTX_H_
#define EVENTFD_CTX_H_
#include "fix.h"
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#include <pthread.h>
#define EVENTFD_CTX_FLAG_SEMAPHORE (1 << 0)
typedef struct {
int kq_; // non owning
int flags_;
pthread_mutex_t mutex_;
bool is_signalled_;
int self_pipe_[2]; // only used if EVFILT_USER is not available
uint_least64_t counter_;
} EventFDCtx;
errno_t eventfd_ctx_init(EventFDCtx *eventfd, int kq, unsigned int counter,
int flags);
errno_t eventfd_ctx_terminate(EventFDCtx *eventfd);
errno_t eventfd_ctx_write(EventFDCtx *eventfd, uint64_t value);
errno_t eventfd_ctx_read(EventFDCtx *eventfd, uint64_t *value);
#endif

19
tpws/epoll-shim/src/fix.c Normal file
View File

@@ -0,0 +1,19 @@
#include "fix.h"
#ifdef __APPLE__
#include <errno.h>
int ppoll(struct pollfd *fds, nfds_t nfds,const struct timespec *tmo_p, const sigset_t *sigmask)
{
// macos does not implement ppoll
// this is a hacky ppoll shim. only for tpws which does not require sigmask
if (sigmask)
{
errno = EINVAL;
return -1;
}
return poll(fds,nfds,tmo_p ? tmo_p->tv_sec*1000 + tmo_p->tv_nsec/1000000 : -1);
}
#endif

20
tpws/epoll-shim/src/fix.h Normal file
View File

@@ -0,0 +1,20 @@
#pragma once
#ifndef _ERRNO_T_DEFINED
#define _ERRNO_T_DEFINED
typedef int errno_t;
#endif
#ifdef __APPLE__
#include <time.h>
#include <signal.h>
#include <poll.h>
struct itimerspec {
struct timespec it_interval;
struct timespec it_value;
};
int ppoll(struct pollfd *fds, nfds_t nfds,const struct timespec *tmo_p, const sigset_t *sigmask);
#endif

View File

@@ -0,0 +1,19 @@
#ifndef SIGNALFD_CTX_H_
#define SIGNALFD_CTX_H_
#include "fix.h"
#include <signal.h>
#include <stdint.h>
#include <stdlib.h>
typedef struct {
int kq; // non owning
} SignalFDCtx;
errno_t signalfd_ctx_init(SignalFDCtx *signalfd, int kq, const sigset_t *sigs);
errno_t signalfd_ctx_terminate(SignalFDCtx *signalfd);
errno_t signalfd_ctx_read(SignalFDCtx *signalfd, uint32_t *ident);
#endif

View File

@@ -0,0 +1,38 @@
#ifndef TIMERFD_CTX_H_
#define TIMERFD_CTX_H_
#include "fix.h"
#include <stdatomic.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#include <pthread.h>
#include <time.h>
typedef struct {
int kq; // non owning
int flags;
pthread_mutex_t mutex;
int clockid;
/*
* Next expiration time, absolute (clock given by clockid).
* If it_interval is != 0, it is a periodic timer.
* If it_value is == 0, the timer is disarmed.
*/
struct itimerspec current_itimerspec;
uint64_t nr_expirations;
} TimerFDCtx;
errno_t timerfd_ctx_init(TimerFDCtx *timerfd, int kq, int clockid);
errno_t timerfd_ctx_terminate(TimerFDCtx *timerfd);
errno_t timerfd_ctx_settime(TimerFDCtx *timerfd, int flags,
struct itimerspec const *new, struct itimerspec *old);
errno_t timerfd_ctx_gettime(TimerFDCtx *timerfd, struct itimerspec *cur);
errno_t timerfd_ctx_read(TimerFDCtx *timerfd, uint64_t *value);
#endif

82
tpws/gzip.c Normal file
View File

@@ -0,0 +1,82 @@
#include "gzip.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define ZCHUNK 16384
#define BUFMIN 128
#define BUFCHUNK (1024*128)
int z_readfile(FILE *F, char **buf, size_t *size)
{
z_stream zs;
int r;
unsigned char in[ZCHUNK];
size_t bufsize;
void *newbuf;
memset(&zs, 0, sizeof(zs));
*buf = NULL;
bufsize = *size = 0;
r = inflateInit2(&zs, 47);
if (r != Z_OK) return r;
do
{
zs.avail_in = fread(in, 1, sizeof(in), F);
if (ferror(F))
{
r = Z_ERRNO;
goto zerr;
}
if (!zs.avail_in) break;
zs.next_in = in;
do
{
if ((bufsize - *size) < BUFMIN)
{
bufsize += BUFCHUNK;
newbuf = *buf ? realloc(*buf, bufsize) : malloc(bufsize);
if (!newbuf)
{
r = Z_MEM_ERROR;
goto zerr;
}
*buf = newbuf;
}
zs.avail_out = bufsize - *size;
zs.next_out = (unsigned char*)(*buf + *size);
r = inflate(&zs, Z_NO_FLUSH);
if (r != Z_OK && r != Z_STREAM_END) goto zerr;
*size = bufsize - zs.avail_out;
} while (r == Z_OK && zs.avail_in);
} while (r == Z_OK);
if (*size < bufsize)
{
// free extra space
if ((newbuf = realloc(*buf, *size))) *buf = newbuf;
}
inflateEnd(&zs);
return Z_OK;
zerr:
inflateEnd(&zs);
if (*buf)
{
free(*buf);
*buf = NULL;
}
return r;
}
bool is_gzip(FILE* F)
{
unsigned char magic[2];
bool b = !fseek(F, 0, SEEK_SET) && fread(magic, 1, 2, F) == 2 && magic[0] == 0x1F && magic[1] == 0x8B;
fseek(F, 0, SEEK_SET);
return b;
}

8
tpws/gzip.h Normal file
View File

@@ -0,0 +1,8 @@
#pragma once
#include <stdio.h>
#include <zlib.h>
#include <stdbool.h>
int z_readfile(FILE *F,char **buf,size_t *size);
bool is_gzip(FILE* F);

289
tpws/helpers.c Normal file
View File

@@ -0,0 +1,289 @@
#define _GNU_SOURCE
#include "helpers.h"
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <ifaddrs.h>
#include <time.h>
#include <sys/stat.h>
char *strncasestr(const char *s,const char *find, size_t slen)
{
char c, sc;
size_t len;
if ((c = *find++) != '\0')
{
len = strlen(find);
do
{
do
{
if (slen-- < 1 || (sc = *s++) == '\0') return NULL;
} while (toupper(c) != toupper(sc));
if (len > slen) return NULL;
} while (strncasecmp(s, find, len) != 0);
s--;
}
return (char *)s;
}
bool append_to_list_file(const char *filename, const char *s)
{
FILE *F = fopen(filename,"at");
if (!F) return false;
bool bOK = fprintf(F,"%s\n",s)>0;
fclose(F);
return bOK;
}
void ntop46(const struct sockaddr *sa, char *str, size_t len)
{
if (!len) return;
*str=0;
switch (sa->sa_family)
{
case AF_INET:
inet_ntop(sa->sa_family, &((struct sockaddr_in*)sa)->sin_addr, str, len);
break;
case AF_INET6:
inet_ntop(sa->sa_family, &((struct sockaddr_in6*)sa)->sin6_addr, str, len);
break;
default:
snprintf(str,len,"UNKNOWN_FAMILY_%d",sa->sa_family);
}
}
void ntop46_port(const struct sockaddr *sa, char *str, size_t len)
{
char ip[40];
ntop46(sa,ip,sizeof(ip));
switch (sa->sa_family)
{
case AF_INET:
snprintf(str,len,"%s:%u",ip,ntohs(((struct sockaddr_in*)sa)->sin_port));
break;
case AF_INET6:
snprintf(str,len,"[%s]:%u",ip,ntohs(((struct sockaddr_in6*)sa)->sin6_port));
break;
default:
snprintf(str,len,"%s",ip);
}
}
void print_sockaddr(const struct sockaddr *sa)
{
char ip_port[48];
ntop46_port(sa,ip_port,sizeof(ip_port));
printf("%s",ip_port);
}
// -1 = error, 0 = not local, 1 = local
bool check_local_ip(const struct sockaddr *saddr)
{
struct ifaddrs *addrs,*a;
if (is_localnet(saddr))
return true;
if (getifaddrs(&addrs)<0) return false;
a = addrs;
bool bres=false;
while (a)
{
if (a->ifa_addr && sacmp(a->ifa_addr,saddr))
{
bres=true;
break;
}
a = a->ifa_next;
}
freeifaddrs(addrs);
return bres;
}
void print_addrinfo(const struct addrinfo *ai)
{
char str[64];
while (ai)
{
switch (ai->ai_family)
{
case AF_INET:
if (inet_ntop(ai->ai_family, &((struct sockaddr_in*)ai->ai_addr)->sin_addr, str, sizeof(str)))
printf("%s\n", str);
break;
case AF_INET6:
if (inet_ntop(ai->ai_family, &((struct sockaddr_in6*)ai->ai_addr)->sin6_addr, str, sizeof(str)))
printf( "%s\n", str);
break;
}
ai = ai->ai_next;
}
}
bool saismapped(const struct sockaddr_in6 *sa)
{
// ::ffff:1.2.3.4
return !memcmp(sa->sin6_addr.s6_addr,"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff",12);
}
bool samappedcmp(const struct sockaddr_in *sa1,const struct sockaddr_in6 *sa2)
{
return saismapped(sa2) && !memcmp(sa2->sin6_addr.s6_addr+12,&sa1->sin_addr.s_addr,4);
}
bool sacmp(const struct sockaddr *sa1,const struct sockaddr *sa2)
{
return (sa1->sa_family==AF_INET && sa2->sa_family==AF_INET && !memcmp(&((struct sockaddr_in*)sa1)->sin_addr,&((struct sockaddr_in*)sa2)->sin_addr,sizeof(struct in_addr))) ||
(sa1->sa_family==AF_INET6 && sa2->sa_family==AF_INET6 && !memcmp(&((struct sockaddr_in6*)sa1)->sin6_addr,&((struct sockaddr_in6*)sa2)->sin6_addr,sizeof(struct in6_addr))) ||
(sa1->sa_family==AF_INET && sa2->sa_family==AF_INET6 && samappedcmp((struct sockaddr_in*)sa1,(struct sockaddr_in6*)sa2)) ||
(sa1->sa_family==AF_INET6 && sa2->sa_family==AF_INET && samappedcmp((struct sockaddr_in*)sa2,(struct sockaddr_in6*)sa1));
}
uint16_t saport(const struct sockaddr *sa)
{
return htons(sa->sa_family==AF_INET ? ((struct sockaddr_in*)sa)->sin_port :
sa->sa_family==AF_INET6 ? ((struct sockaddr_in6*)sa)->sin6_port : 0);
}
bool saconvmapped(struct sockaddr_storage *a)
{
if ((a->ss_family == AF_INET6) && saismapped((struct sockaddr_in6*)a))
{
uint32_t ip4 = IN6_EXTRACT_MAP4(((struct sockaddr_in6*)a)->sin6_addr.s6_addr);
uint16_t port = ((struct sockaddr_in6*)a)->sin6_port;
a->ss_family = AF_INET;
((struct sockaddr_in*)a)->sin_addr.s_addr = ip4;
((struct sockaddr_in*)a)->sin_port = port;
return true;
}
return false;
}
void sacopy(struct sockaddr_storage *sa_dest, const struct sockaddr *sa)
{
switch(sa->sa_family)
{
case AF_INET:
memcpy(sa_dest,sa,sizeof(struct sockaddr_in));
break;
case AF_INET6:
memcpy(sa_dest,sa,sizeof(struct sockaddr_in6));
break;
default:
sa_dest->ss_family = 0;
}
}
bool is_localnet(const struct sockaddr *a)
{
// match 127.0.0.0/8, 0.0.0.0, ::1, ::0, :ffff:127.0.0.0/104, :ffff:0.0.0.0
return (a->sa_family==AF_INET && (IN_LOOPBACK(ntohl(((struct sockaddr_in *)a)->sin_addr.s_addr)) ||
INADDR_ANY == ntohl((((struct sockaddr_in *)a)->sin_addr.s_addr)))) ||
(a->sa_family==AF_INET6 && (IN6_IS_ADDR_LOOPBACK(&((struct sockaddr_in6 *)a)->sin6_addr) ||
IN6_IS_ADDR_UNSPECIFIED(&((struct sockaddr_in6 *)a)->sin6_addr) ||
(IN6_IS_ADDR_V4MAPPED(&((struct sockaddr_in6 *)a)->sin6_addr) && (IN_LOOPBACK(ntohl(IN6_EXTRACT_MAP4(((struct sockaddr_in6*)a)->sin6_addr.s6_addr))) ||
INADDR_ANY == ntohl(IN6_EXTRACT_MAP4(((struct sockaddr_in6*)a)->sin6_addr.s6_addr))))));
}
bool is_linklocal(const struct sockaddr_in6 *a)
{
// fe80::/10
return a->sin6_addr.s6_addr[0]==0xFE && (a->sin6_addr.s6_addr[1] & 0xC0)==0x80;
}
bool is_private6(const struct sockaddr_in6* a)
{
// fc00::/7
return (a->sin6_addr.s6_addr[0] & 0xFE) == 0xFC;
}
bool set_keepalive(int fd)
{
int yes=1;
return setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &yes, sizeof(int))!=-1;
}
bool set_ttl(int fd, int ttl)
{
return setsockopt(fd, IPPROTO_IP, IP_TTL, &ttl, sizeof(ttl))!=-1;
}
bool set_hl(int fd, int hl)
{
return setsockopt(fd, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &hl, sizeof(hl))!=-1;
}
bool set_ttl_hl(int fd, int ttl)
{
bool b1,b2;
// try to set both but one may fail if family is wrong
b1=set_ttl(fd, ttl);
b2=set_hl(fd, ttl);
return b1 || b2;
}
int get_so_error(int fd)
{
// getsockopt(SO_ERROR) clears error
int errn;
socklen_t optlen = sizeof(errn);
if(getsockopt(fd, SOL_SOCKET, SO_ERROR, &errn, &optlen) == -1)
errn=errno;
return errn;
}
int fprint_localtime(FILE *F)
{
struct tm t;
time_t now;
time(&now);
localtime_r(&now,&t);
return fprintf(F, "%02d.%02d.%04d %02d:%02d:%02d", t.tm_mday, t.tm_mon + 1, t.tm_year + 1900, t.tm_hour, t.tm_min, t.tm_sec);
}
time_t file_mod_time(const char *filename)
{
struct stat st;
return stat(filename,&st)==-1 ? 0 : st.st_mtime;
}
bool pf_in_range(uint16_t port, const port_filter *pf)
{
return port && (((!pf->from && !pf->to) || (port>=pf->from && port<=pf->to)) ^ pf->neg);
}
bool pf_parse(const char *s, port_filter *pf)
{
unsigned int v1,v2;
char c;
if (!s) return false;
if (*s=='~')
{
pf->neg=true;
s++;
}
else
pf->neg=false;
if (sscanf(s,"%u-%u%c",&v1,&v2,&c)==2)
{
if (v1>65535 || v2>65535 || v1>v2) return false;
pf->from=(uint16_t)v1;
pf->to=(uint16_t)v2;
}
else if (sscanf(s,"%u%c",&v1,&c)==1)
{
if (v1>65535) return false;
pf->to=pf->from=(uint16_t)v1;
}
else
return false;
// deny all case
if (!pf->from && !pf->to) pf->neg=true;
return true;
}
bool pf_is_empty(const port_filter *pf)
{
return !pf->neg && !pf->from && !pf->to;
}

73
tpws/helpers.h Normal file
View File

@@ -0,0 +1,73 @@
#pragma once
#include <stddef.h>
#include <stdbool.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <netdb.h>
#include <stdio.h>
#include <time.h>
char *strncasestr(const char *s,const char *find, size_t slen);
bool append_to_list_file(const char *filename, const char *s);
void ntop46(const struct sockaddr *sa, char *str, size_t len);
void ntop46_port(const struct sockaddr *sa, char *str, size_t len);
void print_sockaddr(const struct sockaddr *sa);
void print_addrinfo(const struct addrinfo *ai);
bool check_local_ip(const struct sockaddr *saddr);
bool saismapped(const struct sockaddr_in6 *sa);
bool samappedcmp(const struct sockaddr_in *sa1,const struct sockaddr_in6 *sa2);
bool sacmp(const struct sockaddr *sa1,const struct sockaddr *sa2);
uint16_t saport(const struct sockaddr *sa);
// true = was converted
bool saconvmapped(struct sockaddr_storage *a);
void sacopy(struct sockaddr_storage *sa_dest, const struct sockaddr *sa);
bool is_localnet(const struct sockaddr *a);
bool is_linklocal(const struct sockaddr_in6* a);
bool is_private6(const struct sockaddr_in6* a);
bool set_keepalive(int fd);
bool set_ttl(int fd, int ttl);
bool set_hl(int fd, int hl);
bool set_ttl_hl(int fd, int ttl);
int get_so_error(int fd);
// alignment-safe functions
static inline uint16_t pntoh16(const uint8_t *p) {
return ((uint16_t)p[0] << 8) | (uint16_t)p[1];
}
static inline void phton16(uint8_t *p, uint16_t v) {
p[0] = (uint8_t)(v>>8);
p[1] = (uint8_t)v;
}
int fprint_localtime(FILE *F);
time_t file_mod_time(const char *filename);
typedef struct
{
uint16_t from,to;
bool neg;
} port_filter;
bool pf_in_range(uint16_t port, const port_filter *pf);
bool pf_parse(const char *s, port_filter *pf);
bool pf_is_empty(const port_filter *pf);
#ifndef IN_LOOPBACK
#define IN_LOOPBACK(a) ((((uint32_t) (a)) & 0xff000000) == 0x7f000000)
#endif
#ifdef __GNUC__
#define IN6_EXTRACT_MAP4(a) \
(__extension__ \
({ const struct in6_addr *__a = (const struct in6_addr *) (a); \
(((const uint32_t *) (__a))[3]); }))
#else
#define IN6_EXTRACT_MAP4(a) (((const uint32_t *) (a))[3])
#endif

206
tpws/hostlist.c Normal file
View File

@@ -0,0 +1,206 @@
#include <stdio.h>
#include "hostlist.h"
#include "gzip.h"
#include "params.h"
#include "helpers.h"
// inplace tolower() and add to pool
static bool addpool(strpool **hostlist, char **s, const char *end)
{
char *p;
// advance until eol lowering all chars
for (p = *s; p<end && *p && *p!='\r' && *p != '\n'; p++) *p=tolower(*p);
if (!StrPoolAddStrLen(hostlist, *s, p-*s))
{
StrPoolDestroy(hostlist);
*hostlist = NULL;
return false;
}
// advance to the next line
for (; p<end && (!*p || *p=='\r' || *p=='\n') ; p++);
*s = p;
return true;
}
bool AppendHostList(strpool **hostlist, char *filename)
{
char *p, *e, s[256], *zbuf;
size_t zsize;
int ct = 0;
FILE *F;
int r;
DLOG_CONDUP("Loading hostlist %s\n",filename);
if (!(F = fopen(filename, "rb")))
{
DLOG_ERR("Could not open %s\n", filename);
return false;
}
if (is_gzip(F))
{
r = z_readfile(F,&zbuf,&zsize);
fclose(F);
if (r==Z_OK)
{
DLOG_CONDUP("zlib compression detected. uncompressed size : %zu\n", zsize);
p = zbuf;
e = zbuf + zsize;
while(p<e)
{
if ( *p == '#' || *p == ';' || *p == '/' || *p == '\n' ) continue;
if (!addpool(hostlist,&p,e))
{
DLOG_ERR("Not enough memory to store host list : %s\n", filename);
free(zbuf);
return false;
}
ct++;
}
free(zbuf);
}
else
{
DLOG_ERR("zlib decompression failed : result %d\n",r);
return false;
}
}
else
{
DLOG_CONDUP("loading plain text list\n");
while (fgets(s, 256, F))
{
p = s;
if ( *p == '#' || *p == ';' || *p == '/' || *p == '\n' ) continue;
if (!addpool(hostlist,&p,p+strlen(p)))
{
DLOG_ERR("Not enough memory to store host list : %s\n", filename);
fclose(F);
return false;
}
ct++;
}
fclose(F);
}
DLOG_CONDUP("Loaded %d hosts from %s\n", ct, filename);
return true;
}
bool LoadHostLists(strpool **hostlist, struct str_list_head *file_list)
{
struct str_list *file;
if (*hostlist)
{
StrPoolDestroy(hostlist);
*hostlist = NULL;
}
LIST_FOREACH(file, file_list, next)
{
if (!AppendHostList(hostlist, file->str)) return false;
}
return true;
}
bool NonEmptyHostlist(strpool **hostlist)
{
// add impossible hostname if the list is empty
return *hostlist ? true : StrPoolAddStrLen(hostlist, "@&()", 4);
}
bool SearchHostList(strpool *hostlist, const char *host)
{
if (hostlist)
{
const char *p = host;
bool bInHostList;
while (p)
{
bInHostList = StrPoolCheckStr(hostlist, p);
VPRINT("Hostlist check for %s : %s\n", p, bInHostList ? "positive" : "negative");
if (bInHostList) return true;
p = strchr(p, '.');
if (p) p++;
}
}
return false;
}
// return : true = apply fooling, false = do not apply
static bool HostlistCheck_(strpool *hostlist, strpool *hostlist_exclude, const char *host, bool *excluded)
{
if (excluded) *excluded = false;
if (hostlist_exclude)
{
VPRINT("Checking exclude hostlist\n");
if (SearchHostList(hostlist_exclude, host))
{
if (excluded) *excluded = true;
return false;
}
}
if (hostlist)
{
VPRINT("Checking include hostlist\n");
return SearchHostList(hostlist, host);
}
return true;
}
static bool LoadIncludeHostListsForProfile(struct desync_profile *dp)
{
if (!LoadHostLists(&dp->hostlist, &dp->hostlist_files))
return false;
if (*dp->hostlist_auto_filename)
{
dp->hostlist_auto_mod_time = file_mod_time(dp->hostlist_auto_filename);
NonEmptyHostlist(&dp->hostlist);
}
return true;
}
// return : true = apply fooling, false = do not apply
bool HostlistCheck(struct desync_profile *dp, const char *host, bool *excluded)
{
VPRINT("* Hostlist check for profile %d\n",dp->n);
if (*dp->hostlist_auto_filename)
{
time_t t = file_mod_time(dp->hostlist_auto_filename);
if (t!=dp->hostlist_auto_mod_time)
{
DLOG_CONDUP("Autohostlist '%s' from profile %d was modified. Reloading include hostlists for this profile.\n",dp->hostlist_auto_filename, dp->n);
if (!LoadIncludeHostListsForProfile(dp))
{
// what will we do without hostlist ?? sure, gonna die
exit(1);
}
dp->hostlist_auto_mod_time = t;
NonEmptyHostlist(&dp->hostlist);
}
}
return HostlistCheck_(dp->hostlist, dp->hostlist_exclude, host, excluded);
}
bool LoadIncludeHostLists()
{
struct desync_profile_list *dpl;
LIST_FOREACH(dpl, &params.desync_profiles, next)
if (!LoadIncludeHostListsForProfile(&dpl->dp))
return false;
return true;
}
bool LoadExcludeHostLists()
{
struct desync_profile_list *dpl;
LIST_FOREACH(dpl, &params.desync_profiles, next)
if (!LoadHostLists(&dpl->dp.hostlist_exclude, &dpl->dp.hostlist_exclude_files))
return false;
return true;
}

14
tpws/hostlist.h Normal file
View File

@@ -0,0 +1,14 @@
#pragma once
#include <stdbool.h>
#include "pools.h"
#include "params.h"
bool AppendHostList(strpool **hostlist, char *filename);
bool LoadHostLists(strpool **hostlist, struct str_list_head *file_list);
bool LoadIncludeHostLists();
bool LoadExcludeHostLists();
bool NonEmptyHostlist(strpool **hostlist);
bool SearchHostList(strpool *hostlist, const char *host);
// return : true = apply fooling, false = do not apply
bool HostlistCheck(struct desync_profile *dp,const char *host, bool *excluded);

47
tpws/macos/net/pfvar.h Normal file
View File

@@ -0,0 +1,47 @@
#pragma once
#include <stdint.h>
#include <netinet/in.h>
// taken from an older apple SDK
// some fields are different from BSDs
#define DIOCNATLOOK _IOWR('D', 23, struct pfioc_natlook)
enum { PF_INOUT, PF_IN, PF_OUT, PF_FWD };
struct pf_addr {
union {
struct in_addr v4;
struct in6_addr v6;
u_int8_t addr8[16];
u_int16_t addr16[8];
u_int32_t addr32[4];
} pfa; /* 128-bit address */
#define v4 pfa.v4
#define v6 pfa.v6
#define addr8 pfa.addr8
#define addr16 pfa.addr16
#define addr32 pfa.addr32
};
union pf_state_xport {
u_int16_t port;
u_int16_t call_id;
u_int32_t spi;
};
struct pfioc_natlook {
struct pf_addr saddr;
struct pf_addr daddr;
struct pf_addr rsaddr;
struct pf_addr rdaddr;
union pf_state_xport sxport;
union pf_state_xport dxport;
union pf_state_xport rsxport;
union pf_state_xport rdxport;
sa_family_t af;
u_int8_t proto;
u_int8_t proto_variant;
u_int8_t direction;
};

803
tpws/macos/sys/tree.h Normal file
View File

@@ -0,0 +1,803 @@
/* $NetBSD: tree.h,v 1.8 2004/03/28 19:38:30 provos Exp $ */
/* $OpenBSD: tree.h,v 1.7 2002/10/17 21:51:54 art Exp $ */
/* $FreeBSD: releng/12.2/sys/sys/tree.h 326256 2017-11-27 15:01:59Z pfg $ */
/*-
* SPDX-License-Identifier: BSD-2-Clause-FreeBSD
*
* Copyright 2002 Niels Provos <provos@citi.umich.edu>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef _SYS_TREE_H_
#define _SYS_TREE_H_
#include <sys/cdefs.h>
/*
* This file defines data structures for different types of trees:
* splay trees and red-black trees.
*
* A splay tree is a self-organizing data structure. Every operation
* on the tree causes a splay to happen. The splay moves the requested
* node to the root of the tree and partly rebalances it.
*
* This has the benefit that request locality causes faster lookups as
* the requested nodes move to the top of the tree. On the other hand,
* every lookup causes memory writes.
*
* The Balance Theorem bounds the total access time for m operations
* and n inserts on an initially empty tree as O((m + n)lg n). The
* amortized cost for a sequence of m accesses to a splay tree is O(lg n);
*
* A red-black tree is a binary search tree with the node color as an
* extra attribute. It fulfills a set of conditions:
* - every search path from the root to a leaf consists of the
* same number of black nodes,
* - each red node (except for the root) has a black parent,
* - each leaf node is black.
*
* Every operation on a red-black tree is bounded as O(lg n).
* The maximum height of a red-black tree is 2lg (n+1).
*/
#define SPLAY_HEAD(name, type) \
struct name { \
struct type *sph_root; /* root of the tree */ \
}
#define SPLAY_INITIALIZER(root) \
{ NULL }
#define SPLAY_INIT(root) do { \
(root)->sph_root = NULL; \
} while (/*CONSTCOND*/ 0)
#define SPLAY_ENTRY(type) \
struct { \
struct type *spe_left; /* left element */ \
struct type *spe_right; /* right element */ \
}
#define SPLAY_LEFT(elm, field) (elm)->field.spe_left
#define SPLAY_RIGHT(elm, field) (elm)->field.spe_right
#define SPLAY_ROOT(head) (head)->sph_root
#define SPLAY_EMPTY(head) (SPLAY_ROOT(head) == NULL)
/* SPLAY_ROTATE_{LEFT,RIGHT} expect that tmp hold SPLAY_{RIGHT,LEFT} */
#define SPLAY_ROTATE_RIGHT(head, tmp, field) do { \
SPLAY_LEFT((head)->sph_root, field) = SPLAY_RIGHT(tmp, field); \
SPLAY_RIGHT(tmp, field) = (head)->sph_root; \
(head)->sph_root = tmp; \
} while (/*CONSTCOND*/ 0)
#define SPLAY_ROTATE_LEFT(head, tmp, field) do { \
SPLAY_RIGHT((head)->sph_root, field) = SPLAY_LEFT(tmp, field); \
SPLAY_LEFT(tmp, field) = (head)->sph_root; \
(head)->sph_root = tmp; \
} while (/*CONSTCOND*/ 0)
#define SPLAY_LINKLEFT(head, tmp, field) do { \
SPLAY_LEFT(tmp, field) = (head)->sph_root; \
tmp = (head)->sph_root; \
(head)->sph_root = SPLAY_LEFT((head)->sph_root, field); \
} while (/*CONSTCOND*/ 0)
#define SPLAY_LINKRIGHT(head, tmp, field) do { \
SPLAY_RIGHT(tmp, field) = (head)->sph_root; \
tmp = (head)->sph_root; \
(head)->sph_root = SPLAY_RIGHT((head)->sph_root, field); \
} while (/*CONSTCOND*/ 0)
#define SPLAY_ASSEMBLE(head, node, left, right, field) do { \
SPLAY_RIGHT(left, field) = SPLAY_LEFT((head)->sph_root, field); \
SPLAY_LEFT(right, field) = SPLAY_RIGHT((head)->sph_root, field);\
SPLAY_LEFT((head)->sph_root, field) = SPLAY_RIGHT(node, field); \
SPLAY_RIGHT((head)->sph_root, field) = SPLAY_LEFT(node, field); \
} while (/*CONSTCOND*/ 0)
/* Generates prototypes and inline functions */
#define SPLAY_PROTOTYPE(name, type, field, cmp) \
void name##_SPLAY(struct name *, struct type *); \
void name##_SPLAY_MINMAX(struct name *, int); \
struct type *name##_SPLAY_INSERT(struct name *, struct type *); \
struct type *name##_SPLAY_REMOVE(struct name *, struct type *); \
\
/* Finds the node with the same key as elm */ \
static __inline struct type * \
name##_SPLAY_FIND(struct name *head, struct type *elm) \
{ \
if (SPLAY_EMPTY(head)) \
return(NULL); \
name##_SPLAY(head, elm); \
if ((cmp)(elm, (head)->sph_root) == 0) \
return (head->sph_root); \
return (NULL); \
} \
\
static __inline struct type * \
name##_SPLAY_NEXT(struct name *head, struct type *elm) \
{ \
name##_SPLAY(head, elm); \
if (SPLAY_RIGHT(elm, field) != NULL) { \
elm = SPLAY_RIGHT(elm, field); \
while (SPLAY_LEFT(elm, field) != NULL) { \
elm = SPLAY_LEFT(elm, field); \
} \
} else \
elm = NULL; \
return (elm); \
} \
\
static __inline struct type * \
name##_SPLAY_MIN_MAX(struct name *head, int val) \
{ \
name##_SPLAY_MINMAX(head, val); \
return (SPLAY_ROOT(head)); \
}
/* Main splay operation.
* Moves node close to the key of elm to top
*/
#define SPLAY_GENERATE(name, type, field, cmp) \
struct type * \
name##_SPLAY_INSERT(struct name *head, struct type *elm) \
{ \
if (SPLAY_EMPTY(head)) { \
SPLAY_LEFT(elm, field) = SPLAY_RIGHT(elm, field) = NULL; \
} else { \
int __comp; \
name##_SPLAY(head, elm); \
__comp = (cmp)(elm, (head)->sph_root); \
if(__comp < 0) { \
SPLAY_LEFT(elm, field) = SPLAY_LEFT((head)->sph_root, field);\
SPLAY_RIGHT(elm, field) = (head)->sph_root; \
SPLAY_LEFT((head)->sph_root, field) = NULL; \
} else if (__comp > 0) { \
SPLAY_RIGHT(elm, field) = SPLAY_RIGHT((head)->sph_root, field);\
SPLAY_LEFT(elm, field) = (head)->sph_root; \
SPLAY_RIGHT((head)->sph_root, field) = NULL; \
} else \
return ((head)->sph_root); \
} \
(head)->sph_root = (elm); \
return (NULL); \
} \
\
struct type * \
name##_SPLAY_REMOVE(struct name *head, struct type *elm) \
{ \
struct type *__tmp; \
if (SPLAY_EMPTY(head)) \
return (NULL); \
name##_SPLAY(head, elm); \
if ((cmp)(elm, (head)->sph_root) == 0) { \
if (SPLAY_LEFT((head)->sph_root, field) == NULL) { \
(head)->sph_root = SPLAY_RIGHT((head)->sph_root, field);\
} else { \
__tmp = SPLAY_RIGHT((head)->sph_root, field); \
(head)->sph_root = SPLAY_LEFT((head)->sph_root, field);\
name##_SPLAY(head, elm); \
SPLAY_RIGHT((head)->sph_root, field) = __tmp; \
} \
return (elm); \
} \
return (NULL); \
} \
\
void \
name##_SPLAY(struct name *head, struct type *elm) \
{ \
struct type __node, *__left, *__right, *__tmp; \
int __comp; \
\
SPLAY_LEFT(&__node, field) = SPLAY_RIGHT(&__node, field) = NULL;\
__left = __right = &__node; \
\
while ((__comp = (cmp)(elm, (head)->sph_root)) != 0) { \
if (__comp < 0) { \
__tmp = SPLAY_LEFT((head)->sph_root, field); \
if (__tmp == NULL) \
break; \
if ((cmp)(elm, __tmp) < 0){ \
SPLAY_ROTATE_RIGHT(head, __tmp, field); \
if (SPLAY_LEFT((head)->sph_root, field) == NULL)\
break; \
} \
SPLAY_LINKLEFT(head, __right, field); \
} else if (__comp > 0) { \
__tmp = SPLAY_RIGHT((head)->sph_root, field); \
if (__tmp == NULL) \
break; \
if ((cmp)(elm, __tmp) > 0){ \
SPLAY_ROTATE_LEFT(head, __tmp, field); \
if (SPLAY_RIGHT((head)->sph_root, field) == NULL)\
break; \
} \
SPLAY_LINKRIGHT(head, __left, field); \
} \
} \
SPLAY_ASSEMBLE(head, &__node, __left, __right, field); \
} \
\
/* Splay with either the minimum or the maximum element \
* Used to find minimum or maximum element in tree. \
*/ \
void name##_SPLAY_MINMAX(struct name *head, int __comp) \
{ \
struct type __node, *__left, *__right, *__tmp; \
\
SPLAY_LEFT(&__node, field) = SPLAY_RIGHT(&__node, field) = NULL;\
__left = __right = &__node; \
\
while (1) { \
if (__comp < 0) { \
__tmp = SPLAY_LEFT((head)->sph_root, field); \
if (__tmp == NULL) \
break; \
if (__comp < 0){ \
SPLAY_ROTATE_RIGHT(head, __tmp, field); \
if (SPLAY_LEFT((head)->sph_root, field) == NULL)\
break; \
} \
SPLAY_LINKLEFT(head, __right, field); \
} else if (__comp > 0) { \
__tmp = SPLAY_RIGHT((head)->sph_root, field); \
if (__tmp == NULL) \
break; \
if (__comp > 0) { \
SPLAY_ROTATE_LEFT(head, __tmp, field); \
if (SPLAY_RIGHT((head)->sph_root, field) == NULL)\
break; \
} \
SPLAY_LINKRIGHT(head, __left, field); \
} \
} \
SPLAY_ASSEMBLE(head, &__node, __left, __right, field); \
}
#define SPLAY_NEGINF -1
#define SPLAY_INF 1
#define SPLAY_INSERT(name, x, y) name##_SPLAY_INSERT(x, y)
#define SPLAY_REMOVE(name, x, y) name##_SPLAY_REMOVE(x, y)
#define SPLAY_FIND(name, x, y) name##_SPLAY_FIND(x, y)
#define SPLAY_NEXT(name, x, y) name##_SPLAY_NEXT(x, y)
#define SPLAY_MIN(name, x) (SPLAY_EMPTY(x) ? NULL \
: name##_SPLAY_MIN_MAX(x, SPLAY_NEGINF))
#define SPLAY_MAX(name, x) (SPLAY_EMPTY(x) ? NULL \
: name##_SPLAY_MIN_MAX(x, SPLAY_INF))
#define SPLAY_FOREACH(x, name, head) \
for ((x) = SPLAY_MIN(name, head); \
(x) != NULL; \
(x) = SPLAY_NEXT(name, head, x))
/* Macros that define a red-black tree */
#define RB_HEAD(name, type) \
struct name { \
struct type *rbh_root; /* root of the tree */ \
}
#define RB_INITIALIZER(root) \
{ NULL }
#define RB_INIT(root) do { \
(root)->rbh_root = NULL; \
} while (/*CONSTCOND*/ 0)
#define RB_BLACK 0
#define RB_RED 1
#define RB_ENTRY(type) \
struct { \
struct type *rbe_left; /* left element */ \
struct type *rbe_right; /* right element */ \
struct type *rbe_parent; /* parent element */ \
int rbe_color; /* node color */ \
}
#define RB_LEFT(elm, field) (elm)->field.rbe_left
#define RB_RIGHT(elm, field) (elm)->field.rbe_right
#define RB_PARENT(elm, field) (elm)->field.rbe_parent
#define RB_COLOR(elm, field) (elm)->field.rbe_color
#define RB_ROOT(head) (head)->rbh_root
#define RB_EMPTY(head) (RB_ROOT(head) == NULL)
#define RB_SET(elm, parent, field) do { \
RB_PARENT(elm, field) = parent; \
RB_LEFT(elm, field) = RB_RIGHT(elm, field) = NULL; \
RB_COLOR(elm, field) = RB_RED; \
} while (/*CONSTCOND*/ 0)
#define RB_SET_BLACKRED(black, red, field) do { \
RB_COLOR(black, field) = RB_BLACK; \
RB_COLOR(red, field) = RB_RED; \
} while (/*CONSTCOND*/ 0)
#ifndef RB_AUGMENT
#define RB_AUGMENT(x) do {} while (0)
#endif
#define RB_ROTATE_LEFT(head, elm, tmp, field) do { \
(tmp) = RB_RIGHT(elm, field); \
if ((RB_RIGHT(elm, field) = RB_LEFT(tmp, field)) != NULL) { \
RB_PARENT(RB_LEFT(tmp, field), field) = (elm); \
} \
RB_AUGMENT(elm); \
if ((RB_PARENT(tmp, field) = RB_PARENT(elm, field)) != NULL) { \
if ((elm) == RB_LEFT(RB_PARENT(elm, field), field)) \
RB_LEFT(RB_PARENT(elm, field), field) = (tmp); \
else \
RB_RIGHT(RB_PARENT(elm, field), field) = (tmp); \
} else \
(head)->rbh_root = (tmp); \
RB_LEFT(tmp, field) = (elm); \
RB_PARENT(elm, field) = (tmp); \
RB_AUGMENT(tmp); \
if ((RB_PARENT(tmp, field))) \
RB_AUGMENT(RB_PARENT(tmp, field)); \
} while (/*CONSTCOND*/ 0)
#define RB_ROTATE_RIGHT(head, elm, tmp, field) do { \
(tmp) = RB_LEFT(elm, field); \
if ((RB_LEFT(elm, field) = RB_RIGHT(tmp, field)) != NULL) { \
RB_PARENT(RB_RIGHT(tmp, field), field) = (elm); \
} \
RB_AUGMENT(elm); \
if ((RB_PARENT(tmp, field) = RB_PARENT(elm, field)) != NULL) { \
if ((elm) == RB_LEFT(RB_PARENT(elm, field), field)) \
RB_LEFT(RB_PARENT(elm, field), field) = (tmp); \
else \
RB_RIGHT(RB_PARENT(elm, field), field) = (tmp); \
} else \
(head)->rbh_root = (tmp); \
RB_RIGHT(tmp, field) = (elm); \
RB_PARENT(elm, field) = (tmp); \
RB_AUGMENT(tmp); \
if ((RB_PARENT(tmp, field))) \
RB_AUGMENT(RB_PARENT(tmp, field)); \
} while (/*CONSTCOND*/ 0)
/* Generates prototypes and inline functions */
#define RB_PROTOTYPE(name, type, field, cmp) \
RB_PROTOTYPE_INTERNAL(name, type, field, cmp,)
#define RB_PROTOTYPE_STATIC(name, type, field, cmp) \
RB_PROTOTYPE_INTERNAL(name, type, field, cmp, __unused static)
#define RB_PROTOTYPE_INTERNAL(name, type, field, cmp, attr) \
RB_PROTOTYPE_INSERT_COLOR(name, type, attr); \
RB_PROTOTYPE_REMOVE_COLOR(name, type, attr); \
RB_PROTOTYPE_INSERT(name, type, attr); \
RB_PROTOTYPE_REMOVE(name, type, attr); \
RB_PROTOTYPE_FIND(name, type, attr); \
RB_PROTOTYPE_NFIND(name, type, attr); \
RB_PROTOTYPE_NEXT(name, type, attr); \
RB_PROTOTYPE_PREV(name, type, attr); \
RB_PROTOTYPE_MINMAX(name, type, attr);
#define RB_PROTOTYPE_INSERT_COLOR(name, type, attr) \
attr void name##_RB_INSERT_COLOR(struct name *, struct type *)
#define RB_PROTOTYPE_REMOVE_COLOR(name, type, attr) \
attr void name##_RB_REMOVE_COLOR(struct name *, struct type *, struct type *)
#define RB_PROTOTYPE_REMOVE(name, type, attr) \
attr struct type *name##_RB_REMOVE(struct name *, struct type *)
#define RB_PROTOTYPE_INSERT(name, type, attr) \
attr struct type *name##_RB_INSERT(struct name *, struct type *)
#define RB_PROTOTYPE_FIND(name, type, attr) \
attr struct type *name##_RB_FIND(struct name *, struct type *)
#define RB_PROTOTYPE_NFIND(name, type, attr) \
attr struct type *name##_RB_NFIND(struct name *, struct type *)
#define RB_PROTOTYPE_NEXT(name, type, attr) \
attr struct type *name##_RB_NEXT(struct type *)
#define RB_PROTOTYPE_PREV(name, type, attr) \
attr struct type *name##_RB_PREV(struct type *)
#define RB_PROTOTYPE_MINMAX(name, type, attr) \
attr struct type *name##_RB_MINMAX(struct name *, int)
/* Main rb operation.
* Moves node close to the key of elm to top
*/
#define RB_GENERATE(name, type, field, cmp) \
RB_GENERATE_INTERNAL(name, type, field, cmp,)
#define RB_GENERATE_STATIC(name, type, field, cmp) \
RB_GENERATE_INTERNAL(name, type, field, cmp, __unused static)
#define RB_GENERATE_INTERNAL(name, type, field, cmp, attr) \
RB_GENERATE_INSERT_COLOR(name, type, field, attr) \
RB_GENERATE_REMOVE_COLOR(name, type, field, attr) \
RB_GENERATE_INSERT(name, type, field, cmp, attr) \
RB_GENERATE_REMOVE(name, type, field, attr) \
RB_GENERATE_FIND(name, type, field, cmp, attr) \
RB_GENERATE_NFIND(name, type, field, cmp, attr) \
RB_GENERATE_NEXT(name, type, field, attr) \
RB_GENERATE_PREV(name, type, field, attr) \
RB_GENERATE_MINMAX(name, type, field, attr)
#define RB_GENERATE_INSERT_COLOR(name, type, field, attr) \
attr void \
name##_RB_INSERT_COLOR(struct name *head, struct type *elm) \
{ \
struct type *parent, *gparent, *tmp; \
while ((parent = RB_PARENT(elm, field)) != NULL && \
RB_COLOR(parent, field) == RB_RED) { \
gparent = RB_PARENT(parent, field); \
if (parent == RB_LEFT(gparent, field)) { \
tmp = RB_RIGHT(gparent, field); \
if (tmp && RB_COLOR(tmp, field) == RB_RED) { \
RB_COLOR(tmp, field) = RB_BLACK; \
RB_SET_BLACKRED(parent, gparent, field);\
elm = gparent; \
continue; \
} \
if (RB_RIGHT(parent, field) == elm) { \
RB_ROTATE_LEFT(head, parent, tmp, field);\
tmp = parent; \
parent = elm; \
elm = tmp; \
} \
RB_SET_BLACKRED(parent, gparent, field); \
RB_ROTATE_RIGHT(head, gparent, tmp, field); \
} else { \
tmp = RB_LEFT(gparent, field); \
if (tmp && RB_COLOR(tmp, field) == RB_RED) { \
RB_COLOR(tmp, field) = RB_BLACK; \
RB_SET_BLACKRED(parent, gparent, field);\
elm = gparent; \
continue; \
} \
if (RB_LEFT(parent, field) == elm) { \
RB_ROTATE_RIGHT(head, parent, tmp, field);\
tmp = parent; \
parent = elm; \
elm = tmp; \
} \
RB_SET_BLACKRED(parent, gparent, field); \
RB_ROTATE_LEFT(head, gparent, tmp, field); \
} \
} \
RB_COLOR(head->rbh_root, field) = RB_BLACK; \
}
#define RB_GENERATE_REMOVE_COLOR(name, type, field, attr) \
attr void \
name##_RB_REMOVE_COLOR(struct name *head, struct type *parent, struct type *elm) \
{ \
struct type *tmp; \
while ((elm == NULL || RB_COLOR(elm, field) == RB_BLACK) && \
elm != RB_ROOT(head)) { \
if (RB_LEFT(parent, field) == elm) { \
tmp = RB_RIGHT(parent, field); \
if (RB_COLOR(tmp, field) == RB_RED) { \
RB_SET_BLACKRED(tmp, parent, field); \
RB_ROTATE_LEFT(head, parent, tmp, field);\
tmp = RB_RIGHT(parent, field); \
} \
if ((RB_LEFT(tmp, field) == NULL || \
RB_COLOR(RB_LEFT(tmp, field), field) == RB_BLACK) &&\
(RB_RIGHT(tmp, field) == NULL || \
RB_COLOR(RB_RIGHT(tmp, field), field) == RB_BLACK)) {\
RB_COLOR(tmp, field) = RB_RED; \
elm = parent; \
parent = RB_PARENT(elm, field); \
} else { \
if (RB_RIGHT(tmp, field) == NULL || \
RB_COLOR(RB_RIGHT(tmp, field), field) == RB_BLACK) {\
struct type *oleft; \
if ((oleft = RB_LEFT(tmp, field)) \
!= NULL) \
RB_COLOR(oleft, field) = RB_BLACK;\
RB_COLOR(tmp, field) = RB_RED; \
RB_ROTATE_RIGHT(head, tmp, oleft, field);\
tmp = RB_RIGHT(parent, field); \
} \
RB_COLOR(tmp, field) = RB_COLOR(parent, field);\
RB_COLOR(parent, field) = RB_BLACK; \
if (RB_RIGHT(tmp, field)) \
RB_COLOR(RB_RIGHT(tmp, field), field) = RB_BLACK;\
RB_ROTATE_LEFT(head, parent, tmp, field);\
elm = RB_ROOT(head); \
break; \
} \
} else { \
tmp = RB_LEFT(parent, field); \
if (RB_COLOR(tmp, field) == RB_RED) { \
RB_SET_BLACKRED(tmp, parent, field); \
RB_ROTATE_RIGHT(head, parent, tmp, field);\
tmp = RB_LEFT(parent, field); \
} \
if ((RB_LEFT(tmp, field) == NULL || \
RB_COLOR(RB_LEFT(tmp, field), field) == RB_BLACK) &&\
(RB_RIGHT(tmp, field) == NULL || \
RB_COLOR(RB_RIGHT(tmp, field), field) == RB_BLACK)) {\
RB_COLOR(tmp, field) = RB_RED; \
elm = parent; \
parent = RB_PARENT(elm, field); \
} else { \
if (RB_LEFT(tmp, field) == NULL || \
RB_COLOR(RB_LEFT(tmp, field), field) == RB_BLACK) {\
struct type *oright; \
if ((oright = RB_RIGHT(tmp, field)) \
!= NULL) \
RB_COLOR(oright, field) = RB_BLACK;\
RB_COLOR(tmp, field) = RB_RED; \
RB_ROTATE_LEFT(head, tmp, oright, field);\
tmp = RB_LEFT(parent, field); \
} \
RB_COLOR(tmp, field) = RB_COLOR(parent, field);\
RB_COLOR(parent, field) = RB_BLACK; \
if (RB_LEFT(tmp, field)) \
RB_COLOR(RB_LEFT(tmp, field), field) = RB_BLACK;\
RB_ROTATE_RIGHT(head, parent, tmp, field);\
elm = RB_ROOT(head); \
break; \
} \
} \
} \
if (elm) \
RB_COLOR(elm, field) = RB_BLACK; \
}
#define RB_GENERATE_REMOVE(name, type, field, attr) \
attr struct type * \
name##_RB_REMOVE(struct name *head, struct type *elm) \
{ \
struct type *child, *parent, *old = elm; \
int color; \
if (RB_LEFT(elm, field) == NULL) \
child = RB_RIGHT(elm, field); \
else if (RB_RIGHT(elm, field) == NULL) \
child = RB_LEFT(elm, field); \
else { \
struct type *left; \
elm = RB_RIGHT(elm, field); \
while ((left = RB_LEFT(elm, field)) != NULL) \
elm = left; \
child = RB_RIGHT(elm, field); \
parent = RB_PARENT(elm, field); \
color = RB_COLOR(elm, field); \
if (child) \
RB_PARENT(child, field) = parent; \
if (parent) { \
if (RB_LEFT(parent, field) == elm) \
RB_LEFT(parent, field) = child; \
else \
RB_RIGHT(parent, field) = child; \
RB_AUGMENT(parent); \
} else \
RB_ROOT(head) = child; \
if (RB_PARENT(elm, field) == old) \
parent = elm; \
(elm)->field = (old)->field; \
if (RB_PARENT(old, field)) { \
if (RB_LEFT(RB_PARENT(old, field), field) == old)\
RB_LEFT(RB_PARENT(old, field), field) = elm;\
else \
RB_RIGHT(RB_PARENT(old, field), field) = elm;\
RB_AUGMENT(RB_PARENT(old, field)); \
} else \
RB_ROOT(head) = elm; \
RB_PARENT(RB_LEFT(old, field), field) = elm; \
if (RB_RIGHT(old, field)) \
RB_PARENT(RB_RIGHT(old, field), field) = elm; \
if (parent) { \
left = parent; \
do { \
RB_AUGMENT(left); \
} while ((left = RB_PARENT(left, field)) != NULL); \
} \
goto color; \
} \
parent = RB_PARENT(elm, field); \
color = RB_COLOR(elm, field); \
if (child) \
RB_PARENT(child, field) = parent; \
if (parent) { \
if (RB_LEFT(parent, field) == elm) \
RB_LEFT(parent, field) = child; \
else \
RB_RIGHT(parent, field) = child; \
RB_AUGMENT(parent); \
} else \
RB_ROOT(head) = child; \
color: \
if (color == RB_BLACK) \
name##_RB_REMOVE_COLOR(head, parent, child); \
return (old); \
} \
#define RB_GENERATE_INSERT(name, type, field, cmp, attr) \
/* Inserts a node into the RB tree */ \
attr struct type * \
name##_RB_INSERT(struct name *head, struct type *elm) \
{ \
struct type *tmp; \
struct type *parent = NULL; \
int comp = 0; \
tmp = RB_ROOT(head); \
while (tmp) { \
parent = tmp; \
comp = (cmp)(elm, parent); \
if (comp < 0) \
tmp = RB_LEFT(tmp, field); \
else if (comp > 0) \
tmp = RB_RIGHT(tmp, field); \
else \
return (tmp); \
} \
RB_SET(elm, parent, field); \
if (parent != NULL) { \
if (comp < 0) \
RB_LEFT(parent, field) = elm; \
else \
RB_RIGHT(parent, field) = elm; \
RB_AUGMENT(parent); \
} else \
RB_ROOT(head) = elm; \
name##_RB_INSERT_COLOR(head, elm); \
return (NULL); \
}
#define RB_GENERATE_FIND(name, type, field, cmp, attr) \
/* Finds the node with the same key as elm */ \
attr struct type * \
name##_RB_FIND(struct name *head, struct type *elm) \
{ \
struct type *tmp = RB_ROOT(head); \
int comp; \
while (tmp) { \
comp = cmp(elm, tmp); \
if (comp < 0) \
tmp = RB_LEFT(tmp, field); \
else if (comp > 0) \
tmp = RB_RIGHT(tmp, field); \
else \
return (tmp); \
} \
return (NULL); \
}
#define RB_GENERATE_NFIND(name, type, field, cmp, attr) \
/* Finds the first node greater than or equal to the search key */ \
attr struct type * \
name##_RB_NFIND(struct name *head, struct type *elm) \
{ \
struct type *tmp = RB_ROOT(head); \
struct type *res = NULL; \
int comp; \
while (tmp) { \
comp = cmp(elm, tmp); \
if (comp < 0) { \
res = tmp; \
tmp = RB_LEFT(tmp, field); \
} \
else if (comp > 0) \
tmp = RB_RIGHT(tmp, field); \
else \
return (tmp); \
} \
return (res); \
}
#define RB_GENERATE_NEXT(name, type, field, attr) \
/* ARGSUSED */ \
attr struct type * \
name##_RB_NEXT(struct type *elm) \
{ \
if (RB_RIGHT(elm, field)) { \
elm = RB_RIGHT(elm, field); \
while (RB_LEFT(elm, field)) \
elm = RB_LEFT(elm, field); \
} else { \
if (RB_PARENT(elm, field) && \
(elm == RB_LEFT(RB_PARENT(elm, field), field))) \
elm = RB_PARENT(elm, field); \
else { \
while (RB_PARENT(elm, field) && \
(elm == RB_RIGHT(RB_PARENT(elm, field), field)))\
elm = RB_PARENT(elm, field); \
elm = RB_PARENT(elm, field); \
} \
} \
return (elm); \
}
#define RB_GENERATE_PREV(name, type, field, attr) \
/* ARGSUSED */ \
attr struct type * \
name##_RB_PREV(struct type *elm) \
{ \
if (RB_LEFT(elm, field)) { \
elm = RB_LEFT(elm, field); \
while (RB_RIGHT(elm, field)) \
elm = RB_RIGHT(elm, field); \
} else { \
if (RB_PARENT(elm, field) && \
(elm == RB_RIGHT(RB_PARENT(elm, field), field))) \
elm = RB_PARENT(elm, field); \
else { \
while (RB_PARENT(elm, field) && \
(elm == RB_LEFT(RB_PARENT(elm, field), field)))\
elm = RB_PARENT(elm, field); \
elm = RB_PARENT(elm, field); \
} \
} \
return (elm); \
}
#define RB_GENERATE_MINMAX(name, type, field, attr) \
attr struct type * \
name##_RB_MINMAX(struct name *head, int val) \
{ \
struct type *tmp = RB_ROOT(head); \
struct type *parent = NULL; \
while (tmp) { \
parent = tmp; \
if (val < 0) \
tmp = RB_LEFT(tmp, field); \
else \
tmp = RB_RIGHT(tmp, field); \
} \
return (parent); \
}
#define RB_NEGINF -1
#define RB_INF 1
#define RB_INSERT(name, x, y) name##_RB_INSERT(x, y)
#define RB_REMOVE(name, x, y) name##_RB_REMOVE(x, y)
#define RB_FIND(name, x, y) name##_RB_FIND(x, y)
#define RB_NFIND(name, x, y) name##_RB_NFIND(x, y)
#define RB_NEXT(name, x, y) name##_RB_NEXT(y)
#define RB_PREV(name, x, y) name##_RB_PREV(y)
#define RB_MIN(name, x) name##_RB_MINMAX(x, RB_NEGINF)
#define RB_MAX(name, x) name##_RB_MINMAX(x, RB_INF)
#define RB_FOREACH(x, name, head) \
for ((x) = RB_MIN(name, head); \
(x) != NULL; \
(x) = name##_RB_NEXT(x))
#define RB_FOREACH_FROM(x, name, y) \
for ((x) = (y); \
((x) != NULL) && ((y) = name##_RB_NEXT(x), (x) != NULL); \
(x) = (y))
#define RB_FOREACH_SAFE(x, name, head, y) \
for ((x) = RB_MIN(name, head); \
((x) != NULL) && ((y) = name##_RB_NEXT(x), (x) != NULL); \
(x) = (y))
#define RB_FOREACH_REVERSE(x, name, head) \
for ((x) = RB_MAX(name, head); \
(x) != NULL; \
(x) = name##_RB_PREV(x))
#define RB_FOREACH_REVERSE_FROM(x, name, y) \
for ((x) = (y); \
((x) != NULL) && ((y) = name##_RB_PREV(x), (x) != NULL); \
(x) = (y))
#define RB_FOREACH_REVERSE_SAFE(x, name, head, y) \
for ((x) = RB_MAX(name, head); \
((x) != NULL) && ((y) = name##_RB_PREV(x), (x) != NULL); \
(x) = (y))
#endif /* _SYS_TREE_H_ */

193
tpws/params.c Normal file
View File

@@ -0,0 +1,193 @@
#include "params.h"
#include <stdarg.h>
#include <syslog.h>
#include <errno.h>
int DLOG_FILE(FILE *F, const char *format, va_list args)
{
return vfprintf(F, format, args);
}
int DLOG_CON(const char *format, int syslog_priority, va_list args)
{
return DLOG_FILE(syslog_priority==LOG_ERR ? stderr : stdout, format, args);
}
int DLOG_FILENAME(const char *filename, const char *format, va_list args)
{
int r;
FILE *F = fopen(filename,"at");
if (F)
{
r = DLOG_FILE(F, format, args);
fclose(F);
}
else
r=-1;
return r;
}
static char syslog_buf[1024];
static size_t syslog_buf_sz=0;
static void syslog_buffered(int priority, const char *format, va_list args)
{
if (vsnprintf(syslog_buf+syslog_buf_sz,sizeof(syslog_buf)-syslog_buf_sz,format,args)>0)
{
syslog_buf_sz=strlen(syslog_buf);
// log when buffer is full or buffer ends with \n
if (syslog_buf_sz>=(sizeof(syslog_buf)-1) || (syslog_buf_sz && syslog_buf[syslog_buf_sz-1]=='\n'))
{
syslog(priority,"%s",syslog_buf);
syslog_buf_sz = 0;
}
}
}
static int DLOG_VA(const char *format, int syslog_priority, bool condup, int level, va_list args)
{
int r=0;
va_list args2;
if (condup && !(params.debug>=level && params.debug_target==LOG_TARGET_CONSOLE))
{
va_copy(args2,args);
DLOG_CON(format,syslog_priority,args2);
}
if (params.debug>=level)
{
switch(params.debug_target)
{
case LOG_TARGET_CONSOLE:
r = DLOG_CON(format,syslog_priority,args);
break;
case LOG_TARGET_FILE:
r = DLOG_FILENAME(params.debug_logfile,format,args);
break;
case LOG_TARGET_SYSLOG:
// skip newlines
syslog_buffered(syslog_priority,format,args);
r = 1;
break;
default:
break;
}
}
return r;
}
int DLOG(const char *format, int level, ...)
{
int r;
va_list args;
va_start(args, level);
r = DLOG_VA(format, LOG_DEBUG, false, level, args);
va_end(args);
return r;
}
int DLOG_CONDUP(const char *format, ...)
{
int r;
va_list args;
va_start(args, format);
r = DLOG_VA(format, LOG_DEBUG, true, 1, args);
va_end(args);
return r;
}
int DLOG_ERR(const char *format, ...)
{
int r;
va_list args;
va_start(args, format);
r = DLOG_VA(format, LOG_ERR, true, 1, args);
va_end(args);
return r;
}
int DLOG_PERROR(const char *s)
{
return DLOG_ERR("%s: %s\n", s, strerror(errno));
}
int LOG_APPEND(const char *filename, const char *format, va_list args)
{
int r;
FILE *F = fopen(filename,"at");
if (F)
{
fprint_localtime(F);
fprintf(F, " : ");
r = vfprintf(F, format, args);
fprintf(F, "\n");
fclose(F);
}
else
r=-1;
return r;
}
int HOSTLIST_DEBUGLOG_APPEND(const char *format, ...)
{
if (*params.hostlist_auto_debuglog)
{
int r;
va_list args;
va_start(args, format);
r = LOG_APPEND(params.hostlist_auto_debuglog, format, args);
va_end(args);
return r;
}
else
return 0;
}
struct desync_profile_list *dp_list_add(struct desync_profile_list_head *head)
{
struct desync_profile_list *entry = calloc(1,sizeof(struct desync_profile_list));
if (!entry) return NULL;
LIST_INIT(&entry->dp.hostlist_files);
LIST_INIT(&entry->dp.hostlist_exclude_files);
entry->dp.filter_ipv4 = entry->dp.filter_ipv6 = true;
memcpy(entry->dp.hostspell, "host", 4); // default hostspell
entry->dp.hostlist_auto_fail_threshold = HOSTLIST_AUTO_FAIL_THRESHOLD_DEFAULT;
entry->dp.hostlist_auto_fail_time = HOSTLIST_AUTO_FAIL_TIME_DEFAULT;
// add to the tail
struct desync_profile_list *dpn,*dpl=LIST_FIRST(&params.desync_profiles);
if (dpl)
{
while ((dpn=LIST_NEXT(dpl,next))) dpl = dpn;
LIST_INSERT_AFTER(dpl, entry, next);
}
else
LIST_INSERT_HEAD(&params.desync_profiles, entry, next);
return entry;
}
static void dp_entry_destroy(struct desync_profile_list *entry)
{
strlist_destroy(&entry->dp.hostlist_files);
strlist_destroy(&entry->dp.hostlist_exclude_files);
StrPoolDestroy(&entry->dp.hostlist_exclude);
StrPoolDestroy(&entry->dp.hostlist);
HostFailPoolDestroy(&entry->dp.hostlist_auto_fail_counters);
free(entry);
}
void dp_list_destroy(struct desync_profile_list_head *head)
{
struct desync_profile_list *entry;
while ((entry = LIST_FIRST(head)))
{
LIST_REMOVE(entry, next);
dp_entry_destroy(entry);
}
}
bool dp_list_have_autohostlist(struct desync_profile_list_head *head)
{
struct desync_profile_list *dpl;
LIST_FOREACH(dpl, head, next)
if (*dpl->dp.hostlist_auto_filename)
return true;
return false;
}

124
tpws/params.h Normal file
View File

@@ -0,0 +1,124 @@
#pragma once
#include <net/if.h>
#include <stdbool.h>
#include <stdint.h>
#include <sys/param.h>
#include <sys/queue.h>
#include <time.h>
#include "tpws.h"
#include "pools.h"
#include "helpers.h"
#include "protocol.h"
#define HOSTLIST_AUTO_FAIL_THRESHOLD_DEFAULT 3
#define HOSTLIST_AUTO_FAIL_TIME_DEFAULT 60
enum bindll { unwanted=0, no, prefer, force };
#define MAX_BINDS 32
struct bind_s
{
char bindaddr[64],bindiface[IF_NAMESIZE];
bool bind_if6;
enum bindll bindll;
int bind_wait_ifup,bind_wait_ip,bind_wait_ip_ll;
};
enum log_target { LOG_TARGET_CONSOLE=0, LOG_TARGET_FILE, LOG_TARGET_SYSLOG };
struct desync_profile
{
int n; // number of the profile
bool hostcase, hostdot, hosttab, hostnospace, methodspace, methodeol, unixeol, domcase;
int hostpad;
char hostspell[4];
enum httpreqpos split_http_req;
enum tlspos tlsrec;
int tlsrec_pos;
enum tlspos split_tls;
bool split_any_protocol;
int split_pos;
bool disorder, disorder_http, disorder_tls;
bool oob, oob_http, oob_tls;
uint8_t oob_byte;
int mss;
bool tamper_start_n,tamper_cutoff_n;
unsigned int tamper_start,tamper_cutoff;
bool filter_ipv4,filter_ipv6;
port_filter pf_tcp;
strpool *hostlist, *hostlist_exclude;
struct str_list_head hostlist_files, hostlist_exclude_files;
char hostlist_auto_filename[PATH_MAX];
int hostlist_auto_fail_threshold, hostlist_auto_fail_time;
time_t hostlist_auto_mod_time;
hostfail_pool *hostlist_auto_fail_counters;
};
struct desync_profile_list {
struct desync_profile dp;
LIST_ENTRY(desync_profile_list) next;
};
LIST_HEAD(desync_profile_list_head, desync_profile_list);
struct desync_profile_list *dp_list_add(struct desync_profile_list_head *head);
void dp_list_destroy(struct desync_profile_list_head *head);
bool dp_list_have_autohostlist(struct desync_profile_list_head *head);
struct params_s
{
int debug;
enum log_target debug_target;
char debug_logfile[PATH_MAX];
struct bind_s binds[MAX_BINDS];
int binds_last;
bool bind_wait_only;
uint16_t port;
struct sockaddr_in connect_bind4;
struct sockaddr_in6 connect_bind6;
char connect_bind6_ifname[IF_NAMESIZE];
uint8_t proxy_type;
bool no_resolve;
bool skip_nodelay;
bool droproot;
uid_t uid;
gid_t gid;
bool daemon;
char pidfile[256];
int maxconn,resolver_threads,maxfiles,max_orphan_time;
int local_rcvbuf,local_sndbuf,remote_rcvbuf,remote_sndbuf;
#if defined(__linux__) || defined(__APPLE__)
int tcp_user_timeout_local,tcp_user_timeout_remote;
#endif
#if defined(BSD)
bool pf_enable;
#endif
#ifdef SPLICE_PRESENT
bool nosplice;
#endif
int ttl_default;
char hostlist_auto_debuglog[PATH_MAX];
bool tamper; // any tamper option is set
bool tamper_lim; // tamper-start or tamper-cutoff set in any profile
struct desync_profile_list_head desync_profiles;
};
extern struct params_s params;
int DLOG(const char *format, int level, ...);
int DLOG_CONDUP(const char *format, ...);
int DLOG_ERR(const char *format, ...);
int DLOG_PERROR(const char *s);
int HOSTLIST_DEBUGLOG_APPEND(const char *format, ...);
#define VPRINT(format, ...) DLOG(format, 1, ##__VA_ARGS__)
#define DBGPRINT(format, ...) DLOG(format, 2, ##__VA_ARGS__)

153
tpws/pools.c Normal file
View File

@@ -0,0 +1,153 @@
#define _GNU_SOURCE
#include "pools.h"
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#define DESTROY_STR_POOL(etype, ppool) \
etype *elem, *tmp; \
HASH_ITER(hh, *ppool, elem, tmp) { \
free(elem->str); \
HASH_DEL(*ppool, elem); \
free(elem); \
}
#define ADD_STR_POOL(etype, ppool, keystr, keystr_len) \
etype *elem; \
if (!(elem = (etype*)malloc(sizeof(etype)))) \
return false; \
if (!(elem->str = malloc(keystr_len + 1))) \
{ \
free(elem); \
return false; \
} \
memcpy(elem->str, keystr, keystr_len); \
elem->str[keystr_len] = 0; \
oom = false; \
HASH_ADD_KEYPTR(hh, *ppool, elem->str, strlen(elem->str), elem); \
if (oom) \
{ \
free(elem->str); \
free(elem); \
return false; \
}
#undef uthash_nonfatal_oom
#define uthash_nonfatal_oom(elt) ut_oom_recover(elt)
static bool oom = false;
static void ut_oom_recover(void *elem)
{
oom = true;
}
// for not zero terminated strings
bool StrPoolAddStrLen(strpool **pp, const char *s, size_t slen)
{
ADD_STR_POOL(strpool, pp, s, slen)
return true;
}
// for zero terminated strings
bool StrPoolAddStr(strpool **pp, const char *s)
{
return StrPoolAddStrLen(pp, s, strlen(s));
}
bool StrPoolCheckStr(strpool *p, const char *s)
{
strpool *elem;
HASH_FIND_STR(p, s, elem);
return elem != NULL;
}
void StrPoolDestroy(strpool **pp)
{
DESTROY_STR_POOL(strpool, pp)
}
void HostFailPoolDestroy(hostfail_pool **pp)
{
DESTROY_STR_POOL(hostfail_pool, pp)
}
hostfail_pool * HostFailPoolAdd(hostfail_pool **pp,const char *s,int fail_time)
{
size_t slen = strlen(s);
ADD_STR_POOL(hostfail_pool, pp, s, slen)
elem->expire = time(NULL) + fail_time;
elem->counter = 0;
return elem;
}
hostfail_pool *HostFailPoolFind(hostfail_pool *p,const char *s)
{
hostfail_pool *elem;
HASH_FIND_STR(p, s, elem);
return elem;
}
void HostFailPoolDel(hostfail_pool **p, hostfail_pool *elem)
{
HASH_DEL(*p, elem);
free(elem);
}
void HostFailPoolPurge(hostfail_pool **pp)
{
hostfail_pool *elem, *tmp;
time_t now = time(NULL);
HASH_ITER(hh, *pp, elem, tmp)
{
if (now >= elem->expire)
{
free(elem->str);
HASH_DEL(*pp, elem);
free(elem);
}
}
}
static time_t host_fail_purge_prev=0;
void HostFailPoolPurgeRateLimited(hostfail_pool **pp)
{
time_t now = time(NULL);
// do not purge too often to save resources
if (host_fail_purge_prev != now)
{
HostFailPoolPurge(pp);
host_fail_purge_prev = now;
}
}
void HostFailPoolDump(hostfail_pool *p)
{
hostfail_pool *elem, *tmp;
time_t now = time(NULL);
HASH_ITER(hh, p, elem, tmp)
printf("host=%s counter=%d time_left=%lld\n",elem->str,elem->counter,(long long int)elem->expire-now);
}
bool strlist_add(struct str_list_head *head, const char *filename)
{
struct str_list *entry = malloc(sizeof(struct str_list));
if (!entry) return false;
entry->str = strdup(filename);
if (!entry->str)
{
free(entry);
return false;
}
LIST_INSERT_HEAD(head, entry, next);
return true;
}
static void strlist_entry_destroy(struct str_list *entry)
{
if (entry->str) free(entry->str);
free(entry);
}
void strlist_destroy(struct str_list_head *head)
{
struct str_list *entry;
while ((entry = LIST_FIRST(head)))
{
LIST_REMOVE(entry, next);
strlist_entry_destroy(entry);
}
}

46
tpws/pools.h Normal file
View File

@@ -0,0 +1,46 @@
#pragma once
#include <stdbool.h>
#include <ctype.h>
#include <sys/queue.h>
#include <time.h>
//#define HASH_BLOOM 20
#define HASH_NONFATAL_OOM 1
#define HASH_FUNCTION HASH_BER
#include "uthash.h"
typedef struct strpool {
char *str; /* key */
UT_hash_handle hh; /* makes this structure hashable */
} strpool;
void StrPoolDestroy(strpool **pp);
bool StrPoolAddStr(strpool **pp,const char *s);
bool StrPoolAddStrLen(strpool **pp,const char *s,size_t slen);
bool StrPoolCheckStr(strpool *p,const char *s);
struct str_list {
char *str;
LIST_ENTRY(str_list) next;
};
LIST_HEAD(str_list_head, str_list);
typedef struct hostfail_pool {
char *str; /* key */
int counter; /* value */
time_t expire; /* when to expire record (unixtime) */
UT_hash_handle hh; /* makes this structure hashable */
} hostfail_pool;
void HostFailPoolDestroy(hostfail_pool **pp);
hostfail_pool *HostFailPoolAdd(hostfail_pool **pp,const char *s,int fail_time);
hostfail_pool *HostFailPoolFind(hostfail_pool *p,const char *s);
void HostFailPoolDel(hostfail_pool **pp, hostfail_pool *elem);
void HostFailPoolPurge(hostfail_pool **pp);
void HostFailPoolPurgeRateLimited(hostfail_pool **pp);
void HostFailPoolDump(hostfail_pool *p);
bool strlist_add(struct str_list_head *head, const char *filename);
void strlist_destroy(struct str_list_head *head);

347
tpws/protocol.c Normal file
View File

@@ -0,0 +1,347 @@
#define _GNU_SOURCE
#include "protocol.h"
#include "helpers.h"
#include <string.h>
#include <ctype.h>
#include <arpa/inet.h>
#include <string.h>
const char *http_methods[] = { "GET /","POST /","HEAD /","OPTIONS /","PUT /","DELETE /","CONNECT /","TRACE /",NULL };
const char *HttpMethod(const uint8_t *data, size_t len)
{
const char **method;
size_t method_len;
for (method = http_methods; *method; method++)
{
method_len = strlen(*method);
if (method_len <= len && !memcmp(data, *method, method_len))
return *method;
}
return NULL;
}
bool IsHttp(const uint8_t *data, size_t len)
{
return !!HttpMethod(data,len);
}
static bool IsHostAt(const uint8_t *p)
{
return \
p[0]=='\n' &&
(p[1]=='H' || p[1]=='h') &&
(p[2]=='o' || p[2]=='O') &&
(p[3]=='s' || p[3]=='S') &&
(p[4]=='t' || p[4]=='T') &&
p[5]==':';
}
static uint8_t *FindHostIn(uint8_t *buf, size_t bs)
{
size_t pos;
if (bs<6) return NULL;
bs-=6;
for(pos=0;pos<=bs;pos++)
if (IsHostAt(buf+pos))
return buf+pos;
return NULL;
}
static const uint8_t *FindHostInConst(const uint8_t *buf, size_t bs)
{
size_t pos;
if (bs<6) return NULL;
bs-=6;
for(pos=0;pos<=bs;pos++)
if (IsHostAt(buf+pos))
return buf+pos;
return NULL;
}
// pHost points to "Host: ..."
bool HttpFindHost(uint8_t **pHost,uint8_t *buf,size_t bs)
{
if (!*pHost)
{
*pHost = FindHostIn(buf, bs);
if (*pHost) (*pHost)++;
}
return !!*pHost;
}
bool HttpFindHostConst(const uint8_t **pHost,const uint8_t *buf,size_t bs)
{
if (!*pHost)
{
*pHost = FindHostInConst(buf, bs);
if (*pHost) (*pHost)++;
}
return !!*pHost;
}
bool IsHttpReply(const uint8_t *data, size_t len)
{
// HTTP/1.x 200\r\n
return len>14 && !memcmp(data,"HTTP/1.",7) && (data[7]=='0' || data[7]=='1') && data[8]==' ' &&
data[9]>='0' && data[9]<='9' &&
data[10]>='0' && data[10]<='9' &&
data[11]>='0' && data[11]<='9';
}
int HttpReplyCode(const uint8_t *data, size_t len)
{
return (data[9]-'0')*100 + (data[10]-'0')*10 + (data[11]-'0');
}
bool HttpExtractHeader(const uint8_t *data, size_t len, const char *header, char *buf, size_t len_buf)
{
const uint8_t *p, *s, *e = data + len;
p = (uint8_t*)strncasestr((char*)data, header, len);
if (!p) return false;
p += strlen(header);
while (p < e && (*p == ' ' || *p == '\t')) p++;
s = p;
while (s < e && (*s != '\r' && *s != '\n' && *s != ' ' && *s != '\t')) s++;
if (s > p)
{
size_t slen = s - p;
if (buf && len_buf)
{
if (slen >= len_buf) slen = len_buf - 1;
for (size_t i = 0; i < slen; i++) buf[i] = tolower(p[i]);
buf[slen] = 0;
}
return true;
}
return false;
}
bool HttpExtractHost(const uint8_t *data, size_t len, char *host, size_t len_host)
{
return HttpExtractHeader(data, len, "\nHost:", host, len_host);
}
const char *HttpFind2ndLevelDomain(const char *host)
{
const char *p=NULL;
if (*host)
{
for (p = host + strlen(host)-1; p>host && *p!='.'; p--);
if (*p=='.') for (p--; p>host && *p!='.'; p--);
if (*p=='.') p++;
}
return p;
}
// DPI redirects are global redirects to another domain
bool HttpReplyLooksLikeDPIRedirect(const uint8_t *data, size_t len, const char *host)
{
char loc[256],*redirect_host, *p;
int code;
if (!host || !*host) return false;
code = HttpReplyCode(data,len);
if ((code!=302 && code!=307) || !HttpExtractHeader(data,len,"\nLocation:",loc,sizeof(loc))) return false;
// something like : https://censor.net/badpage.php?reason=denied&source=RKN
if (!strncmp(loc,"http://",7))
redirect_host=loc+7;
else if (!strncmp(loc,"https://",8))
redirect_host=loc+8;
else
return false;
// somethinkg like : censor.net/badpage.php?reason=denied&source=RKN
for(p=redirect_host; *p && *p!='/' ; p++);
*p=0;
if (!*redirect_host) return false;
// somethinkg like : censor.net
// extract 2nd level domains
const char *dhost = HttpFind2ndLevelDomain(host);
const char *drhost = HttpFind2ndLevelDomain(redirect_host);
return strcasecmp(dhost, drhost)!=0;
}
size_t HttpPos(enum httpreqpos tpos_type, size_t hpos_pos, const uint8_t *http, size_t sz)
{
const uint8_t *method, *host=NULL;
int i;
switch(tpos_type)
{
case httpreqpos_method:
// recognize some tpws pre-applied hacks
method=http;
if (sz<10) break;
if (*method=='\n' || *method=='\r') method++;
if (*method=='\n' || *method=='\r') method++;
for (i=0;i<7;i++) if (*method>='A' && *method<='Z') method++;
if (i<3 || *method!=' ') break;
return method-http-1;
case httpreqpos_host:
if (HttpFindHostConst(&host,http,sz) && (host-http+7)<sz)
{
host+=5;
if (*host==' ') host++;
return host-http;
}
break;
case httpreqpos_pos:
break;
default:
return 0;
}
return hpos_pos<sz ? hpos_pos : 0;
}
uint16_t TLSRecordDataLen(const uint8_t *data)
{
return pntoh16(data + 3);
}
size_t TLSRecordLen(const uint8_t *data)
{
return TLSRecordDataLen(data) + 5;
}
bool IsTLSRecordFull(const uint8_t *data, size_t len)
{
return TLSRecordLen(data)<=len;
}
bool IsTLSClientHello(const uint8_t *data, size_t len, bool bPartialIsOK)
{
return len >= 6 && data[0] == 0x16 && data[1] == 0x03 && data[2] <= 0x03 && data[5] == 0x01 && (bPartialIsOK || TLSRecordLen(data) <= len);
}
// bPartialIsOK=true - accept partial packets not containing the whole TLS message
bool TLSFindExtInHandshake(const uint8_t *data, size_t len, uint16_t type, const uint8_t **ext, size_t *len_ext, bool bPartialIsOK)
{
// +0
// u8 HandshakeType: ClientHello
// u24 Length
// u16 Version
// c[32] random
// u8 SessionIDLength
// <SessionID>
// u16 CipherSuitesLength
// <CipherSuites>
// u8 CompressionMethodsLength
// <CompressionMethods>
// u16 ExtensionsLength
size_t l, ll;
l = 1 + 3 + 2 + 32;
// SessionIDLength
if (len < (l + 1)) return false;
if (!bPartialIsOK)
{
ll = data[1] << 16 | data[2] << 8 | data[3]; // HandshakeProtocol length
if (len < (ll + 4)) return false;
}
l += data[l] + 1;
// CipherSuitesLength
if (len < (l + 2)) return false;
l += pntoh16(data + l) + 2;
// CompressionMethodsLength
if (len < (l + 1)) return false;
l += data[l] + 1;
// ExtensionsLength
if (len < (l + 2)) return false;
data += l; len -= l;
l = pntoh16(data);
data += 2; len -= 2;
if (bPartialIsOK)
{
if (len < l) l = len;
}
else
{
if (len < l) return false;
}
while (l >= 4)
{
uint16_t etype = pntoh16(data);
size_t elen = pntoh16(data + 2);
data += 4; l -= 4;
if (l < elen) break;
if (etype == type)
{
if (ext && len_ext)
{
*ext = data;
*len_ext = elen;
}
return true;
}
data += elen; l -= elen;
}
return false;
}
bool TLSFindExt(const uint8_t *data, size_t len, uint16_t type, const uint8_t **ext, size_t *len_ext, bool bPartialIsOK)
{
// +0
// u8 ContentType: Handshake
// u16 Version: TLS1.0
// u16 Length
size_t reclen;
if (!IsTLSClientHello(data, len, bPartialIsOK)) return false;
reclen=TLSRecordLen(data);
if (reclen<len) len=reclen; // correct len if it has more data than the first tls record has
return TLSFindExtInHandshake(data + 5, len - 5, type, ext, len_ext, bPartialIsOK);
}
static bool TLSExtractHostFromExt(const uint8_t *ext, size_t elen, char *host, size_t len_host)
{
// u16 data+0 - name list length
// u8 data+2 - server name type. 0=host_name
// u16 data+3 - server name length
if (elen < 5 || ext[2] != 0) return false;
size_t slen = pntoh16(ext + 3);
ext += 5; elen -= 5;
if (slen < elen) return false;
if (host && len_host)
{
if (slen >= len_host) slen = len_host - 1;
for (size_t i = 0; i < slen; i++) host[i] = tolower(ext[i]);
host[slen] = 0;
}
return true;
}
bool TLSHelloExtractHost(const uint8_t *data, size_t len, char *host, size_t len_host, bool bPartialIsOK)
{
const uint8_t *ext;
size_t elen;
if (!TLSFindExt(data, len, 0, &ext, &elen, bPartialIsOK)) return false;
return TLSExtractHostFromExt(ext, elen, host, len_host);
}
bool TLSHelloExtractHostFromHandshake(const uint8_t *data, size_t len, char *host, size_t len_host, bool bPartialIsOK)
{
const uint8_t *ext;
size_t elen;
if (!TLSFindExtInHandshake(data, len, 0, &ext, &elen, bPartialIsOK)) return false;
return TLSExtractHostFromExt(ext, elen, host, len_host);
}
size_t TLSPos(enum tlspos tpos_type, size_t tpos_pos, const uint8_t *tls, size_t sz, uint8_t type)
{
size_t elen;
const uint8_t *ext;
switch(tpos_type)
{
case tlspos_sni:
case tlspos_sniext:
if (TLSFindExt(tls,sz,0,&ext,&elen,false))
return (tpos_type==tlspos_sni) ? ext-tls+6 : ext-tls+1;
// fall through
case tlspos_pos:
return tpos_pos<sz ? tpos_pos : 0;
default:
return 0;
}
}

33
tpws/protocol.h Normal file
View File

@@ -0,0 +1,33 @@
#pragma once
#include <stddef.h>
#include <stdint.h>
#include <stdbool.h>
extern const char *http_methods[9];
const char *HttpMethod(const uint8_t *data, size_t len);
bool IsHttp(const uint8_t *data, size_t len);
bool HttpFindHost(uint8_t **pHost,uint8_t *buf,size_t bs);
bool HttpFindHostConst(const uint8_t **pHost,const uint8_t *buf,size_t bs);
// header must be passed like this : "\nHost:"
bool HttpExtractHeader(const uint8_t *data, size_t len, const char *header, char *buf, size_t len_buf);
bool HttpExtractHost(const uint8_t *data, size_t len, char *host, size_t len_host);
bool IsHttpReply(const uint8_t *data, size_t len);
const char *HttpFind2ndLevelDomain(const char *host);
// must be pre-checked by IsHttpReply
int HttpReplyCode(const uint8_t *data, size_t len);
// must be pre-checked by IsHttpReply
bool HttpReplyLooksLikeDPIRedirect(const uint8_t *data, size_t len, const char *host);
enum httpreqpos { httpreqpos_none = 0, httpreqpos_method, httpreqpos_host, httpreqpos_pos };
size_t HttpPos(enum httpreqpos tpos_type, size_t hpos_pos, const uint8_t *http, size_t sz);
uint16_t TLSRecordDataLen(const uint8_t *data);
size_t TLSRecordLen(const uint8_t *data);
bool IsTLSRecordFull(const uint8_t *data, size_t len);
bool IsTLSClientHello(const uint8_t *data, size_t len, bool bPartialIsOK);
bool TLSFindExt(const uint8_t *data, size_t len, uint16_t type, const uint8_t **ext, size_t *len_ext, bool bPartialIsOK);
bool TLSFindExtInHandshake(const uint8_t *data, size_t len, uint16_t type, const uint8_t **ext, size_t *len_ext, bool bPartialIsOK);
bool TLSHelloExtractHost(const uint8_t *data, size_t len, char *host, size_t len_host, bool bPartialIsOK);
bool TLSHelloExtractHostFromHandshake(const uint8_t *data, size_t len, char *host, size_t len_host, bool bPartialIsOK);
enum tlspos { tlspos_none = 0, tlspos_sni, tlspos_sniext, tlspos_pos };
size_t TLSPos(enum tlspos tpos_type, size_t tpos_pos, const uint8_t *tls, size_t sz, uint8_t type);

230
tpws/redirect.c Normal file
View File

@@ -0,0 +1,230 @@
#include "redirect.h"
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <arpa/inet.h>
#include <sys/ioctl.h>
#include "params.h"
#include "helpers.h"
#ifdef __linux__
#include <linux/netfilter_ipv4.h>
#ifndef IP6T_SO_ORIGINAL_DST
#define IP6T_SO_ORIGINAL_DST 80
#endif
#endif
#if defined(BSD)
#include <net/if.h>
#include <net/pfvar.h>
static int redirector_fd=-1;
void redir_close(void)
{
if (redirector_fd!=-1)
{
close(redirector_fd);
redirector_fd = -1;
DBGPRINT("closed redirector\n");
}
}
static bool redir_open_private(const char *fname, int flags)
{
redir_close();
redirector_fd = open(fname, flags);
if (redirector_fd < 0)
{
DLOG_PERROR("redir_openv_private");
return false;
}
DBGPRINT("opened redirector %s\n",fname);
return true;
}
bool redir_init(void)
{
return params.pf_enable ? redir_open_private("/dev/pf", O_RDONLY) : true;
}
static bool destination_from_pf(const struct sockaddr *accept_sa, struct sockaddr_storage *orig_dst)
{
struct pfioc_natlook nl;
struct sockaddr_storage asa2;
if (redirector_fd==-1) return false;
if (params.debug>=2)
{
char s[48],s2[48];
*s=0; ntop46_port(accept_sa, s, sizeof(s));
*s2=0; ntop46_port((struct sockaddr *)orig_dst, s2, sizeof(s2));
DBGPRINT("destination_from_pf %s %s\n",s,s2);
}
saconvmapped(orig_dst);
if (accept_sa->sa_family==AF_INET6 && orig_dst->ss_family==AF_INET)
{
memcpy(&asa2,accept_sa,sizeof(struct sockaddr_in6));
saconvmapped(&asa2);
accept_sa = (struct sockaddr*)&asa2;
}
if (params.debug>=2)
{
char s[48],s2[48];
*s=0; ntop46_port(accept_sa, s, sizeof(s));
*s2=0; ntop46_port((struct sockaddr *)orig_dst, s2, sizeof(s2));
DBGPRINT("destination_from_pf (saconvmapped) %s %s\n",s,s2);
}
if (accept_sa->sa_family!=orig_dst->ss_family)
{
DBGPRINT("accept_sa and orig_dst sa_family mismatch : %d %d\n", accept_sa->sa_family, orig_dst->ss_family);
return false;
}
memset(&nl, 0, sizeof(nl));
nl.proto = IPPROTO_TCP;
nl.direction = PF_OUT;
nl.af = orig_dst->ss_family;
switch(orig_dst->ss_family)
{
case AF_INET:
{
struct sockaddr_in *sin = (struct sockaddr_in *)orig_dst;
nl.daddr.v4.s_addr = sin->sin_addr.s_addr;
nl.saddr.v4.s_addr = ((struct sockaddr_in*)accept_sa)->sin_addr.s_addr;
#ifdef __APPLE__
nl.sxport.port = ((struct sockaddr_in*)accept_sa)->sin_port;
nl.dxport.port = sin->sin_port;
#else
nl.sport = ((struct sockaddr_in*)accept_sa)->sin_port;
nl.dport = sin->sin_port;
#endif
}
break;
case AF_INET6:
{
struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)orig_dst;
nl.daddr.v6 = sin6->sin6_addr;
nl.saddr.v6 = ((struct sockaddr_in6*)accept_sa)->sin6_addr;
#ifdef __APPLE__
nl.sxport.port = ((struct sockaddr_in6*)accept_sa)->sin6_port;
nl.dxport.port = sin6->sin6_port;
#else
nl.sport = ((struct sockaddr_in6*)accept_sa)->sin6_port;
nl.dport = sin6->sin6_port;
#endif
}
break;
default:
DBGPRINT("destination_from_pf : unexpected address family %d\n",orig_dst->ss_family);
return false;
}
if (ioctl(redirector_fd, DIOCNATLOOK, &nl) < 0)
{
DLOG_PERROR("ioctl(DIOCNATLOOK) failed");
return false;
}
DBGPRINT("destination_from_pf : got orig dest addr from pf\n");
switch(nl.af)
{
case AF_INET:
orig_dst->ss_family = nl.af;
#ifdef __APPLE__
((struct sockaddr_in*)orig_dst)->sin_port = nl.rdxport.port;
#else
((struct sockaddr_in*)orig_dst)->sin_port = nl.rdport;
#endif
((struct sockaddr_in*)orig_dst)->sin_addr = nl.rdaddr.v4;
break;
case AF_INET6:
orig_dst->ss_family = nl.af;
#ifdef __APPLE__
((struct sockaddr_in6*)orig_dst)->sin6_port = nl.rdxport.port;
#else
((struct sockaddr_in6*)orig_dst)->sin6_port = nl.rdport;
#endif
((struct sockaddr_in6*)orig_dst)->sin6_addr = nl.rdaddr.v6;
break;
default:
DBGPRINT("destination_from_pf : DIOCNATLOOK returned unexpected address family %d\n",nl.af);
return false;
}
return true;
}
#else
bool redir_init(void) {return true;}
void redir_close(void) {};
#endif
//Store the original destination address in orig_dst
bool get_dest_addr(int sockfd, const struct sockaddr *accept_sa, struct sockaddr_storage *orig_dst)
{
char orig_dst_str[INET6_ADDRSTRLEN];
socklen_t addrlen = sizeof(*orig_dst);
int r;
memset(orig_dst, 0, addrlen);
//For UDP transparent proxying:
//Set IP_RECVORIGDSTADDR socket option for getting the original
//destination of a datagram
#ifdef __linux__
// DNAT
r=getsockopt(sockfd, SOL_IP, SO_ORIGINAL_DST, (struct sockaddr*) orig_dst, &addrlen);
if (r<0)
r = getsockopt(sockfd, SOL_IPV6, IP6T_SO_ORIGINAL_DST, (struct sockaddr*) orig_dst, &addrlen);
if (r<0)
{
DBGPRINT("both SO_ORIGINAL_DST and IP6T_SO_ORIGINAL_DST failed !\n");
#endif
// TPROXY : socket is bound to original destination
r=getsockname(sockfd, (struct sockaddr*) orig_dst, &addrlen);
if (r<0)
{
DLOG_PERROR("getsockname");
return false;
}
if (orig_dst->ss_family==AF_INET6)
((struct sockaddr_in6*)orig_dst)->sin6_scope_id=0; // or MacOS will not connect()
#ifdef BSD
if (params.pf_enable && !destination_from_pf(accept_sa, orig_dst))
DBGPRINT("pf filter destination_from_pf failed\n");
#endif
#ifdef __linux__
}
#endif
if (saconvmapped(orig_dst))
DBGPRINT("Original destination : converted ipv6 mapped address to ipv4\n");
if (params.debug)
{
if (orig_dst->ss_family == AF_INET)
{
inet_ntop(AF_INET, &(((struct sockaddr_in*) orig_dst)->sin_addr), orig_dst_str, INET_ADDRSTRLEN);
VPRINT("Original destination for socket fd=%d : %s:%d\n", sockfd,orig_dst_str, htons(((struct sockaddr_in*) orig_dst)->sin_port));
}
else if (orig_dst->ss_family == AF_INET6)
{
inet_ntop(AF_INET6,&(((struct sockaddr_in6*) orig_dst)->sin6_addr), orig_dst_str, INET6_ADDRSTRLEN);
VPRINT("Original destination for socket fd=%d : [%s]:%d\n", sockfd,orig_dst_str, htons(((struct sockaddr_in6*) orig_dst)->sin6_port));
}
}
return true;
}

9
tpws/redirect.h Normal file
View File

@@ -0,0 +1,9 @@
#pragma once
#include <stdbool.h>
#include <netinet/in.h>
#include <sys/socket.h>
bool get_dest_addr(int sockfd, const struct sockaddr *accept_sa, struct sockaddr_storage *orig_dst);
bool redir_init(void);
void redir_close(void);

264
tpws/resolver.c Normal file
View File

@@ -0,0 +1,264 @@
#define _GNU_SOURCE
#include "resolver.h"
#include "params.h"
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <semaphore.h>
#include <fcntl.h>
#include <pthread.h>
#include <signal.h>
#include <sys/syscall.h>
#include <errno.h>
#include <unistd.h>
#define SIG_BREAK SIGUSR1
#ifdef __APPLE__
static const char *sem_name="/tpws_resolver";
#endif
TAILQ_HEAD(resolve_tailhead, resolve_item);
typedef struct
{
int fd_signal_pipe;
sem_t *sem;
#ifndef __APPLE__
sem_t _sem;
#endif
struct resolve_tailhead resolve_list;
pthread_mutex_t resolve_list_lock;
int threads;
pthread_t *thread;
bool bInit, bStop;
} t_resolver;
static t_resolver resolver = { .bInit = false };
#define rlist_lock pthread_mutex_lock(&resolver.resolve_list_lock)
#define rlist_unlock pthread_mutex_unlock(&resolver.resolve_list_lock)
static void resolver_clear_list(void)
{
struct resolve_item *ri;
for (;;)
{
ri = TAILQ_FIRST(&resolver.resolve_list);
if (!ri) break;
TAILQ_REMOVE(&resolver.resolve_list, ri, next);
free(ri);
}
}
int resolver_thread_count(void)
{
return resolver.bInit ? resolver.threads : 0;
}
static void *resolver_thread(void *arg)
{
int r;
sigset_t signal_mask;
sigemptyset(&signal_mask);
sigaddset(&signal_mask, SIG_BREAK);
//printf("resolver_thread %d start\n",syscall(SYS_gettid));
for(;;)
{
if (resolver.bStop) break;
r = sem_wait(resolver.sem);
if (resolver.bStop) break;
if (r)
{
if (errno!=EINTR)
{
DLOG_PERROR("sem_wait (resolver_thread)");
break; // fatal err
}
}
else
{
struct resolve_item *ri;
ssize_t wr;
rlist_lock;
ri = TAILQ_FIRST(&resolver.resolve_list);
if (ri) TAILQ_REMOVE(&resolver.resolve_list, ri, next);
rlist_unlock;
if (ri)
{
struct addrinfo *ai,hints;
char sport[6];
//printf("THREAD %d GOT JOB %s\n", syscall(SYS_gettid), ri->dom);
snprintf(sport,sizeof(sport),"%u",ri->port);
memset(&hints, 0, sizeof(struct addrinfo));
hints.ai_socktype = SOCK_STREAM;
// unfortunately getaddrinfo cannot be interrupted with a signal. we cannot cancel a query
ri->ga_res = getaddrinfo(ri->dom,sport,&hints,&ai);
if (!ri->ga_res)
{
memcpy(&ri->ss, ai->ai_addr, ai->ai_addrlen);
freeaddrinfo(ai);
}
//printf("THREAD %d END JOB %s FIRST=%p\n", syscall(SYS_gettid), ri->dom, TAILQ_FIRST(&resolver.resolve_list));
// never interrupt this
pthread_sigmask(SIG_BLOCK, &signal_mask, NULL);
wr = write(resolver.fd_signal_pipe,&ri,sizeof(void*));
if (wr<0)
{
free(ri);
DLOG_PERROR("write resolve_pipe");
}
else if (wr!=sizeof(void*))
{
// partial pointer write is FATAL. in any case it will cause pointer corruption and coredump
free(ri);
DLOG_ERR("write resolve_pipe : not full write\n");
exit(1000);
}
pthread_sigmask(SIG_UNBLOCK, &signal_mask, NULL);
}
}
}
//printf("resolver_thread %d exit\n",syscall(SYS_gettid));
return NULL;
}
static void sigbreak(int sig)
{
}
void resolver_deinit(void)
{
if (resolver.bInit)
{
resolver.bStop = true;
// wait all threads to terminate
for (int t = 0; t < resolver.threads; t++)
pthread_kill(resolver.thread[t], SIGUSR1);
for (int t = 0; t < resolver.threads; t++)
{
pthread_kill(resolver.thread[t], SIGUSR1);
pthread_join(resolver.thread[t], NULL);
}
pthread_mutex_destroy(&resolver.resolve_list_lock);
free(resolver.thread);
#ifdef __APPLE__
sem_close(resolver.sem);
#else
sem_destroy(resolver.sem);
#endif
resolver_clear_list();
memset(&resolver,0,sizeof(resolver));
}
}
bool resolver_init(int threads, int fd_signal_pipe)
{
int t;
struct sigaction action;
if (threads<1 || resolver.bInit) return false;
memset(&resolver,0,sizeof(resolver));
resolver.bInit = true;
#ifdef __APPLE__
// MacOS does not support unnamed semaphores
char sn[64];
snprintf(sn,sizeof(sn),"%s_%d",sem_name,getpid());
resolver.sem = sem_open(sn,O_CREAT,0600,0);
if (resolver.sem==SEM_FAILED)
{
DLOG_PERROR("sem_open");
goto ex;
}
// unlink immediately to remove tails
sem_unlink(sn);
#else
if (sem_init(&resolver._sem,0,0)==-1)
{
DLOG_PERROR("sem_init");
goto ex;
}
resolver.sem = &resolver._sem;
#endif
if (pthread_mutex_init(&resolver.resolve_list_lock, NULL)) goto ex;
resolver.fd_signal_pipe = fd_signal_pipe;
TAILQ_INIT(&resolver.resolve_list);
// start as many threads as we can up to specified number
resolver.thread = malloc(sizeof(pthread_t)*threads);
if (!resolver.thread) goto ex;
memset(&action,0,sizeof(action));
action.sa_handler = sigbreak;
sigaction(SIG_BREAK, &action, NULL);
pthread_attr_t attr;
if (pthread_attr_init(&attr)) goto ex;
// set minimum thread stack size
if (pthread_attr_setstacksize(&attr,PTHREAD_STACK_MIN>20480 ? PTHREAD_STACK_MIN : 20480))
{
pthread_attr_destroy(&attr);
goto ex;
}
for(t=0, resolver.threads=threads ; t<threads ; t++)
{
if (pthread_create(resolver.thread + t, &attr, resolver_thread, NULL))
{
resolver.threads=t;
break;
}
}
pthread_attr_destroy(&attr);
if (!resolver.threads) goto ex;
return true;
ex:
resolver_deinit();
return false;
}
struct resolve_item *resolver_queue(const char *dom, uint16_t port, void *ptr)
{
struct resolve_item *ri = calloc(1,sizeof(struct resolve_item));
if (!ri) return NULL;
strncpy(ri->dom,dom,sizeof(ri->dom));
ri->dom[sizeof(ri->dom)-1] = 0;
ri->port = port;
ri->ptr = ptr;
rlist_lock;
TAILQ_INSERT_TAIL(&resolver.resolve_list, ri, next);
rlist_unlock;
if (sem_post(resolver.sem)<0)
{
DLOG_PERROR("resolver_queue sem_post");
free(ri);
return NULL;
}
return ri;
}

22
tpws/resolver.h Normal file
View File

@@ -0,0 +1,22 @@
#pragma once
#include <inttypes.h>
#include <stdbool.h>
#include <sys/queue.h>
#include <sys/socket.h>
#include <netdb.h>
struct resolve_item
{
char dom[256]; // request dom
struct sockaddr_storage ss; // resolve result
int ga_res; // getaddrinfo result code
uint16_t port; // request port
void *ptr;
TAILQ_ENTRY(resolve_item) next;
};
struct resolve_item *resolver_queue(const char *dom, uint16_t port, void *ptr);
void resolver_deinit(void);
bool resolver_init(int threads, int fd_signal_pipe);
int resolver_thread_count(void);

360
tpws/sec.c Normal file
View File

@@ -0,0 +1,360 @@
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include "sec.h"
#include <unistd.h>
#include <fcntl.h>
#include <grp.h>
#ifdef __linux__
#include <sys/prctl.h>
#include <sys/syscall.h>
#include <linux/seccomp.h>
#include <linux/filter.h>
// __X32_SYSCALL_BIT defined in linux/unistd.h
#include <linux/unistd.h>
#include <syscall.h>
#include <errno.h>
/************ SECCOMP ************/
// block most of the undesired syscalls to harden against code execution
static long blocked_syscalls[] = {
#ifdef SYS_execv
SYS_execv,
#endif
SYS_execve,
#ifdef SYS_execveat
SYS_execveat,
#endif
#ifdef SYS_exec_with_loader
SYS_exec_with_loader,
#endif
#ifdef SYS_osf_execve
SYS_osf_execve,
#endif
#ifdef SYS_uselib
SYS_uselib,
#endif
#ifdef SYS_unlink
SYS_unlink,
#endif
SYS_unlinkat,
#ifdef SYS_chmod
SYS_chmod,
#endif
SYS_fchmod,SYS_fchmodat,
#ifdef SYS_chown
SYS_chown,
#endif
#ifdef SYS_chown32
SYS_chown32,
#endif
SYS_fchown,
#ifdef SYS_fchown32
SYS_fchown32,
#endif
#ifdef SYS_lchown
SYS_lchown,
#endif
#ifdef SYS_lchown32
SYS_lchown32,
#endif
SYS_fchownat,
#ifdef SYS_symlink
SYS_symlink,
#endif
SYS_symlinkat,
#ifdef SYS_link
SYS_link,
#endif
SYS_linkat,
SYS_truncate,
#ifdef SYS_truncate64
SYS_truncate64,
#endif
SYS_ftruncate,
#ifdef SYS_ftruncate64
SYS_ftruncate64,
#endif
#ifdef SYS_mknod
SYS_mknod,
#endif
SYS_mknodat,
#ifdef SYS_mkdir
SYS_mkdir,
#endif
SYS_mkdirat,
#ifdef SYS_rmdir
SYS_rmdir,
#endif
#ifdef SYS_rename
SYS_rename,
#endif
#ifdef SYS_renameat2
SYS_renameat2,
#endif
#ifdef SYS_renameat
SYS_renameat,
#endif
#ifdef SYS_readdir
SYS_readdir,
#endif
#ifdef SYS_getdents
SYS_getdents,
#endif
#ifdef SYS_getdents64
SYS_getdents64,
#endif
#ifdef SYS_process_vm_readv
SYS_process_vm_readv,
#endif
#ifdef SYS_process_vm_writev
SYS_process_vm_writev,
#endif
#ifdef SYS_process_madvise
SYS_process_madvise,
#endif
SYS_kill, SYS_ptrace
};
#define BLOCKED_SYSCALL_COUNT (sizeof(blocked_syscalls)/sizeof(*blocked_syscalls))
static void set_filter(struct sock_filter *filter, __u16 code, __u8 jt, __u8 jf, __u32 k)
{
filter->code = code;
filter->jt = jt;
filter->jf = jf;
filter->k = k;
}
// deny all blocked syscalls
static bool set_seccomp(void)
{
#ifdef __X32_SYSCALL_BIT
#define SECCOMP_PROG_SIZE (6 + BLOCKED_SYSCALL_COUNT)
#else
#define SECCOMP_PROG_SIZE (5 + BLOCKED_SYSCALL_COUNT)
#endif
struct sock_filter sockf[SECCOMP_PROG_SIZE];
struct sock_fprog prog = { .len = SECCOMP_PROG_SIZE, .filter = sockf };
int i,idx=0;
set_filter(&prog.filter[idx++], BPF_LD + BPF_W + BPF_ABS, 0, 0, arch_nr);
#ifdef __X32_SYSCALL_BIT
// x86 only
set_filter(&prog.filter[idx++], BPF_JMP + BPF_JEQ + BPF_K, 0, 3 + BLOCKED_SYSCALL_COUNT, ARCH_NR); // fail
set_filter(&prog.filter[idx++], BPF_LD + BPF_W + BPF_ABS, 0, 0, syscall_nr);
set_filter(&prog.filter[idx++], BPF_JMP + BPF_JGT + BPF_K, 1 + BLOCKED_SYSCALL_COUNT, 0, __X32_SYSCALL_BIT - 1); // fail
#else
set_filter(&prog.filter[idx++], BPF_JMP + BPF_JEQ + BPF_K, 0, 2 + BLOCKED_SYSCALL_COUNT, ARCH_NR); // fail
set_filter(&prog.filter[idx++], BPF_LD + BPF_W + BPF_ABS, 0, 0, syscall_nr);
#endif
/*
// ! THIS IS NOT WORKING BECAUSE perror() in glibc dups() stderr
set_filter(&prog.filter[idx++], BPF_JMP + BPF_JEQ + BPF_K, 0, 3, SYS_write); // special check for write call
set_filter(&prog.filter[idx++], BPF_LD + BPF_W + BPF_ABS, 0, 0, syscall_arg(0)); // fd
set_filter(&prog.filter[idx++], BPF_JMP + BPF_JGT + BPF_K, 2 + BLOCKED_SYSCALL_COUNT, 0, 2); // 1 - stdout, 2 - stderr. greater are bad
set_filter(&prog.filter[idx++], BPF_LD + BPF_W + BPF_ABS, 0, 0, syscall_nr); // reload syscall_nr
*/
for(i=0 ; i<BLOCKED_SYSCALL_COUNT ; i++)
{
set_filter(&prog.filter[idx++], BPF_JMP + BPF_JEQ + BPF_K, BLOCKED_SYSCALL_COUNT-i, 0, blocked_syscalls[i]);
}
set_filter(&prog.filter[idx++], BPF_RET + BPF_K, 0, 0, SECCOMP_RET_ALLOW); // success case
set_filter(&prog.filter[idx++], BPF_RET + BPF_K, 0, 0, SECCOMP_RET_KILL); // fail case
return prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog) >= 0;
}
bool sec_harden(void)
{
if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0))
{
DLOG_PERROR("PR_SET_NO_NEW_PRIVS(prctl)");
return false;
}
#if ARCH_NR!=0
if (!set_seccomp())
{
DLOG_PERROR("seccomp");
if (errno==EINVAL) DLOG_ERR("seccomp: this can be safely ignored if kernel does not support seccomp\n");
return false;
}
#endif
return true;
}
bool checkpcap(uint64_t caps)
{
if (!caps) return true; // no special caps reqd
struct __user_cap_header_struct ch = {_LINUX_CAPABILITY_VERSION_3, getpid()};
struct __user_cap_data_struct cd[2];
uint32_t c0 = (uint32_t)caps;
uint32_t c1 = (uint32_t)(caps>>32);
return !capget(&ch,cd) && (cd[0].effective & c0)==c0 && (cd[1].effective & c1)==c1;
}
bool setpcap(uint64_t caps)
{
struct __user_cap_header_struct ch = {_LINUX_CAPABILITY_VERSION_3, getpid()};
struct __user_cap_data_struct cd[2];
cd[0].effective = cd[0].permitted = (uint32_t)caps;
cd[0].inheritable = 0;
cd[1].effective = cd[1].permitted = (uint32_t)(caps>>32);
cd[1].inheritable = 0;
return !capset(&ch,cd);
}
int getmaxcap(void)
{
int maxcap = CAP_LAST_CAP;
FILE *F = fopen("/proc/sys/kernel/cap_last_cap", "r");
if (F)
{
int n = fscanf(F, "%d", &maxcap);
fclose(F);
}
return maxcap;
}
bool dropcaps(void)
{
uint64_t caps = 0;
int maxcap = getmaxcap();
if (setpcap(caps|(1<<CAP_SETPCAP)))
{
for (int cap = 0; cap <= maxcap; cap++)
{
if (prctl(PR_CAPBSET_DROP, cap)<0)
{
DLOG_ERR("could not drop bound cap %d\n", cap);
DLOG_PERROR("cap_drop_bound");
}
}
}
// now without CAP_SETPCAP
if (!setpcap(caps))
{
DLOG_PERROR("setpcap");
return checkpcap(caps);
}
return true;
}
#else // __linux__
bool sec_harden(void)
{
// noop
return true;
}
#endif // __linux__
bool can_drop_root(void)
{
#ifdef __linux__
// has some caps
return checkpcap((1<<CAP_SETUID)|(1<<CAP_SETGID)|(1<<CAP_SETPCAP));
#else
// effective root
return !geteuid();
#endif
}
bool droproot(uid_t uid, gid_t gid)
{
#ifdef __linux__
if (prctl(PR_SET_KEEPCAPS, 1L))
{
DLOG_PERROR("prctl(PR_SET_KEEPCAPS)");
return false;
}
#endif
// drop all SGIDs
if (setgroups(0,NULL))
{
DLOG_PERROR("setgroups");
return false;
}
if (setgid(gid))
{
DLOG_PERROR("setgid");
return false;
}
if (setuid(uid))
{
DLOG_PERROR("setuid");
return false;
}
#ifdef __linux__
return dropcaps();
#else
return true;
#endif
}
void print_id(void)
{
int i,N;
gid_t g[128];
DLOG_CONDUP("Running as UID=%u GID=",getuid());
N=getgroups(sizeof(g)/sizeof(*g),g);
if (N>0)
{
for(i=0;i<N;i++)
DLOG_CONDUP(i==(N-1) ? "%u" : "%u,", g[i]);
DLOG_CONDUP("\n");
}
else
DLOG_CONDUP("%u\n",getgid());
}
void daemonize(void)
{
int pid;
pid = fork();
if (pid == -1)
{
DLOG_PERROR("fork");
exit(2);
}
else if (pid != 0)
exit(0);
if (setsid() == -1)
exit(2);
if (chdir("/") == -1)
exit(2);
close(STDIN_FILENO);
close(STDOUT_FILENO);
close(STDERR_FILENO);
/* redirect fd's 0,1,2 to /dev/null */
open("/dev/null", O_RDWR);
int fd;
/* stdin */
fd = dup(0);
/* stdout */
fd = dup(0);
/* stderror */
}
bool writepid(const char *filename)
{
FILE *F;
if (!(F = fopen(filename, "w")))
return false;
fprintf(F, "%d", getpid());
fclose(F);
return true;
}

90
tpws/sec.h Normal file
View File

@@ -0,0 +1,90 @@
#pragma once
#include "params.h"
#include <sys/types.h>
#include <stdbool.h>
#ifdef __linux__
#include <stddef.h>
#include <sys/capability.h>
#include <linux/audit.h>
bool checkpcap(uint64_t caps);
bool setpcap(uint64_t caps);
int getmaxcap(void);
bool dropcaps(void);
#define syscall_nr (offsetof(struct seccomp_data, nr))
#define arch_nr (offsetof(struct seccomp_data, arch))
#define syscall_arg(x) (offsetof(struct seccomp_data, args[x]))
#if defined(__aarch64__)
# define ARCH_NR AUDIT_ARCH_AARCH64
#elif defined(__amd64__)
# define ARCH_NR AUDIT_ARCH_X86_64
#elif defined(__arm__) && (defined(__ARM_EABI__) || defined(__thumb__))
# if __BYTE_ORDER == __LITTLE_ENDIAN
# define ARCH_NR AUDIT_ARCH_ARM
# else
# define ARCH_NR AUDIT_ARCH_ARMEB
# endif
#elif defined(__i386__)
# define ARCH_NR AUDIT_ARCH_I386
#elif defined(__mips__)
#if _MIPS_SIM == _MIPS_SIM_ABI32
# if __BYTE_ORDER == __LITTLE_ENDIAN
# define ARCH_NR AUDIT_ARCH_MIPSEL
# else
# define ARCH_NR AUDIT_ARCH_MIPS
# endif
#elif _MIPS_SIM == _MIPS_SIM_ABI64
# if __BYTE_ORDER == __LITTLE_ENDIAN
# define ARCH_NR AUDIT_ARCH_MIPSEL64
# else
# define ARCH_NR AUDIT_ARCH_MIPS64
# endif
#else
# error "Unsupported mips abi"
#endif
#elif defined(__PPC64__)
# if __BYTE_ORDER == __LITTLE_ENDIAN
# define ARCH_NR AUDIT_ARCH_PPC64LE
# else
# define ARCH_NR AUDIT_ARCH_PPC64
# endif
#elif defined(__PPC__)
# define ARCH_NR AUDIT_ARCH_PPC
#elif __riscv && __riscv_xlen == 64
# define ARCH_NR AUDIT_ARCH_RISCV64
#else
# error "Platform does not support seccomp filter yet"
#endif
#endif
bool sec_harden(void);
bool can_drop_root();
bool droproot(uid_t uid, gid_t gid);
void print_id(void);
void daemonize(void);
bool writepid(const char *filename);

93
tpws/socks.h Normal file
View File

@@ -0,0 +1,93 @@
#pragma once
#include <stdint.h>
#include <arpa/inet.h>
#pragma pack(push,1)
#define S4_CMD_CONNECT 1
#define S4_CMD_BIND 2
typedef struct
{
uint8_t ver,cmd;
uint16_t port;
uint32_t ip;
} s4_req;
#define S4_REQ_HEADER_VALID(r,l) (l>=sizeof(s4_req) && r->ver==4)
#define S4_REQ_CONNECT_VALID(r,l) (S4_REQ_HEADER_VALID(r,l) && r->cmd==S4_CMD_CONNECT)
#define S4_REP_OK 90
#define S4_REP_FAILED 91
typedef struct
{
uint8_t zero,rep;
uint16_t port;
uint32_t ip;
} s4_rep;
#define S5_AUTH_NONE 0
#define S5_AUTH_GSSAPI 1
#define S5_AUTH_USERPASS 2
#define S5_AUTH_UNACCEPTABLE 0xFF
typedef struct
{
uint8_t ver,nmethods,methods[255];
} s5_handshake;
#define S5_REQ_HANDHSHAKE_VALID(r,l) (l>=3 && r->ver==5 && r->nmethods && l>=(2+r->nmethods))
typedef struct
{
uint8_t ver,method;
} s5_handshake_ack;
#define S5_CMD_CONNECT 1
#define S5_CMD_BIND 2
#define S5_CMD_UDP_ASSOC 3
#define S5_ATYP_IP4 1
#define S5_ATYP_DOM 3
#define S5_ATYP_IP6 4
typedef struct
{
uint8_t ver,cmd,rsv,atyp;
union {
struct {
struct in_addr addr;
uint16_t port;
} d4;
struct {
struct in6_addr addr;
uint16_t port;
} d6;
struct {
uint8_t len;
char domport[255+2]; // max hostname + binary port
} dd;
};
} s5_req;
#define S5_REQ_HEADER_VALID(r,l) (l>=4 && r->ver==5)
#define S5_IP46_VALID(r,l) ((r->atyp==S5_ATYP_IP4 && l>=(4+sizeof(r->d4))) || (r->atyp==S5_ATYP_IP6 && l>=(4+sizeof(r->d6))))
#define S5_REQ_CONNECT_VALID(r,l) (S5_REQ_HEADER_VALID(r,l) && r->cmd==S5_CMD_CONNECT && (S5_IP46_VALID(r,l) || (r->atyp==S5_ATYP_DOM && l>=5 && l>=(5+r->dd.len))))
#define S5_PORT_FROM_DD(r,l) (l>=(4+r->dd.len+2) ? ntohs(*(uint16_t*)(r->dd.domport+r->dd.len)) : 0)
#define S5_REP_OK 0
#define S5_REP_GENERAL_FAILURE 1
#define S5_REP_NOT_ALLOWED_BY_RULESET 2
#define S5_REP_NETWORK_UNREACHABLE 3
#define S5_REP_HOST_UNREACHABLE 4
#define S5_REP_CONN_REFUSED 5
#define S5_REP_TTL_EXPIRED 6
#define S5_REP_COMMAND_NOT_SUPPORTED 7
#define S5_REP_ADDR_TYPE_NOT_SUPPORTED 8
typedef struct
{
uint8_t ver,rep,rsv,atyp;
union {
struct {
struct in_addr addr;
uint16_t port;
} d4;
};
} s5_rep;
#pragma pack(pop)

461
tpws/tamper.c Normal file
View File

@@ -0,0 +1,461 @@
#define _GNU_SOURCE
#include "tamper.h"
#include "hostlist.h"
#include "protocol.h"
#include "helpers.h"
#include <string.h>
#include <stdio.h>
static bool dp_match_l3l4(struct desync_profile *dp, bool ipv6, uint16_t tcp_port)
{
return \
((!ipv6 && dp->filter_ipv4) || (ipv6 && dp->filter_ipv6)) &&
(!tcp_port || pf_in_range(tcp_port,&dp->pf_tcp));
}
static bool dp_match(struct desync_profile *dp, bool ipv6, uint16_t tcp_port, const char *hostname)
{
if (dp_match_l3l4(dp,ipv6,tcp_port))
{
// autohostlist profile matching l3/l4 filter always win
if (*dp->hostlist_auto_filename) return true;
if (dp->hostlist || dp->hostlist_exclude)
{
// without known hostname first profile matching l3/l4 filter and without hostlist filter wins
if (hostname)
return HostlistCheck(dp, hostname, NULL);
}
else
// profile without hostlist filter wins
return true;
}
return false;
}
static struct desync_profile *dp_find(struct desync_profile_list_head *head, bool ipv6, uint16_t tcp_port, const char *hostname)
{
struct desync_profile_list *dpl;
VPRINT("desync profile search for hostname='%s' ipv6=%u tcp_port=%u\n", hostname ? hostname : "", ipv6, tcp_port);
LIST_FOREACH(dpl, head, next)
{
if (dp_match(&dpl->dp,ipv6,tcp_port,hostname))
{
VPRINT("desync profile %d matches\n",dpl->dp.n);
return &dpl->dp;
}
}
VPRINT("desync profile not found\n");
return NULL;
}
void apply_desync_profile(t_ctrack *ctrack, const struct sockaddr *dest)
{
ctrack->dp = dp_find(&params.desync_profiles, dest->sa_family==AF_INET6, saport(dest), ctrack->hostname);
}
// segment buffer has at least 5 extra bytes to extend data block
void tamper_out(t_ctrack *ctrack, const struct sockaddr *dest, uint8_t *segment,size_t segment_buffer_size,size_t *size, size_t *split_pos, uint8_t *split_flags)
{
uint8_t *p, *pp, *pHost = NULL;
size_t method_len = 0, pos;
size_t tpos, spos;
const char *method;
bool bHaveHost = false;
char *pc, Host[256];
t_l7proto l7proto;
DBGPRINT("tamper_out\n");
if (params.debug)
{
char ip_port[48];
ntop46_port(dest,ip_port,sizeof(ip_port));
VPRINT("tampering tcp segment with size %zu to %s\n", *size, ip_port);
if (ctrack->dp) VPRINT("using cached desync profile %d\n",ctrack->dp->n);
if (ctrack->hostname) VPRINT("connection hostname: %s\n", ctrack->hostname);
}
if (dest->sa_family!=AF_INET && dest->sa_family!=AF_INET6)
{
DLOG_ERR("tamper_out dest family unknown\n");
return;
}
*split_pos=0;
*split_flags=0;
if ((method = HttpMethod(segment,*size)))
{
method_len = strlen(method)-2;
VPRINT("Data block looks like http request start : %s\n", method);
l7proto=HTTP;
if (HttpFindHost(&pHost,segment,*size))
{
p = pHost + 5;
while (p < (segment + *size) && (*p == ' ' || *p == '\t')) p++;
pp = p;
while (pp < (segment + *size) && (pp - p) < (sizeof(Host) - 1) && *pp != '\r' && *pp != '\n') pp++;
memcpy(Host, p, pp - p);
Host[pp - p] = '\0';
bHaveHost = true;
for(pc = Host; *pc; pc++) *pc=tolower(*pc);
}
}
else if (IsTLSClientHello(segment,*size,false))
{
VPRINT("Data block contains TLS ClientHello\n");
l7proto=TLS;
bHaveHost=TLSHelloExtractHost((uint8_t*)segment,*size,Host,sizeof(Host),false);
}
else
{
VPRINT("Data block contains unknown payload\n");
l7proto = UNKNOWN;
}
if (ctrack->l7proto==UNKNOWN) ctrack->l7proto=l7proto;
if (bHaveHost)
{
VPRINT("request hostname: %s\n", Host);
if (!ctrack->hostname)
{
if (!(ctrack->hostname=strdup(Host)))
{
DLOG_ERR("strdup hostname : out of memory\n");
return;
}
struct desync_profile *dp_prev = ctrack->dp;
apply_desync_profile(ctrack, dest);
if (ctrack->dp!=dp_prev)
VPRINT("desync profile changed by revealed hostname !\n");
else if (*ctrack->dp->hostlist_auto_filename)
{
bool bHostExcluded;
if (!HostlistCheck(ctrack->dp, Host, &bHostExcluded))
{
ctrack->b_ah_check = !bHostExcluded;
VPRINT("Not acting on this request\n");
return;
}
}
}
}
if (!ctrack->dp) return;
switch(l7proto)
{
case HTTP:
if (ctrack->dp->unixeol)
{
p = pp = segment;
while ((p = memmem(p, segment + *size - p, "\r\n", 2)))
{
*p = '\n'; p++;
memmove(p, p + 1, segment + *size - p - 1);
(*size)--;
if (pp == (p - 1))
{
// probably end of http headers
VPRINT("Found double EOL at pos %td. Stop replacing.\n", pp - segment);
break;
}
pp = p;
}
pHost = NULL; // invalidate
}
if (ctrack->dp->methodeol && (*size+1+!ctrack->dp->unixeol)<=segment_buffer_size)
{
VPRINT("Adding EOL before method\n");
if (ctrack->dp->unixeol)
{
memmove(segment + 1, segment, *size);
(*size)++;;
segment[0] = '\n';
}
else
{
memmove(segment + 2, segment, *size);
*size += 2;
segment[0] = '\r';
segment[1] = '\n';
}
pHost = NULL; // invalidate
}
if (ctrack->dp->methodspace && *size<segment_buffer_size)
{
// we only work with data blocks looking as HTTP query, so method is at the beginning
VPRINT("Adding extra space after method\n");
p = segment + method_len + 1;
pos = method_len + 1;
memmove(p + 1, p, *size - pos);
*p = ' '; // insert extra space
(*size)++; // block will grow by 1 byte
if (pHost) pHost++; // Host: position will move by 1 byte
}
if ((ctrack->dp->hostdot || ctrack->dp->hosttab) && *size<segment_buffer_size && HttpFindHost(&pHost,segment,*size))
{
p = pHost + 5;
while (p < (segment + *size) && *p != '\r' && *p != '\n') p++;
if (p < (segment + *size))
{
pos = p - segment;
VPRINT("Adding %s to host name at pos %zu\n", ctrack->dp->hostdot ? "dot" : "tab", pos);
memmove(p + 1, p, *size - pos);
*p = ctrack->dp->hostdot ? '.' : '\t'; // insert dot or tab
(*size)++; // block will grow by 1 byte
}
}
if (ctrack->dp->domcase && HttpFindHost(&pHost,segment,*size))
{
p = pHost + 5;
pos = p - segment;
VPRINT("Mixing domain case at pos %zu\n",pos);
for (; p < (segment + *size) && *p != '\r' && *p != '\n'; p++)
*p = (((size_t)p) & 1) ? tolower(*p) : toupper(*p);
}
if (ctrack->dp->hostnospace && HttpFindHost(&pHost,segment,*size) && (pHost+5)<(segment+*size) && pHost[5] == ' ')
{
p = pHost + 6;
pos = p - segment;
VPRINT("Removing space before host name at pos %zu\n", pos);
memmove(p - 1, p, *size - pos);
(*size)--; // block will shrink by 1 byte
}
if (ctrack->dp->hostcase && HttpFindHost(&pHost,segment,*size))
{
VPRINT("Changing 'Host:' => '%c%c%c%c:' at pos %td\n", ctrack->dp->hostspell[0], ctrack->dp->hostspell[1], ctrack->dp->hostspell[2], ctrack->dp->hostspell[3], pHost - segment);
memcpy(pHost, ctrack->dp->hostspell, 4);
}
if (ctrack->dp->hostpad && HttpFindHost(&pHost,segment,*size))
{
// add : XXXXX: <padding?[\r\n|\n]
char s[8];
size_t hsize = ctrack->dp->unixeol ? 8 : 9;
size_t hostpad = ctrack->dp->hostpad<hsize ? hsize : ctrack->dp->hostpad;
if ((hsize+*size)>segment_buffer_size)
VPRINT("could not add host padding : buffer too small\n");
else
{
if ((hostpad+*size)>segment_buffer_size)
{
hostpad=segment_buffer_size-*size;
VPRINT("host padding reduced to %zu bytes : buffer too small\n", hostpad);
}
else
VPRINT("host padding with %zu bytes\n", hostpad);
p = pHost;
pos = p - segment;
memmove(p + hostpad, p, *size - pos);
(*size) += hostpad;
while(hostpad)
{
#define MAX_HDR_SIZE 2048
size_t padsize = hostpad > hsize ? hostpad-hsize : 0;
if (padsize>MAX_HDR_SIZE) padsize=MAX_HDR_SIZE;
// if next header would be too small then add extra padding to the current one
if ((hostpad-padsize-hsize)<hsize) padsize+=hostpad-padsize-hsize;
snprintf(s,sizeof(s),"%c%04x: ", 'a'+rand()%('z'-'a'+1), rand() & 0xFFFF);
memcpy(p,s,7);
p+=7;
memset(p,'a'+rand()%('z'-'a'+1),padsize);
p+=padsize;
if (ctrack->dp->unixeol)
*p++='\n';
else
{
*p++='\r';
*p++='\n';
}
hostpad-=hsize+padsize;
}
pHost = NULL; // invalidate
}
}
*split_pos = HttpPos(ctrack->dp->split_http_req, ctrack->dp->split_pos, segment, *size);
if (ctrack->dp->disorder_http) *split_flags |= SPLIT_FLAG_DISORDER;
if (ctrack->dp->oob_http) *split_flags |= SPLIT_FLAG_OOB;
break;
case TLS:
spos = TLSPos(ctrack->dp->split_tls, ctrack->dp->split_pos, segment, *size, 0);
if ((5+*size)<=segment_buffer_size)
{
tpos = TLSPos(ctrack->dp->tlsrec, ctrack->dp->tlsrec_pos+5, segment, *size, 0);
if (tpos>5)
{
// construct 2 TLS records from one
uint16_t l = pntoh16(segment+3); // length
if (l>=2)
{
// length is checked in IsTLSClientHello and cannot exceed buffer size
if ((tpos-5)>=l) tpos=5+1;
VPRINT("making 2 TLS records at pos %zu\n",tpos);
memmove(segment+tpos+5,segment+tpos,*size-tpos);
segment[tpos] = segment[0];
segment[tpos+1] = segment[1];
segment[tpos+2] = segment[2];
phton16(segment+tpos+3,l-(tpos-5));
phton16(segment+3,tpos-5);
*size += 5;
// split pos present and it is not before tlsrec split. increase split pos by tlsrec header size (5 bytes)
if (spos && spos>=tpos) spos+=5;
}
}
}
if (spos && spos < *size)
*split_pos = spos;
if (ctrack->dp->disorder_tls) *split_flags |= SPLIT_FLAG_DISORDER;
if (ctrack->dp->oob_tls) *split_flags |= SPLIT_FLAG_OOB;
break;
default:
if (ctrack->dp->split_any_protocol && ctrack->dp->split_pos < *size)
*split_pos = ctrack->dp->split_pos;
}
if (ctrack->dp->disorder) *split_flags |= SPLIT_FLAG_DISORDER;
if (ctrack->dp->oob) *split_flags |= SPLIT_FLAG_OOB;
}
static void auto_hostlist_reset_fail_counter(struct desync_profile *dp, const char *hostname)
{
if (hostname)
{
hostfail_pool *fail_counter;
fail_counter = HostFailPoolFind(dp->hostlist_auto_fail_counters, hostname);
if (fail_counter)
{
HostFailPoolDel(&dp->hostlist_auto_fail_counters, fail_counter);
VPRINT("auto hostlist (profile %d) : %s : fail counter reset. website is working.\n", dp->n, hostname);
HOSTLIST_DEBUGLOG_APPEND("%s : profile %d : fail counter reset. website is working.", hostname, dp->n);
}
}
}
static void auto_hostlist_failed(struct desync_profile *dp, const char *hostname)
{
hostfail_pool *fail_counter;
fail_counter = HostFailPoolFind(dp->hostlist_auto_fail_counters, hostname);
if (!fail_counter)
{
fail_counter = HostFailPoolAdd(&dp->hostlist_auto_fail_counters, hostname, dp->hostlist_auto_fail_time);
if (!fail_counter)
{
DLOG_ERR("HostFailPoolAdd: out of memory\n");
return;
}
}
fail_counter->counter++;
VPRINT("auto hostlist (profile %d) : %s : fail counter %d/%d\n", dp->n , hostname, fail_counter->counter, dp->hostlist_auto_fail_threshold);
HOSTLIST_DEBUGLOG_APPEND("%s : profile %d : fail counter %d/%d", hostname, dp->n, fail_counter->counter, dp->hostlist_auto_fail_threshold);
if (fail_counter->counter >= dp->hostlist_auto_fail_threshold)
{
VPRINT("auto hostlist (profile %d) : fail threshold reached. adding %s to auto hostlist\n", dp->n , hostname);
HostFailPoolDel(&dp->hostlist_auto_fail_counters, fail_counter);
VPRINT("auto hostlist (profile %d) : rechecking %s to avoid duplicates\n", dp->n, hostname);
bool bExcluded=false;
if (!HostlistCheck(dp, hostname, &bExcluded) && !bExcluded)
{
VPRINT("auto hostlist (profile %d) : adding %s to %s\n", dp->n, hostname, dp->hostlist_auto_filename);
HOSTLIST_DEBUGLOG_APPEND("%s : profile %d : adding to %s", hostname, dp->n, dp->hostlist_auto_filename);
if (!StrPoolAddStr(&dp->hostlist, hostname))
{
DLOG_ERR("StrPoolAddStr out of memory\n");
return;
}
if (!append_to_list_file(dp->hostlist_auto_filename, hostname))
{
DLOG_PERROR("write to auto hostlist:");
return;
}
dp->hostlist_auto_mod_time = file_mod_time(dp->hostlist_auto_filename);
}
else
{
VPRINT("auto hostlist (profile %d) : NOT adding %s\n", dp->n, hostname);
HOSTLIST_DEBUGLOG_APPEND("%s : profile %d : NOT adding, duplicate detected", hostname, dp->n);
}
}
}
void tamper_in(t_ctrack *ctrack, uint8_t *segment,size_t segment_buffer_size,size_t *size)
{
bool bFail=false;
DBGPRINT("tamper_in hostname=%s\n", ctrack->hostname);
if (ctrack->dp && ctrack->b_ah_check)
{
HostFailPoolPurgeRateLimited(&ctrack->dp->hostlist_auto_fail_counters);
if (ctrack->l7proto==HTTP && ctrack->hostname)
{
if (IsHttpReply(segment,*size))
{
VPRINT("incoming HTTP reply detected for hostname %s\n", ctrack->hostname);
bFail = HttpReplyLooksLikeDPIRedirect(segment, *size, ctrack->hostname);
if (bFail)
{
VPRINT("redirect to another domain detected. possibly DPI redirect.\n");
HOSTLIST_DEBUGLOG_APPEND("%s : profile %d : redirect to another domain", ctrack->hostname, ctrack->dp->n);
}
else
VPRINT("local or in-domain redirect detected. it's not a DPI redirect.\n");
}
else
{
// received not http reply. do not monitor this connection anymore
VPRINT("incoming unknown HTTP data detected for hostname %s\n", ctrack->hostname);
}
if (bFail) auto_hostlist_failed(ctrack->dp, ctrack->hostname);
}
if (!bFail) auto_hostlist_reset_fail_counter(ctrack->dp, ctrack->hostname);
}
ctrack->bTamperInCutoff = true;
}
void rst_in(t_ctrack *ctrack)
{
DBGPRINT("rst_in hostname=%s\n", ctrack->hostname);
if (ctrack->dp && ctrack->b_ah_check)
{
HostFailPoolPurgeRateLimited(&ctrack->dp->hostlist_auto_fail_counters);
if (!ctrack->bTamperInCutoff && ctrack->hostname)
{
VPRINT("incoming RST detected for hostname %s\n", ctrack->hostname);
HOSTLIST_DEBUGLOG_APPEND("%s : profile %d : incoming RST", ctrack->hostname, ctrack->dp->n);
auto_hostlist_failed(ctrack->dp, ctrack->hostname);
}
}
}
void hup_out(t_ctrack *ctrack)
{
DBGPRINT("hup_out hostname=%s\n", ctrack->hostname);
if (ctrack->dp && ctrack->b_ah_check)
{
HostFailPoolPurgeRateLimited(&ctrack->dp->hostlist_auto_fail_counters);
if (!ctrack->bTamperInCutoff && ctrack->hostname)
{
// local leg dropped connection after first request. probably due to timeout.
VPRINT("local leg closed connection after first request (timeout ?). hostname: %s\n", ctrack->hostname);
HOSTLIST_DEBUGLOG_APPEND("%s : profile %d : client closed connection without server reply", ctrack->hostname, ctrack->dp->n);
auto_hostlist_failed(ctrack->dp, ctrack->hostname);
}
}
}

31
tpws/tamper.h Normal file
View File

@@ -0,0 +1,31 @@
#pragma once
#include <stdbool.h>
#include <stdint.h>
#include <sys/types.h>
#include "params.h"
#define SPLIT_FLAG_DISORDER 0x01
#define SPLIT_FLAG_OOB 0x02
typedef enum {UNKNOWN=0, HTTP, TLS} t_l7proto;
typedef struct
{
// common state
t_l7proto l7proto;
bool bFirstReplyChecked;
bool bTamperInCutoff;
bool b_ah_check;
char *hostname;
struct desync_profile *dp; // desync profile cache
} t_ctrack;
void apply_desync_profile(t_ctrack *ctrack, const struct sockaddr *dest);
void tamper_out(t_ctrack *ctrack, const struct sockaddr *dest, uint8_t *segment,size_t segment_buffer_size,size_t *size, size_t *split_pos, uint8_t *split_flags);
void tamper_in(t_ctrack *ctrack, uint8_t *segment,size_t segment_buffer_size,size_t *size);
// connection reset by remote leg
void rst_in(t_ctrack *ctrack);
// local leg closed connection (timeout waiting response ?)
void hup_out(t_ctrack *ctrack);

1450
tpws/tpws.c Normal file

File diff suppressed because it is too large Load Diff

9
tpws/tpws.h Normal file
View File

@@ -0,0 +1,9 @@
#pragma once
#ifdef __linux__
#define SPLICE_PRESENT
#endif
#include <sys/param.h>
void dohup(void);

1690
tpws/tpws_conn.c Normal file

File diff suppressed because it is too large Load Diff

108
tpws/tpws_conn.h Normal file
View File

@@ -0,0 +1,108 @@
#pragma once
#include <stdbool.h>
#include <inttypes.h>
#include <sys/queue.h>
#include <time.h>
#include "tamper.h"
#include "params.h"
#include "resolver.h"
#define BACKLOG 10
#define MAX_EPOLL_EVENTS 64
#define IP_TRANSPARENT 19 //So that application compiles on OpenWRT
#define SPLICE_LEN 65536
#define DEFAULT_MAX_CONN 512
#define DEFAULT_MAX_ORPHAN_TIME 5
#define DEFAULT_TCP_USER_TIMEOUT_LOCAL 10
#define DEFAULT_TCP_USER_TIMEOUT_REMOTE 20
int event_loop(const int *listen_fd, size_t listen_fd_ct);
//Three different states of a connection
enum{
CONN_UNAVAILABLE=0, // connecting
CONN_AVAILABLE, // operational
CONN_RDHUP, // received RDHUP, only sending unsent buffers. more RDHUPs are blocked
CONN_CLOSED // will be deleted soon
};
typedef uint8_t conn_state_t;
// data in a send_buffer can be sent in several stages
// pos indicates size of already sent data
// when pos==len its time to free buffer
struct send_buffer
{
uint8_t *data;
size_t len,pos;
int ttl, flags;
};
typedef struct send_buffer send_buffer_t;
enum{
CONN_TYPE_TRANSPARENT=0,
CONN_TYPE_SOCKS
};
typedef uint8_t conn_type_t;
struct tproxy_conn
{
bool listener; // true - listening socket. false = connecion socket
bool remote; // false - accepted, true - connected
int efd; // epoll fd
int fd;
int splice_pipe[2];
conn_state_t state;
conn_type_t conn_type;
struct sockaddr_storage dest;
struct tproxy_conn *partner; // other leg
time_t orphan_since;
// socks5 state machine
enum {
S_WAIT_HANDSHAKE=0,
S_WAIT_REQUEST,
S_WAIT_RESOLVE,
S_WAIT_CONNECTION,
S_TCP
} socks_state;
uint8_t socks_ver;
struct resolve_item *socks_ri;
// these value are used in flow control. we do not use ET (edge triggered) polling
// if we dont disable notifications they will come endlessly until condition becomes false and will eat all cpu time
bool bFlowIn,bFlowOut, bShutdown, bFlowInPrev,bFlowOutPrev, bPrevRdhup;
// total read,write
uint64_t trd,twr, tnrd;
// number of epoll_wait events
unsigned int event_count;
// connection is either spliced or send/recv
// spliced connection have pipe buffering but also can have send_buffer's
// pipe buffer comes first, then send_buffer's from 0 to countof(wr_buf)-1
// send/recv connection do not have pipe and wr_unsent is meaningless, always 0
ssize_t wr_unsent; // unsent bytes in the pipe
// buffer 0 : send before split_pos
// buffer 1 : send after split_pos
// buffer 2 : after RDHUP read all and buffer to the partner
// buffer 3 : after HUP read all and buffer to the partner
// (2 and 3 should not be filled simultaneously, but who knows what can happen. if we have to refill non-empty buffer its FATAL)
// all buffers are sent strictly from 0 to countof(wr_buf)-1
// buffer cannot be sent if there is unsent data in a lower buffer
struct send_buffer wr_buf[4];
t_ctrack track;
//Create the struct which contains ptrs to next/prev element
TAILQ_ENTRY(tproxy_conn) conn_ptrs;
};
typedef struct tproxy_conn tproxy_conn_t;
//Define the struct tailhead (code in sys/queue.h is quite intuitive)
//Use tail queue for efficient delete
TAILQ_HEAD(tailhead, tproxy_conn);
bool set_socket_buffers(int fd, int rcvbuf, int sndbuf);

1136
tpws/uthash.h Normal file

File diff suppressed because it is too large Load Diff