mirror of
https://github.com/bol-van/zapret.git
synced 2025-05-24 22:32:58 +03:00
history purge
This commit is contained in:
12
tpws/BSDmakefile
Normal file
12
tpws/BSDmakefile
Normal file
@@ -0,0 +1,12 @@
|
||||
CC ?= cc
|
||||
CFLAGS += -std=gnu99 -s -O3 -Wno-logical-op-parentheses
|
||||
LIBS = -lz
|
||||
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
|
21
tpws/Makefile
Normal file
21
tpws/Makefile
Normal file
@@ -0,0 +1,21 @@
|
||||
CC ?= gcc
|
||||
CFLAGS += -std=gnu99 -O3
|
||||
CFLAGS_BSD = -Wno-address-of-packed-member -Wno-logical-op-parentheses -Wno-switch
|
||||
CFLAGS_MAC = -mmacosx-version-min=10.8
|
||||
STRIP = -s
|
||||
LIBS = -lz
|
||||
SRC_FILES = *.c
|
||||
|
||||
all: tpws
|
||||
|
||||
tpws: $(SRC_FILES)
|
||||
$(CC) $(STRIP) $(CFLAGS) -o $@ $(SRC_FILES) $(LDFLAGS) $(LIBS)
|
||||
|
||||
bsd: $(SRC_FILES)
|
||||
$(CC) $(STRIP) $(CFLAGS) $(CFLAGS_BSD) -Iepoll-shim/include -o tpws $(SRC_FILES) epoll-shim/src/*.c $(LDFLAGS) $(LIBS)
|
||||
|
||||
mac: $(SRC_FILES)
|
||||
$(CC) $(CFLAGS) $(CFLAGS_BSD) $(CFLAGS_MAC) -Iepoll-shim/include -Imacos -o tpws $(SRC_FILES) epoll-shim/src/*.c $(LDFLAGS) $(LIBS)
|
||||
|
||||
clean:
|
||||
rm -f tpws *.o
|
80
tpws/epoll-shim/include/sys/epoll.h
Normal file
80
tpws/epoll-shim/include/sys/epoll.h
Normal 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
305
tpws/epoll-shim/src/epoll.c
Normal 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, /**/
|
||||
¤t_time) < 0) {
|
||||
return errno;
|
||||
}
|
||||
|
||||
timespecsub(deadline, ¤t_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);
|
||||
}
|
281
tpws/epoll-shim/src/epoll_shim_ctx.c
Normal file
281
tpws/epoll-shim/src/epoll_shim_ctx.c
Normal 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;
|
||||
}
|
76
tpws/epoll-shim/src/epoll_shim_ctx.h
Normal file
76
tpws/epoll-shim/src/epoll_shim_ctx.h
Normal 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
|
1386
tpws/epoll-shim/src/epollfd_ctx.c
Normal file
1386
tpws/epoll-shim/src/epollfd_ctx.c
Normal file
File diff suppressed because it is too large
Load Diff
108
tpws/epoll-shim/src/epollfd_ctx.h
Normal file
108
tpws/epoll-shim/src/epollfd_ctx.h
Normal 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
|
31
tpws/epoll-shim/src/eventfd_ctx.h
Normal file
31
tpws/epoll-shim/src/eventfd_ctx.h
Normal 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
19
tpws/epoll-shim/src/fix.c
Normal 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
20
tpws/epoll-shim/src/fix.h
Normal 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
|
19
tpws/epoll-shim/src/signalfd_ctx.h
Normal file
19
tpws/epoll-shim/src/signalfd_ctx.h
Normal 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
|
38
tpws/epoll-shim/src/timerfd_ctx.h
Normal file
38
tpws/epoll-shim/src/timerfd_ctx.h
Normal 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
82
tpws/gzip.c
Normal 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
8
tpws/gzip.h
Normal 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);
|
146
tpws/helpers.c
Normal file
146
tpws/helpers.c
Normal file
@@ -0,0 +1,146 @@
|
||||
#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>
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
void print_sockaddr(const struct sockaddr *sa)
|
||||
{
|
||||
char str[64];
|
||||
switch (sa->sa_family)
|
||||
{
|
||||
case AF_INET:
|
||||
if (inet_ntop(sa->sa_family, &((struct sockaddr_in*)sa)->sin_addr, str, sizeof(str)))
|
||||
printf("%s:%d", str, ntohs(((struct sockaddr_in*)sa)->sin_port));
|
||||
break;
|
||||
case AF_INET6:
|
||||
if (inet_ntop(sa->sa_family, &((struct sockaddr_in6*)sa)->sin6_addr, str, sizeof(str)))
|
||||
printf("%s:%d", str, ntohs(((struct sockaddr_in6*)sa)->sin6_port));
|
||||
break;
|
||||
default:
|
||||
printf("UNKNOWN_FAMILY_%d", sa->sa_family);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// -1 = error, 0 = not local, 1 = local
|
||||
bool check_local_ip(const struct sockaddr *saddr)
|
||||
{
|
||||
struct ifaddrs *addrs,*a;
|
||||
|
||||
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 = *(uint32_t*)(((struct sockaddr_in6*)a)->sin6_addr.s6_addr+12);
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
int set_keepalive(int fd)
|
||||
{
|
||||
int yes=1;
|
||||
return setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &yes, sizeof(int))!=-1;
|
||||
}
|
||||
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;
|
||||
}
|
23
tpws/helpers.h
Normal file
23
tpws/helpers.h
Normal file
@@ -0,0 +1,23 @@
|
||||
#pragma once
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdbool.h>
|
||||
#include <netinet/in.h>
|
||||
#include <sys/socket.h>
|
||||
#include <netdb.h>
|
||||
|
||||
char *strncasestr(const char *s,const char *find, size_t slen);
|
||||
|
||||
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);
|
||||
|
||||
int set_keepalive(int fd);
|
||||
int get_so_error(int fd);
|
112
tpws/hostlist.c
Normal file
112
tpws/hostlist.c
Normal file
@@ -0,0 +1,112 @@
|
||||
#include <stdio.h>
|
||||
#include "hostlist.h"
|
||||
#include "gzip.h"
|
||||
#include "params.h"
|
||||
|
||||
static bool addpool(strpool **hostlist, char **s, 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 LoadHostList(strpool **hostlist, char *filename)
|
||||
{
|
||||
char *p, *e, s[256], *zbuf;
|
||||
size_t zsize;
|
||||
int ct = 0;
|
||||
FILE *F;
|
||||
int r;
|
||||
|
||||
if (*hostlist)
|
||||
{
|
||||
StrPoolDestroy(hostlist);
|
||||
*hostlist = NULL;
|
||||
}
|
||||
|
||||
if (!(F = fopen(filename, "rb")))
|
||||
{
|
||||
fprintf(stderr, "Could not open %s\n", filename);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (is_gzip(F))
|
||||
{
|
||||
r = z_readfile(F,&zbuf,&zsize);
|
||||
fclose(F);
|
||||
if (r==Z_OK)
|
||||
{
|
||||
printf("zlib compression detected. uncompressed size : %zu\n", zsize);
|
||||
|
||||
p = zbuf;
|
||||
e = zbuf + zsize;
|
||||
while(p<e)
|
||||
{
|
||||
if (!addpool(hostlist,&p,e))
|
||||
{
|
||||
fprintf(stderr, "Not enough memory to store host list : %s\n", filename);
|
||||
free(zbuf);
|
||||
return false;
|
||||
}
|
||||
ct++;
|
||||
}
|
||||
free(zbuf);
|
||||
}
|
||||
else
|
||||
{
|
||||
fprintf(stderr, "zlib decompression failed : result %d\n",r);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("loading plain text list\n");
|
||||
|
||||
while (fgets(s, 256, F))
|
||||
{
|
||||
p = s;
|
||||
if (!addpool(hostlist,&p,p+strlen(p)))
|
||||
{
|
||||
fprintf(stderr, "Not enough memory to store host list : %s\n", filename);
|
||||
fclose(F);
|
||||
return false;
|
||||
}
|
||||
ct++;
|
||||
}
|
||||
fclose(F);
|
||||
}
|
||||
|
||||
printf("Loaded %d hosts from %s\n", ct, filename);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool SearchHostList(strpool *hostlist, const char *host, bool debug)
|
||||
{
|
||||
if (hostlist)
|
||||
{
|
||||
const char *p = host;
|
||||
bool bInHostList;
|
||||
while (p)
|
||||
{
|
||||
bInHostList = StrPoolCheckStr(hostlist, p);
|
||||
if (debug) VPRINT("Hostlist check for %s : %s", p, bInHostList ? "positive" : "negative")
|
||||
if (bInHostList) return true;
|
||||
p = strchr(p, '.');
|
||||
if (p) p++;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
7
tpws/hostlist.h
Normal file
7
tpws/hostlist.h
Normal file
@@ -0,0 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include "strpool.h"
|
||||
|
||||
bool LoadHostList(strpool **hostlist, char *filename);
|
||||
bool SearchHostList(strpool *hostlist, const char *host, bool debug);
|
47
tpws/macos/net/pfvar.h
Normal file
47
tpws/macos/net/pfvar.h
Normal 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
803
tpws/macos/sys/tree.h
Normal 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_ */
|
54
tpws/params.h
Normal file
54
tpws/params.h
Normal file
@@ -0,0 +1,54 @@
|
||||
#pragma once
|
||||
|
||||
#include <net/if.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include "strpool.h"
|
||||
|
||||
enum splithttpreq { split_none = 0, split_method, split_host };
|
||||
|
||||
#define MAX_BINDS 32
|
||||
struct bind_s
|
||||
{
|
||||
char bindaddr[64],bindiface[IF_NAMESIZE];
|
||||
bool bind_if6;
|
||||
bool bindll,bindll_force;
|
||||
int bind_wait_ifup,bind_wait_ip,bind_wait_ip_ll;
|
||||
};
|
||||
|
||||
struct params_s
|
||||
{
|
||||
struct bind_s binds[MAX_BINDS];
|
||||
int binds_last;
|
||||
bool bind_wait_only;
|
||||
uint16_t port;
|
||||
|
||||
uint8_t proxy_type;
|
||||
bool no_resolve;
|
||||
bool skip_nodelay;
|
||||
bool droproot;
|
||||
uid_t uid;
|
||||
gid_t gid;
|
||||
bool daemon;
|
||||
int maxconn,maxfiles,max_orphan_time;
|
||||
int local_rcvbuf,local_sndbuf,remote_rcvbuf,remote_sndbuf;
|
||||
|
||||
bool tamper; // any tamper option is set
|
||||
bool hostcase, hostdot, hosttab, hostnospace, methodspace, methodeol, unixeol, domcase;
|
||||
int hostpad;
|
||||
char hostspell[4];
|
||||
enum splithttpreq split_http_req;
|
||||
bool split_any_protocol;
|
||||
int split_pos;
|
||||
char hostfile[256];
|
||||
char pidfile[256];
|
||||
strpool *hostlist;
|
||||
|
||||
int debug;
|
||||
};
|
||||
|
||||
extern struct params_s params;
|
||||
|
||||
#define _DBGPRINT(format, level, ...) { if (params.debug>=level) printf(format "\n", ##__VA_ARGS__); }
|
||||
#define VPRINT(format, ...) _DBGPRINT(format,1,##__VA_ARGS__)
|
||||
#define DBGPRINT(format, ...) _DBGPRINT(format,2,##__VA_ARGS__)
|
132
tpws/protocol.c
Normal file
132
tpws/protocol.c
Normal file
@@ -0,0 +1,132 @@
|
||||
#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 };
|
||||
bool IsHttp(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 true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
bool HttpExtractHost(const uint8_t *data, size_t len, char *host, size_t len_host)
|
||||
{
|
||||
const uint8_t *p, *s, *e=data+len;
|
||||
|
||||
p = (uint8_t*)strncasestr((char*)data, "\nHost:", len);
|
||||
if (!p) return false;
|
||||
p+=6;
|
||||
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 (host && len_host)
|
||||
{
|
||||
if (slen>=len_host) slen=len_host-1;
|
||||
for(size_t i=0;i<slen;i++) host[i]=tolower(p[i]);
|
||||
host[slen]=0;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
bool IsTLSClientHello(const uint8_t *data, size_t len)
|
||||
{
|
||||
return len>=6 && data[0]==0x16 && data[1]==0x03 && data[2]==0x01 && data[5]==0x01 && (ntohs(*(uint16_t*)(data+3))+5)<=len;
|
||||
}
|
||||
bool TLSFindExt(const uint8_t *data, size_t len, uint16_t type, const uint8_t **ext, size_t *len_ext)
|
||||
{
|
||||
// +0
|
||||
// u8 ContentType: Handshake
|
||||
// u16 Version: TLS1.0
|
||||
// u16 Length
|
||||
// +5
|
||||
// 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+2+2+1+3+2+32;
|
||||
// SessionIDLength
|
||||
if (len<(l+1)) return false;
|
||||
ll = data[6]<<16 | data[7]<<8 | data[8]; // HandshakeProtocol length
|
||||
if (len<(ll+9)) return false;
|
||||
l += data[l]+1;
|
||||
// CipherSuitesLength
|
||||
if (len<(l+2)) return false;
|
||||
l += ntohs(*(uint16_t*)(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=ntohs(*(uint16_t*)data);
|
||||
data+=2; len-=2;
|
||||
if (l<len) return false;
|
||||
|
||||
uint16_t ntype=htons(type);
|
||||
while(l>=4)
|
||||
{
|
||||
uint16_t etype=*(uint16_t*)data;
|
||||
size_t elen=ntohs(*(uint16_t*)(data+2));
|
||||
data+=4; l-=4;
|
||||
if (l<elen) break;
|
||||
if (etype==ntype)
|
||||
{
|
||||
if (ext && len_ext)
|
||||
{
|
||||
*ext = data;
|
||||
*len_ext = elen;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
data+=elen; l-=elen;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
bool TLSHelloExtractHost(const uint8_t *data, size_t len, char *host, size_t len_host)
|
||||
{
|
||||
const uint8_t *ext;
|
||||
size_t elen;
|
||||
|
||||
if (!TLSFindExt(data,len,0,&ext,&elen)) return false;
|
||||
// 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 = ntohs(*(uint16_t*)(ext+3));
|
||||
ext+=5; elen-=5;
|
||||
if (slen<elen) return false;
|
||||
if (ext && 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;
|
||||
}
|
11
tpws/protocol.h
Normal file
11
tpws/protocol.h
Normal file
@@ -0,0 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
bool IsHttp(const uint8_t *data, size_t len);
|
||||
bool HttpExtractHost(const uint8_t *data, size_t len, char *host, size_t len_host);
|
||||
bool IsTLSClientHello(const uint8_t *data, size_t len);
|
||||
bool TLSFindExt(const uint8_t *data, size_t len, uint16_t type, const uint8_t **ext, size_t *len_ext);
|
||||
bool TLSHelloExtractHost(const uint8_t *data, size_t len, char *host, size_t len_host);
|
211
tpws/redirect.c
Normal file
211
tpws/redirect.c
Normal file
@@ -0,0 +1,211 @@
|
||||
#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"
|
||||
|
||||
//#if !defined(USE_PF) && defined(__OpenBSD__)
|
||||
#if !defined(USE_PF) && (defined(__OpenBSD__) || defined(__APPLE__))
|
||||
#define USE_PF 1
|
||||
#endif
|
||||
|
||||
#ifdef __linux__
|
||||
#include <linux/netfilter_ipv4.h>
|
||||
#ifndef IP6T_SO_ORIGINAL_DST
|
||||
#define IP6T_SO_ORIGINAL_DST 80
|
||||
#endif
|
||||
#endif
|
||||
#ifdef USE_PF
|
||||
#include <net/if.h>
|
||||
#include <net/pfvar.h>
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
#if defined(USE_PF)
|
||||
static int redirector_fd=-1;
|
||||
|
||||
void redir_close()
|
||||
{
|
||||
if (redirector_fd!=-1)
|
||||
{
|
||||
close(redirector_fd);
|
||||
redirector_fd = -1;
|
||||
DBGPRINT("closed redirector");
|
||||
}
|
||||
}
|
||||
static bool redir_open_private(const char *fname, int flags)
|
||||
{
|
||||
redir_close();
|
||||
redirector_fd = open(fname, flags);
|
||||
if (redirector_fd < 0)
|
||||
{
|
||||
perror("redir_openv_private: ");
|
||||
return false;
|
||||
}
|
||||
DBGPRINT("opened redirector %s",fname);
|
||||
return true;
|
||||
}
|
||||
bool redir_init()
|
||||
{
|
||||
return redir_open_private("/dev/pf", O_RDONLY);
|
||||
}
|
||||
|
||||
static bool destination_from_pf(const struct sockaddr *accept_sa, struct sockaddr_storage *orig_dst)
|
||||
{
|
||||
struct pfioc_natlook nl;
|
||||
|
||||
if (redirector_fd==-1) return false;
|
||||
|
||||
if (accept_sa->sa_family!=orig_dst->ss_family)
|
||||
{
|
||||
DBGPRINT("accept_sa and orig_dst sa_family mismatch : %d %d", 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.saddr.v4.s_addr = ((struct sockaddr_in*)accept_sa)->sin_addr.s_addr;
|
||||
nl.daddr.v4.s_addr = sin->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.saddr.v6 = ((struct sockaddr_in6*)accept_sa)->sin6_addr;
|
||||
nl.daddr.v6 = sin6->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",orig_dst->ss_family);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ioctl(redirector_fd, DIOCNATLOOK, &nl) < 0)
|
||||
{
|
||||
DBGPRINT("ioctl(DIOCNATLOOK) failed: %s",strerror(errno));
|
||||
return false;
|
||||
}
|
||||
DBGPRINT("destination_from_pf : got orig dest addr from pf");
|
||||
|
||||
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",nl.af);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
#else
|
||||
|
||||
bool redir_init() {return true;}
|
||||
void redir_close() {};
|
||||
|
||||
#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 !");
|
||||
#endif
|
||||
// TPROXY : socket is bound to original destination
|
||||
r=getsockname(sockfd, (struct sockaddr*) orig_dst, &addrlen);
|
||||
if (r<0)
|
||||
{
|
||||
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 USE_PF
|
||||
if (!destination_from_pf(accept_sa, orig_dst))
|
||||
DBGPRINT("pf filter destination_from_pf failed");
|
||||
#endif
|
||||
#ifdef __linux__
|
||||
}
|
||||
#endif
|
||||
if (saconvmapped(orig_dst))
|
||||
DBGPRINT("Original destination : converted ipv6 mapped address to ipv4");
|
||||
|
||||
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", 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", sockfd,orig_dst_str, htons(((struct sockaddr_in6*) orig_dst)->sin6_port))
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
9
tpws/redirect.h
Normal file
9
tpws/redirect.h
Normal 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 redir_close();
|
172
tpws/sec.c
Normal file
172
tpws/sec.c
Normal file
@@ -0,0 +1,172 @@
|
||||
#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>
|
||||
|
||||
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()
|
||||
{
|
||||
int maxcap = CAP_LAST_CAP;
|
||||
FILE *F = fopen("/proc/sys/kernel/cap_last_cap", "r");
|
||||
if (F)
|
||||
{
|
||||
fscanf(F, "%d", &maxcap);
|
||||
fclose(F);
|
||||
}
|
||||
return maxcap;
|
||||
|
||||
}
|
||||
bool dropcaps()
|
||||
{
|
||||
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)
|
||||
{
|
||||
fprintf(stderr, "could not drop bound cap %d\n", cap);
|
||||
perror("cap_drop_bound");
|
||||
}
|
||||
}
|
||||
}
|
||||
// now without CAP_SETPCAP
|
||||
if (!setpcap(caps))
|
||||
{
|
||||
perror("setpcap");
|
||||
return checkpcap(caps);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
bool can_drop_root()
|
||||
{
|
||||
#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))
|
||||
{
|
||||
perror("prctl(PR_SET_KEEPCAPS): ");
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
// drop all SGIDs
|
||||
if (setgroups(0,NULL))
|
||||
{
|
||||
perror("setgroups: ");
|
||||
return false;
|
||||
}
|
||||
if (setgid(gid))
|
||||
{
|
||||
perror("setgid: ");
|
||||
return false;
|
||||
}
|
||||
if (setuid(uid))
|
||||
{
|
||||
perror("setuid: ");
|
||||
return false;
|
||||
}
|
||||
#ifdef __linux__
|
||||
return dropcaps();
|
||||
#else
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
||||
void print_id()
|
||||
{
|
||||
int i,N;
|
||||
gid_t g[128];
|
||||
printf("Running as UID=%u GID=",getuid());
|
||||
N=getgroups(sizeof(g)/sizeof(*g),g);
|
||||
if (N>0)
|
||||
{
|
||||
for(i=0;i<N;i++)
|
||||
printf(i==(N-1) ? "%u" : "%u,", g[i]);
|
||||
printf("\n");
|
||||
}
|
||||
else
|
||||
printf("%u\n",getgid());
|
||||
}
|
||||
|
||||
void daemonize()
|
||||
{
|
||||
int pid;
|
||||
|
||||
pid = fork();
|
||||
if (pid == -1)
|
||||
{
|
||||
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;
|
||||
}
|
20
tpws/sec.h
Normal file
20
tpws/sec.h
Normal file
@@ -0,0 +1,20 @@
|
||||
#pragma once
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#ifdef __linux__
|
||||
|
||||
#include <sys/capability.h>
|
||||
|
||||
bool checkpcap(uint64_t caps);
|
||||
bool setpcap(uint64_t caps);
|
||||
int getmaxcap();
|
||||
bool dropcaps();
|
||||
#endif
|
||||
|
||||
bool can_drop_root();
|
||||
bool droproot(uid_t uid, gid_t gid);
|
||||
void print_id();
|
||||
void daemonize();
|
||||
bool writepid(const char *filename);
|
93
tpws/socks.h
Normal file
93
tpws/socks.h
Normal 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)
|
76
tpws/strpool.c
Normal file
76
tpws/strpool.c
Normal file
@@ -0,0 +1,76 @@
|
||||
#define _GNU_SOURCE
|
||||
#include "strpool.h"
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#undef uthash_nonfatal_oom
|
||||
#define uthash_nonfatal_oom(elt) ut_oom_recover(elt)
|
||||
|
||||
static bool oom=false;
|
||||
static void ut_oom_recover(strpool *elem)
|
||||
{
|
||||
oom=true;
|
||||
}
|
||||
|
||||
// for zero terminated strings
|
||||
bool StrPoolAddStr(strpool **pp,const char *s)
|
||||
{
|
||||
strpool *elem;
|
||||
if (!(elem = (strpool*)malloc(sizeof(strpool))))
|
||||
return false;
|
||||
if (!(elem->str = strdup(s)))
|
||||
{
|
||||
free(elem);
|
||||
return false;
|
||||
}
|
||||
oom = false;
|
||||
HASH_ADD_KEYPTR( hh, *pp, elem->str, strlen(elem->str), elem );
|
||||
if (oom)
|
||||
{
|
||||
free(elem->str);
|
||||
free(elem);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
// for not zero terminated strings
|
||||
bool StrPoolAddStrLen(strpool **pp,const char *s,size_t slen)
|
||||
{
|
||||
strpool *elem;
|
||||
if (!(elem = (strpool*)malloc(sizeof(strpool))))
|
||||
return false;
|
||||
if (!(elem->str = malloc(slen+1)))
|
||||
{
|
||||
free(elem);
|
||||
return false;
|
||||
}
|
||||
memcpy(elem->str,s,slen);
|
||||
elem->str[slen]=0;
|
||||
oom = false;
|
||||
HASH_ADD_KEYPTR( hh, *pp, elem->str, strlen(elem->str), elem );
|
||||
if (oom)
|
||||
{
|
||||
free(elem->str);
|
||||
free(elem);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool StrPoolCheckStr(strpool *p,const char *s)
|
||||
{
|
||||
strpool *elem;
|
||||
HASH_FIND_STR( p, s, elem);
|
||||
return elem!=NULL;
|
||||
}
|
||||
|
||||
void StrPoolDestroy(strpool **p)
|
||||
{
|
||||
strpool *elem,*tmp;
|
||||
HASH_ITER(hh, *p, elem, tmp) {
|
||||
free(elem->str);
|
||||
HASH_DEL(*p, elem);
|
||||
free(elem);
|
||||
}
|
||||
*p = NULL;
|
||||
}
|
19
tpws/strpool.h
Normal file
19
tpws/strpool.h
Normal file
@@ -0,0 +1,19 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <ctype.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 **p);
|
||||
bool StrPoolAddStr(strpool **pp,const char *s);
|
||||
bool StrPoolAddStrLen(strpool **pp,const char *s,size_t slen);
|
||||
bool StrPoolCheckStr(strpool *p,const char *s);
|
235
tpws/tamper.c
Normal file
235
tpws/tamper.c
Normal file
@@ -0,0 +1,235 @@
|
||||
#define _GNU_SOURCE
|
||||
|
||||
#include "tamper.h"
|
||||
#include "params.h"
|
||||
#include "hostlist.h"
|
||||
#include "protocol.h"
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
|
||||
// pHost points to "Host: ..."
|
||||
bool find_host(char **pHost,char *buf,size_t bs)
|
||||
{
|
||||
if (!*pHost)
|
||||
{
|
||||
*pHost = memmem(buf, bs, "\nHost:", 6);
|
||||
if (*pHost)
|
||||
{
|
||||
(*pHost)++;
|
||||
VPRINT("Found Host: at pos %zu",*pHost - buf)
|
||||
}
|
||||
}
|
||||
return !!*pHost;
|
||||
}
|
||||
|
||||
static const char *http_methods[] = { "GET /","POST /","HEAD /","OPTIONS /","PUT /","DELETE /","CONNECT /","TRACE /",NULL };
|
||||
void modify_tcp_segment(char *segment,size_t segment_buffer_size,size_t *size,size_t *split_pos)
|
||||
{
|
||||
char *p, *pp, *pHost = NULL;
|
||||
size_t method_len = 0, pos;
|
||||
const char **method;
|
||||
bool bIsHttp = false, bBypass = false;
|
||||
char bRemovedHostSpace = 0;
|
||||
char Host[128];
|
||||
|
||||
*split_pos=0;
|
||||
|
||||
for (method = http_methods; *method; method++)
|
||||
{
|
||||
method_len = strlen(*method);
|
||||
if (method_len <= *size && !memcmp(segment, *method, method_len))
|
||||
{
|
||||
bIsHttp = true;
|
||||
method_len -= 2; // "GET /" => "GET"
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (bIsHttp)
|
||||
{
|
||||
VPRINT("Data block looks like http request start : %s", *method)
|
||||
// cpu saving : we search host only if and when required. we do not research host every time we need its position
|
||||
if (params.hostlist && find_host(&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';
|
||||
VPRINT("Requested Host is : %s", Host)
|
||||
for(p = Host; *p; p++) *p=tolower(*p);
|
||||
bBypass = !SearchHostList(params.hostlist,Host,!!params.debug);
|
||||
}
|
||||
if (!bBypass)
|
||||
{
|
||||
if (params.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 %zu. Stop replacing.", pp - segment)
|
||||
break;
|
||||
}
|
||||
pp = p;
|
||||
}
|
||||
pHost = NULL; // invalidate
|
||||
}
|
||||
if (params.methodeol && (*size+1+!params.unixeol)<=segment_buffer_size)
|
||||
{
|
||||
VPRINT("Adding EOL before method")
|
||||
if (params.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 (params.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")
|
||||
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 ((params.hostdot || params.hosttab) && *size<segment_buffer_size && find_host(&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", params.hostdot ? "dot" : "tab", pos)
|
||||
memmove(p + 1, p, *size - pos);
|
||||
*p = params.hostdot ? '.' : '\t'; // insert dot or tab
|
||||
(*size)++; // block will grow by 1 byte
|
||||
}
|
||||
}
|
||||
if (params.domcase && find_host(&pHost,segment,*size))
|
||||
{
|
||||
p = pHost + 5;
|
||||
pos = p - segment;
|
||||
VPRINT("Mixing domain case at pos %zu",pos)
|
||||
for (; p < (segment + *size) && *p != '\r' && *p != '\n'; p++)
|
||||
*p = (((size_t)p) & 1) ? tolower(*p) : toupper(*p);
|
||||
}
|
||||
if (params.hostnospace && find_host(&pHost,segment,*size) && (pHost+5)<(segment+*size) && pHost[5] == ' ')
|
||||
{
|
||||
p = pHost + 6;
|
||||
pos = p - segment;
|
||||
VPRINT("Removing space before host name at pos %zu", pos)
|
||||
memmove(p - 1, p, *size - pos);
|
||||
(*size)--; // block will shrink by 1 byte
|
||||
bRemovedHostSpace = 1;
|
||||
}
|
||||
if (params.hostcase && find_host(&pHost,segment,*size))
|
||||
{
|
||||
VPRINT("Changing 'Host:' => '%c%c%c%c:' at pos %zu", params.hostspell[0], params.hostspell[1], params.hostspell[2], params.hostspell[3], pHost - segment)
|
||||
memcpy(pHost, params.hostspell, 4);
|
||||
}
|
||||
if (params.hostpad && find_host(&pHost,segment,*size))
|
||||
{
|
||||
// add : XXXXX: <padding?[\r\n|\n]
|
||||
char s[8];
|
||||
size_t hsize = params.unixeol ? 8 : 9;
|
||||
size_t hostpad = params.hostpad<hsize ? hsize : params.hostpad;
|
||||
|
||||
if ((hsize+*size)>segment_buffer_size)
|
||||
VPRINT("could not add host padding : buffer too small")
|
||||
else
|
||||
{
|
||||
if ((hostpad+*size)>segment_buffer_size)
|
||||
{
|
||||
hostpad=segment_buffer_size-*size;
|
||||
VPRINT("host padding reduced to %zu bytes : buffer too small", hostpad)
|
||||
}
|
||||
else
|
||||
VPRINT("host padding with %zu bytes", 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 (params.unixeol)
|
||||
*p++='\n';
|
||||
else
|
||||
{
|
||||
*p++='\r';
|
||||
*p++='\n';
|
||||
}
|
||||
hostpad-=hsize+padsize;
|
||||
}
|
||||
pHost = NULL; // invalidate
|
||||
}
|
||||
}
|
||||
switch (params.split_http_req)
|
||||
{
|
||||
case split_method:
|
||||
*split_pos = method_len - 1 + params.methodeol + (params.methodeol && !params.unixeol);
|
||||
break;
|
||||
case split_host:
|
||||
if (find_host(&pHost,segment,*size))
|
||||
*split_pos = pHost + 6 - bRemovedHostSpace - segment;
|
||||
break;
|
||||
default:
|
||||
if (params.split_pos < *size) *split_pos = params.split_pos;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
VPRINT("Not acting on this request")
|
||||
}
|
||||
}
|
||||
else if (params.split_pos && params.split_pos < *size)
|
||||
{
|
||||
// split-pos is the only parameter applicable to non-http block (may be https ?)
|
||||
if (IsTLSClientHello((uint8_t*)segment,*size))
|
||||
{
|
||||
char host[256];
|
||||
|
||||
VPRINT("packet contains TLS ClientHello")
|
||||
// we need host only if hostlist is present
|
||||
if (params.hostlist && TLSHelloExtractHost((uint8_t*)segment,*size,host,sizeof(host)))
|
||||
{
|
||||
VPRINT("hostname: %s",host)
|
||||
if (!SearchHostList(params.hostlist,host,!!params.debug))
|
||||
{
|
||||
VPRINT("Not acting on this request")
|
||||
return;
|
||||
}
|
||||
}
|
||||
*split_pos = params.split_pos;
|
||||
}
|
||||
else if (params.split_any_protocol)
|
||||
*split_pos = params.split_pos;
|
||||
}
|
||||
}
|
7
tpws/tamper.h
Normal file
7
tpws/tamper.h
Normal file
@@ -0,0 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
bool find_host(char **pHost,char *buf,size_t bs);
|
||||
void modify_tcp_segment(char *segment,size_t segment_buffer_size,size_t *size,size_t *split_pos);
|
878
tpws/tpws.c
Normal file
878
tpws/tpws.c
Normal file
@@ -0,0 +1,878 @@
|
||||
#define _GNU_SOURCE
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
#include <netinet/ip.h>
|
||||
#include <net/if.h>
|
||||
#include <ifaddrs.h>
|
||||
#include <netdb.h>
|
||||
#include <unistd.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <sys/select.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdint.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <signal.h>
|
||||
#include <errno.h>
|
||||
#include <stdbool.h>
|
||||
#include <netinet/tcp.h>
|
||||
#include <getopt.h>
|
||||
#include <pwd.h>
|
||||
#include <sys/resource.h>
|
||||
#include <time.h>
|
||||
|
||||
#include "tpws.h"
|
||||
|
||||
#ifdef BSD
|
||||
#include <sys/sysctl.h>
|
||||
#endif
|
||||
|
||||
#include "tpws_conn.h"
|
||||
#include "hostlist.h"
|
||||
#include "params.h"
|
||||
#include "sec.h"
|
||||
#include "redirect.h"
|
||||
|
||||
struct params_s params;
|
||||
|
||||
bool bHup = false;
|
||||
static void onhup(int sig)
|
||||
{
|
||||
printf("HUP received !\n");
|
||||
if (params.hostlist)
|
||||
printf("Will reload hostlist on next request\n");
|
||||
bHup = true;
|
||||
}
|
||||
// should be called in normal execution
|
||||
void dohup()
|
||||
{
|
||||
if (bHup)
|
||||
{
|
||||
if (params.hostlist)
|
||||
{
|
||||
if (!LoadHostList(¶ms.hostlist, params.hostfile))
|
||||
{
|
||||
// what will we do without hostlist ?? sure, gonna die
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
bHup = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
static int8_t block_sigpipe()
|
||||
{
|
||||
sigset_t sigset;
|
||||
memset(&sigset, 0, sizeof(sigset));
|
||||
|
||||
//Get the old sigset, add SIGPIPE and update sigset
|
||||
if (sigprocmask(SIG_BLOCK, NULL, &sigset) == -1) {
|
||||
perror("sigprocmask (get)");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (sigaddset(&sigset, SIGPIPE) == -1) {
|
||||
perror("sigaddset");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (sigprocmask(SIG_BLOCK, &sigset, NULL) == -1) {
|
||||
perror("sigprocmask (set)");
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static bool is_interface_online(const char *ifname)
|
||||
{
|
||||
struct ifreq ifr;
|
||||
int sock;
|
||||
|
||||
if ((sock=socket(PF_INET, SOCK_DGRAM, IPPROTO_IP))==-1)
|
||||
return false;
|
||||
memset(&ifr, 0, sizeof(ifr));
|
||||
strncpy(ifr.ifr_name, ifname, IFNAMSIZ);
|
||||
ifr.ifr_name[IFNAMSIZ-1] = 0;
|
||||
ioctl(sock, SIOCGIFFLAGS, &ifr);
|
||||
close(sock);
|
||||
return !!(ifr.ifr_flags & IFF_UP);
|
||||
}
|
||||
|
||||
|
||||
static void exithelp()
|
||||
{
|
||||
printf(
|
||||
" --bind-addr=<v4_addr>|<v6_addr>; for v6 link locals append %%interface_name\n"
|
||||
" --bind-iface4=<interface_name>\t; bind to the first ipv4 addr of interface\n"
|
||||
" --bind-iface6=<interface_name>\t; bind to the first ipv6 addr of interface\n"
|
||||
" --bind-linklocal=prefer|force\t; prefer or force ipv6 link local\n"
|
||||
" --bind-wait-ifup=<sec>\t\t; wait for interface to appear and up\n"
|
||||
" --bind-wait-ip=<sec>\t\t; after ifup wait for ip address to appear up to N seconds\n"
|
||||
" --bind-wait-ip-linklocal=<sec>\t; accept only link locals first N seconds then any\n"
|
||||
" --bind-wait-only\t\t; wait for bind conditions satisfaction then exit. return code 0 if success.\n"
|
||||
" * multiple binds are supported. each bind-addr, bind-iface* start new bind\n"
|
||||
" --port=<port>\t\t\t; only one port number for all binds is supported\n"
|
||||
" --socks\t\t\t; implement socks4/5 proxy instead of transparent proxy\n"
|
||||
" --no-resolve\t\t\t; disable socks5 remote dns ability (resolves are not async, they block all activity)\n"
|
||||
" --local-rcvbuf=<bytes>\n"
|
||||
" --local-sndbuf=<bytes>\n"
|
||||
" --remote-rcvbuf=<bytes>\n"
|
||||
" --remote-sndbuf=<bytes>\n"
|
||||
" --skip-nodelay\t\t\t; do not set TCP_NODELAY option for outgoing connections (incompatible with split options)\n"
|
||||
" --maxconn=<max_connections>\n"
|
||||
#ifdef SPLICE_PRESENT
|
||||
" --maxfiles=<max_open_files>\t; should be at least (X*connections+16), where X=6 in tcp proxy mode, X=4 in tampering mode\n"
|
||||
#else
|
||||
" --maxfiles=<max_open_files>\t; should be at least (connections*2+16)\n"
|
||||
#endif
|
||||
" --max-orphan-time=<sec>\t; if local leg sends something and closes and remote leg is still connecting then cancel connection attempt after N seconds\n"
|
||||
" --daemon\t\t\t; daemonize\n"
|
||||
" --pidfile=<filename>\t\t; write pid to file\n"
|
||||
" --user=<username>\t\t; drop root privs\n"
|
||||
" --uid=uid[:gid]\t\t; drop root privs\n"
|
||||
" --debug=0|1|2\t\t\t; 0(default)=silent 1=verbose 2=debug\n"
|
||||
"\nTAMPERING:\n"
|
||||
" --hostlist=<filename>\t\t; only act on host in the list (one host per line, subdomains auto apply)\n"
|
||||
" --split-http-req=method|host\n"
|
||||
" --split-pos=<numeric_offset>\t; split at specified pos. split-http-req takes precedence for http.\n"
|
||||
" --split-any-protocol\t\t; split not only http and https\n"
|
||||
" --hostcase\t\t\t; change Host: => host:\n"
|
||||
" --hostspell\t\t\t; exact spelling of \"Host\" header. must be 4 chars. default is \"host\"\n"
|
||||
" --hostdot\t\t\t; add \".\" after Host: name\n"
|
||||
" --hosttab\t\t\t; add tab after Host: name\n"
|
||||
" --hostnospace\t\t\t; remove space after Host:\n"
|
||||
" --hostpad=<bytes>\t\t; add dummy padding headers before Host:\n"
|
||||
" --domcase\t\t\t; mix domain case : Host: TeSt.cOm\n"
|
||||
" --methodspace\t\t\t; add extra space after method\n"
|
||||
" --methodeol\t\t\t; add end-of-line before method\n"
|
||||
" --unixeol\t\t\t; replace 0D0A to 0A\n"
|
||||
);
|
||||
exit(1);
|
||||
}
|
||||
static void cleanup_params()
|
||||
{
|
||||
if (params.hostlist)
|
||||
{
|
||||
StrPoolDestroy(¶ms.hostlist);
|
||||
params.hostlist = NULL;
|
||||
}
|
||||
}
|
||||
static void exithelp_clean()
|
||||
{
|
||||
cleanup_params();
|
||||
exithelp();
|
||||
}
|
||||
static void exit_clean(int code)
|
||||
{
|
||||
cleanup_params();
|
||||
exit(code);
|
||||
}
|
||||
static void nextbind_clean()
|
||||
{
|
||||
params.binds_last++;
|
||||
if (params.binds_last>=MAX_BINDS)
|
||||
{
|
||||
fprintf(stderr,"maximum of %d binds are supported\n",MAX_BINDS);
|
||||
exit_clean(1);
|
||||
}
|
||||
}
|
||||
static void checkbind_clean()
|
||||
{
|
||||
if (params.binds_last<0)
|
||||
{
|
||||
fprintf(stderr,"start new bind with --bind-addr,--bind-iface*\n");
|
||||
exit_clean(1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void parse_params(int argc, char *argv[])
|
||||
{
|
||||
int option_index = 0;
|
||||
int v, i;
|
||||
|
||||
memset(¶ms, 0, sizeof(params));
|
||||
memcpy(params.hostspell, "host", 4); // default hostspell
|
||||
params.maxconn = DEFAULT_MAX_CONN;
|
||||
params.max_orphan_time = DEFAULT_MAX_ORPHAN_TIME;
|
||||
params.binds_last = -1;
|
||||
if (can_drop_root())
|
||||
{
|
||||
params.uid = params.gid = 0x7FFFFFFF; // default uid:gid
|
||||
params.droproot = true;
|
||||
}
|
||||
|
||||
const struct option long_options[] = {
|
||||
{ "help",no_argument,0,0 },// optidx=0
|
||||
{ "h",no_argument,0,0 },// optidx=1
|
||||
{ "bind-addr",required_argument,0,0 },// optidx=2
|
||||
{ "bind-iface4",required_argument,0,0 },// optidx=3
|
||||
{ "bind-iface6",required_argument,0,0 },// optidx=4
|
||||
{ "bind-linklocal",required_argument,0,0 },// optidx=5
|
||||
{ "bind-wait-ifup",required_argument,0,0 },// optidx=6
|
||||
{ "bind-wait-ip",required_argument,0,0 },// optidx=7
|
||||
{ "bind-wait-ip-linklocal",required_argument,0,0 },// optidx=8
|
||||
{ "bind-wait-only",no_argument,0,0 },// optidx=9
|
||||
{ "port",required_argument,0,0 },// optidx=10
|
||||
{ "daemon",no_argument,0,0 },// optidx=11
|
||||
{ "user",required_argument,0,0 },// optidx=12
|
||||
{ "uid",required_argument,0,0 },// optidx=13
|
||||
{ "maxconn",required_argument,0,0 },// optidx=14
|
||||
{ "maxfiles",required_argument,0,0 },// optidx=15
|
||||
{ "max-orphan-time",required_argument,0,0 },// optidx=16
|
||||
{ "hostcase",no_argument,0,0 },// optidx=17
|
||||
{ "hostspell",required_argument,0,0 },// optidx=18
|
||||
{ "hostdot",no_argument,0,0 },// optidx=19
|
||||
{ "hostnospace",no_argument,0,0 },// optidx=20
|
||||
{ "hostpad",required_argument,0,0 },// optidx=21
|
||||
{ "domcase",no_argument,0,0 },// optidx=22
|
||||
{ "split-http-req",required_argument,0,0 },// optidx=23
|
||||
{ "split-pos",required_argument,0,0 },// optidx=24
|
||||
{ "split-any-protocol",optional_argument,0,0},// optidx=25
|
||||
{ "methodspace",no_argument,0,0 },// optidx=26
|
||||
{ "methodeol",no_argument,0,0 },// optidx=27
|
||||
{ "hosttab",no_argument,0,0 },// optidx=28
|
||||
{ "unixeol",no_argument,0,0 },// optidx=29
|
||||
{ "hostlist",required_argument,0,0 },// optidx=30
|
||||
{ "pidfile",required_argument,0,0 },// optidx=31
|
||||
{ "debug",optional_argument,0,0 },// optidx=32
|
||||
{ "local-rcvbuf",required_argument,0,0 },// optidx=33
|
||||
{ "local-sndbuf",required_argument,0,0 },// optidx=34
|
||||
{ "remote-rcvbuf",required_argument,0,0 },// optidx=35
|
||||
{ "remote-sndbuf",required_argument,0,0 },// optidx=36
|
||||
{ "socks",no_argument,0,0 },// optidx=37
|
||||
{ "no-resolve",no_argument,0,0 },// optidx=38
|
||||
{ "skip-nodelay",no_argument,0,0 },// optidx=39
|
||||
{ NULL,0,NULL,0 }
|
||||
};
|
||||
while ((v = getopt_long_only(argc, argv, "", long_options, &option_index)) != -1)
|
||||
{
|
||||
if (v) exithelp_clean();
|
||||
switch (option_index)
|
||||
{
|
||||
case 0:
|
||||
case 1:
|
||||
exithelp_clean();
|
||||
break;
|
||||
case 2: /* bind-addr */
|
||||
nextbind_clean();
|
||||
{
|
||||
char *p = strchr(optarg,'%');
|
||||
if (p)
|
||||
{
|
||||
*p=0;
|
||||
strncpy(params.binds[params.binds_last].bindiface, p+1, sizeof(params.binds[params.binds_last].bindiface));
|
||||
}
|
||||
strncpy(params.binds[params.binds_last].bindaddr, optarg, sizeof(params.binds[params.binds_last].bindaddr));
|
||||
}
|
||||
params.binds[params.binds_last].bindaddr[sizeof(params.binds[params.binds_last].bindaddr) - 1] = 0;
|
||||
break;
|
||||
case 3: /* bind-iface4 */
|
||||
nextbind_clean();
|
||||
params.binds[params.binds_last].bind_if6=false;
|
||||
strncpy(params.binds[params.binds_last].bindiface, optarg, sizeof(params.binds[params.binds_last].bindiface));
|
||||
params.binds[params.binds_last].bindiface[sizeof(params.binds[params.binds_last].bindiface) - 1] = 0;
|
||||
break;
|
||||
case 4: /* bind-iface6 */
|
||||
nextbind_clean();
|
||||
params.binds[params.binds_last].bind_if6=true;
|
||||
strncpy(params.binds[params.binds_last].bindiface, optarg, sizeof(params.binds[params.binds_last].bindiface));
|
||||
params.binds[params.binds_last].bindiface[sizeof(params.binds[params.binds_last].bindiface) - 1] = 0;
|
||||
break;
|
||||
case 5: /* bind-linklocal */
|
||||
checkbind_clean();
|
||||
params.binds[params.binds_last].bindll = true;
|
||||
if (!strcmp(optarg, "force"))
|
||||
params.binds[params.binds_last].bindll_force=true;
|
||||
else if (strcmp(optarg, "prefer"))
|
||||
{
|
||||
fprintf(stderr, "invalid parameter in bind-linklocal : %s\n",optarg);
|
||||
exit_clean(1);
|
||||
}
|
||||
break;
|
||||
case 6: /* bind-wait-ifup */
|
||||
checkbind_clean();
|
||||
params.binds[params.binds_last].bind_wait_ifup = atoi(optarg);
|
||||
break;
|
||||
case 7: /* bind-wait-ip */
|
||||
checkbind_clean();
|
||||
params.binds[params.binds_last].bind_wait_ip = atoi(optarg);
|
||||
break;
|
||||
case 8: /* bind-wait-ip-linklocal */
|
||||
checkbind_clean();
|
||||
params.binds[params.binds_last].bind_wait_ip_ll = atoi(optarg);
|
||||
break;
|
||||
case 9: /* bind-wait-only */
|
||||
params.bind_wait_only = true;
|
||||
break;
|
||||
case 10: /* port */
|
||||
i = atoi(optarg);
|
||||
if (i <= 0 || i > 65535)
|
||||
{
|
||||
fprintf(stderr, "bad port number\n");
|
||||
exit_clean(1);
|
||||
}
|
||||
params.port = (uint16_t)i;
|
||||
break;
|
||||
case 11: /* daemon */
|
||||
params.daemon = true;
|
||||
break;
|
||||
case 12: /* user */
|
||||
{
|
||||
struct passwd *pwd = getpwnam(optarg);
|
||||
if (!pwd)
|
||||
{
|
||||
fprintf(stderr, "non-existent username supplied\n");
|
||||
exit_clean(1);
|
||||
}
|
||||
params.uid = pwd->pw_uid;
|
||||
params.gid = pwd->pw_gid;
|
||||
params.droproot = true;
|
||||
break;
|
||||
}
|
||||
case 13: /* uid */
|
||||
params.gid=0x7FFFFFFF; // default git. drop gid=0
|
||||
params.droproot = true;
|
||||
if (!sscanf(optarg,"%u:%u",¶ms.uid,¶ms.gid))
|
||||
{
|
||||
fprintf(stderr, "--uid should be : uid[:gid]\n");
|
||||
exit_clean(1);
|
||||
}
|
||||
break;
|
||||
case 14: /* maxconn */
|
||||
params.maxconn = atoi(optarg);
|
||||
if (params.maxconn <= 0 || params.maxconn > 10000)
|
||||
{
|
||||
fprintf(stderr, "bad maxconn\n");
|
||||
exit_clean(1);
|
||||
}
|
||||
break;
|
||||
case 15: /* maxfiles */
|
||||
params.maxfiles = atoi(optarg);
|
||||
if (params.maxfiles < 0)
|
||||
{
|
||||
fprintf(stderr, "bad maxfiles\n");
|
||||
exit_clean(1);
|
||||
}
|
||||
break;
|
||||
case 16: /* max-orphan-time */
|
||||
params.max_orphan_time = atoi(optarg);
|
||||
if (params.max_orphan_time < 0)
|
||||
{
|
||||
fprintf(stderr, "bad max_orphan_time\n");
|
||||
exit_clean(1);
|
||||
}
|
||||
break;
|
||||
case 17: /* hostcase */
|
||||
params.hostcase = true;
|
||||
params.tamper = true;
|
||||
break;
|
||||
case 18: /* hostspell */
|
||||
if (strlen(optarg) != 4)
|
||||
{
|
||||
fprintf(stderr, "hostspell must be exactly 4 chars long\n");
|
||||
exit_clean(1);
|
||||
}
|
||||
params.hostcase = true;
|
||||
memcpy(params.hostspell, optarg, 4);
|
||||
params.tamper = true;
|
||||
break;
|
||||
case 19: /* hostdot */
|
||||
params.hostdot = true;
|
||||
params.tamper = true;
|
||||
break;
|
||||
case 20: /* hostnospace */
|
||||
params.hostnospace = true;
|
||||
params.tamper = true;
|
||||
break;
|
||||
case 21: /* hostpad */
|
||||
params.hostpad = atoi(optarg);
|
||||
params.tamper = true;
|
||||
break;
|
||||
case 22: /* domcase */
|
||||
params.domcase = true;
|
||||
params.tamper = true;
|
||||
break;
|
||||
case 23: /* split-http-req */
|
||||
if (!strcmp(optarg, "method"))
|
||||
params.split_http_req = split_method;
|
||||
else if (!strcmp(optarg, "host"))
|
||||
params.split_http_req = split_host;
|
||||
else
|
||||
{
|
||||
fprintf(stderr, "Invalid argument for split-http-req\n");
|
||||
exit_clean(1);
|
||||
}
|
||||
params.tamper = true;
|
||||
break;
|
||||
case 24: /* split-pos */
|
||||
i = atoi(optarg);
|
||||
if (i)
|
||||
params.split_pos = i;
|
||||
else
|
||||
{
|
||||
fprintf(stderr, "Invalid argument for split-pos\n");
|
||||
exit_clean(1);
|
||||
}
|
||||
params.tamper = true;
|
||||
break;
|
||||
case 25: /* split-any-protocol */
|
||||
params.split_any_protocol = true;
|
||||
break;
|
||||
case 26: /* methodspace */
|
||||
params.methodspace = true;
|
||||
params.tamper = true;
|
||||
break;
|
||||
case 27: /* methodeol */
|
||||
params.methodeol = true;
|
||||
params.tamper = true;
|
||||
break;
|
||||
case 28: /* hosttab */
|
||||
params.hosttab = true;
|
||||
params.tamper = true;
|
||||
break;
|
||||
case 29: /* unixeol */
|
||||
params.unixeol = true;
|
||||
params.tamper = true;
|
||||
break;
|
||||
case 30: /* hostlist */
|
||||
if (!LoadHostList(¶ms.hostlist, optarg))
|
||||
exit_clean(1);
|
||||
strncpy(params.hostfile,optarg,sizeof(params.hostfile));
|
||||
params.hostfile[sizeof(params.hostfile)-1]='\0';
|
||||
params.tamper = true;
|
||||
break;
|
||||
case 31: /* pidfile */
|
||||
strncpy(params.pidfile,optarg,sizeof(params.pidfile));
|
||||
params.pidfile[sizeof(params.pidfile)-1]='\0';
|
||||
break;
|
||||
case 32:
|
||||
params.debug = optarg ? atoi(optarg) : 1;
|
||||
break;
|
||||
case 33: /* local-rcvbuf */
|
||||
params.local_rcvbuf = atoi(optarg)/2;
|
||||
break;
|
||||
case 34: /* local-sndbuf */
|
||||
params.local_sndbuf = atoi(optarg)/2;
|
||||
break;
|
||||
case 35: /* remote-rcvbuf */
|
||||
params.remote_rcvbuf = atoi(optarg)/2;
|
||||
break;
|
||||
case 36: /* remote-sndbuf */
|
||||
params.remote_sndbuf = atoi(optarg)/2;
|
||||
break;
|
||||
case 37: /* socks */
|
||||
params.proxy_type = CONN_TYPE_SOCKS;
|
||||
break;
|
||||
case 38: /* no-resolve */
|
||||
params.no_resolve = true;
|
||||
break;
|
||||
case 39: /* skip-nodelay */
|
||||
params.skip_nodelay = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!params.bind_wait_only && !params.port)
|
||||
{
|
||||
fprintf(stderr, "Need port number\n");
|
||||
exit_clean(1);
|
||||
}
|
||||
if (params.binds_last<=0)
|
||||
{
|
||||
params.binds_last=0; // default bind to all
|
||||
}
|
||||
if (params.skip_nodelay && (params.split_http_req || params.split_pos))
|
||||
{
|
||||
fprintf(stderr, "Cannot split with --skip-nodelay\n");
|
||||
exit_clean(1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static bool is_linklocal(const struct sockaddr_in6* a)
|
||||
{
|
||||
return a->sin6_addr.s6_addr[0]==0xFE && (a->sin6_addr.s6_addr[1] & 0xC0)==0x80;
|
||||
}
|
||||
static bool find_listen_addr(struct sockaddr_storage *salisten, const char *bindiface, bool bind_if6, bool bindll, int *if_index)
|
||||
{
|
||||
struct ifaddrs *addrs,*a;
|
||||
bool found=false;
|
||||
|
||||
if (getifaddrs(&addrs)<0)
|
||||
return false;
|
||||
|
||||
int maxpass = (bind_if6 && !bindll) ? 2 : 1;
|
||||
for(int pass=0;pass<maxpass;pass++)
|
||||
{
|
||||
a = addrs;
|
||||
while (a)
|
||||
{
|
||||
if (a->ifa_addr)
|
||||
{
|
||||
if (a->ifa_addr->sa_family==AF_INET &&
|
||||
*bindiface && !bind_if6 && !strcmp(a->ifa_name, bindiface))
|
||||
{
|
||||
salisten->ss_family = AF_INET;
|
||||
memcpy(&((struct sockaddr_in*)salisten)->sin_addr, &((struct sockaddr_in*)a->ifa_addr)->sin_addr, sizeof(struct in_addr));
|
||||
found=true;
|
||||
goto ex;
|
||||
}
|
||||
// ipv6 links locals are fe80::/10
|
||||
else if (a->ifa_addr->sa_family==AF_INET6
|
||||
&&
|
||||
(!*bindiface && bindll ||
|
||||
*bindiface && bind_if6 && !strcmp(a->ifa_name, bindiface))
|
||||
&&
|
||||
(bindll && is_linklocal((struct sockaddr_in6*)a->ifa_addr) ||
|
||||
!bindll && (pass || !is_linklocal((struct sockaddr_in6*)a->ifa_addr)))
|
||||
)
|
||||
{
|
||||
salisten->ss_family = AF_INET6;
|
||||
memcpy(&((struct sockaddr_in6*)salisten)->sin6_addr, &((struct sockaddr_in6*)a->ifa_addr)->sin6_addr, sizeof(struct in6_addr));
|
||||
if (if_index) *if_index = if_nametoindex(a->ifa_name);
|
||||
found=true;
|
||||
goto ex;
|
||||
}
|
||||
}
|
||||
a = a->ifa_next;
|
||||
}
|
||||
}
|
||||
ex:
|
||||
freeifaddrs(addrs);
|
||||
return found;
|
||||
}
|
||||
|
||||
static bool read_system_maxfiles(rlim_t *maxfile)
|
||||
{
|
||||
#ifdef __linux__
|
||||
FILE *F;
|
||||
int n;
|
||||
uintmax_t um;
|
||||
if (!(F=fopen("/proc/sys/fs/file-max","r")))
|
||||
return false;
|
||||
n=fscanf(F,"%ju",&um);
|
||||
fclose(F);
|
||||
if (!n) return false;
|
||||
*maxfile = (rlim_t)um;
|
||||
return true;
|
||||
#elif defined(BSD)
|
||||
int maxfiles,mib[2]={CTL_KERN, KERN_MAXFILES};
|
||||
size_t len = sizeof(maxfiles);
|
||||
if (sysctl(mib,2,&maxfiles,&len,NULL,0)==-1)
|
||||
return false;
|
||||
*maxfile = (rlim_t)maxfiles;
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
static bool write_system_maxfiles(rlim_t maxfile)
|
||||
{
|
||||
#ifdef __linux__
|
||||
FILE *F;
|
||||
int n;
|
||||
if (!(F=fopen("/proc/sys/fs/file-max","w")))
|
||||
return false;
|
||||
n=fprintf(F,"%ju",(uintmax_t)maxfile);
|
||||
fclose(F);
|
||||
return !!n;
|
||||
#elif defined(BSD)
|
||||
int maxfiles=(int)maxfile,mib[2]={CTL_KERN, KERN_MAXFILES};
|
||||
if (sysctl(mib,2,NULL,0,&maxfiles,sizeof(maxfiles))==-1)
|
||||
return false;
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
static bool set_ulimit()
|
||||
{
|
||||
rlim_t fdmax,fdmin_system,cur_lim=0;
|
||||
int n;
|
||||
|
||||
if (!params.maxfiles)
|
||||
{
|
||||
// 4 fds per tamper connection (2 pipe + 2 socket), 6 fds for tcp proxy connection (4 pipe + 2 socket)
|
||||
// additional 1/2 for unpaired remote legs sending buffers
|
||||
// 16 for listen_fd, epoll, hostlist, ...
|
||||
#ifdef SPLICE_PRESENT
|
||||
fdmax = (params.tamper ? 4 : 6) * params.maxconn;
|
||||
#else
|
||||
fdmax = 2 * params.maxconn;
|
||||
#endif
|
||||
fdmax += fdmax/2 + 16;
|
||||
}
|
||||
else
|
||||
fdmax = params.maxfiles;
|
||||
fdmin_system = fdmax + 4096;
|
||||
DBGPRINT("set_ulimit : fdmax=%ju fdmin_system=%ju",(uintmax_t)fdmax,(uintmax_t)fdmin_system)
|
||||
|
||||
if (!read_system_maxfiles(&cur_lim))
|
||||
return false;
|
||||
DBGPRINT("set_ulimit : current system file-max=%ju",(uintmax_t)cur_lim)
|
||||
if (cur_lim<fdmin_system)
|
||||
{
|
||||
DBGPRINT("set_ulimit : system fd limit is too low. trying to increase to %jd",(uintmax_t)fdmin_system)
|
||||
if (!write_system_maxfiles(fdmin_system))
|
||||
{
|
||||
fprintf(stderr,"could not set system-wide max file descriptors\n");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
struct rlimit rlim = {fdmax,fdmax};
|
||||
n=setrlimit(RLIMIT_NOFILE, &rlim);
|
||||
if (n==-1) perror("setrlimit");
|
||||
return n!=-1;
|
||||
}
|
||||
|
||||
struct salisten_s
|
||||
{
|
||||
struct sockaddr_storage salisten;
|
||||
socklen_t salisten_len;
|
||||
int ipv6_only;
|
||||
};
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
int i, listen_fd[MAX_BINDS], yes = 1, retval = 0, if_index, exit_v=EXIT_FAILURE;
|
||||
struct salisten_s list[MAX_BINDS];
|
||||
|
||||
srand(time(NULL));
|
||||
parse_params(argc, argv);
|
||||
|
||||
if (params.daemon) daemonize();
|
||||
|
||||
if (*params.pidfile && !writepid(params.pidfile))
|
||||
{
|
||||
fprintf(stderr,"could not write pidfile\n");
|
||||
goto exiterr;
|
||||
}
|
||||
|
||||
memset(&list, 0, sizeof(list));
|
||||
for(i=0;i<=params.binds_last;i++) listen_fd[i]=-1;
|
||||
|
||||
for(i=0;i<=params.binds_last;i++)
|
||||
{
|
||||
VPRINT("Prepare bind %d : addr=%s iface=%s v6=%u link_local=%u link_local_force=%u wait_ifup=%d wait_ip=%d wait_ip_ll=%d",i,
|
||||
params.binds[i].bindaddr,params.binds[i].bindiface,params.binds[i].bind_if6,params.binds[i].bindll,params.binds[i].bindll_force,
|
||||
params.binds[i].bind_wait_ifup,params.binds[i].bind_wait_ip,params.binds[i].bind_wait_ip_ll);
|
||||
if_index=0;
|
||||
if (*params.binds[i].bindiface)
|
||||
{
|
||||
if (params.binds[i].bind_wait_ifup > 0)
|
||||
{
|
||||
int sec=0;
|
||||
if (!is_interface_online(params.binds[i].bindiface))
|
||||
{
|
||||
printf("waiting for ifup of %s for up to %d second(s)...\n",params.binds[i].bindiface,params.binds[i].bind_wait_ifup);
|
||||
do
|
||||
{
|
||||
sleep(1);
|
||||
sec++;
|
||||
}
|
||||
while (!is_interface_online(params.binds[i].bindiface) && sec<params.binds[i].bind_wait_ifup);
|
||||
if (sec>=params.binds[i].bind_wait_ifup)
|
||||
{
|
||||
printf("wait timed out\n");
|
||||
goto exiterr;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!(if_index = if_nametoindex(params.binds[i].bindiface)) && params.binds[i].bind_wait_ip<=0)
|
||||
{
|
||||
printf("bad iface %s\n",params.binds[i].bindiface);
|
||||
goto exiterr;
|
||||
}
|
||||
}
|
||||
if (*params.binds[i].bindaddr)
|
||||
{
|
||||
if (inet_pton(AF_INET, params.binds[i].bindaddr, &((struct sockaddr_in*)(&list[i].salisten))->sin_addr))
|
||||
{
|
||||
list[i].salisten.ss_family = AF_INET;
|
||||
}
|
||||
else if (inet_pton(AF_INET6, params.binds[i].bindaddr, &((struct sockaddr_in6*)(&list[i].salisten))->sin6_addr))
|
||||
{
|
||||
list[i].salisten.ss_family = AF_INET6;
|
||||
list[i].ipv6_only = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("bad bind addr : %s\n", params.binds[i].bindaddr);
|
||||
goto exiterr;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (*params.binds[i].bindiface || params.binds[i].bindll)
|
||||
{
|
||||
bool found;
|
||||
int sec=0;
|
||||
|
||||
if (params.binds[i].bind_wait_ip > 0)
|
||||
{
|
||||
printf("waiting for ip on %s for up to %d second(s)...\n", *params.binds[i].bindiface ? params.binds[i].bindiface : "<any>", params.binds[i].bind_wait_ip);
|
||||
if (params.binds[i].bindll && !params.binds[i].bindll_force && params.binds[i].bind_wait_ip_ll>0)
|
||||
printf("during the first %d second(s) accepting only link locals...\n", params.binds[i].bind_wait_ip_ll);
|
||||
}
|
||||
|
||||
for(;;)
|
||||
{
|
||||
found = find_listen_addr(&list[i].salisten,params.binds[i].bindiface,params.binds[i].bind_if6,params.binds[i].bindll,&if_index);
|
||||
if (found) break;
|
||||
|
||||
if (params.binds[i].bindll && !params.binds[i].bindll_force && sec>=params.binds[i].bind_wait_ip_ll)
|
||||
if ((found = find_listen_addr(&list[i].salisten,params.binds[i].bindiface,params.binds[i].bind_if6,false,&if_index)))
|
||||
{
|
||||
printf("link local address wait timeout. using global address\n");
|
||||
break;
|
||||
}
|
||||
|
||||
if (sec>=params.binds[i].bind_wait_ip)
|
||||
break;
|
||||
|
||||
sleep(1);
|
||||
sec++;
|
||||
}
|
||||
|
||||
if (!found)
|
||||
{
|
||||
printf("suitable ip address not found\n");
|
||||
goto exiterr;
|
||||
}
|
||||
list[i].ipv6_only=1;
|
||||
}
|
||||
else
|
||||
{
|
||||
list[i].salisten.ss_family = AF_INET6;
|
||||
// leave sin6_addr zero
|
||||
}
|
||||
}
|
||||
if (list[i].salisten.ss_family == AF_INET6)
|
||||
{
|
||||
list[i].salisten_len = sizeof(struct sockaddr_in6);
|
||||
((struct sockaddr_in6*)(&list[i].salisten))->sin6_port = htons(params.port);
|
||||
if (is_linklocal((struct sockaddr_in6*)(&list[i].salisten)))
|
||||
((struct sockaddr_in6*)(&list[i].salisten))->sin6_scope_id = if_index;
|
||||
}
|
||||
else
|
||||
{
|
||||
list[i].salisten_len = sizeof(struct sockaddr_in);
|
||||
((struct sockaddr_in*)(&list[i].salisten))->sin_port = htons(params.port);
|
||||
}
|
||||
}
|
||||
|
||||
if (params.bind_wait_only)
|
||||
{
|
||||
printf("bind wait condition satisfied. exiting.\n");
|
||||
exit_v = 0;
|
||||
goto exiterr;
|
||||
}
|
||||
|
||||
if (params.proxy_type==CONN_TYPE_TRANSPARENT && !redir_init())
|
||||
{
|
||||
fprintf(stderr,"could not initialize redirector !!!\n");
|
||||
goto exiterr;
|
||||
}
|
||||
|
||||
for(i=0;i<=params.binds_last;i++)
|
||||
{
|
||||
VPRINT("Binding %d",i);
|
||||
|
||||
if ((listen_fd[i] = socket(list[i].salisten.ss_family, SOCK_STREAM, 0)) == -1) {
|
||||
perror("socket: ");
|
||||
goto exiterr;
|
||||
}
|
||||
#ifndef __OpenBSD__
|
||||
// in OpenBSD always IPV6_ONLY for wildcard sockets
|
||||
if ((list[i].salisten.ss_family == AF_INET6) && setsockopt(listen_fd[i], IPPROTO_IPV6, IPV6_V6ONLY, &list[i].ipv6_only, sizeof(int)) == -1)
|
||||
{
|
||||
perror("setsockopt (IPV6_ONLY): ");
|
||||
goto exiterr;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (setsockopt(listen_fd[i], SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)) == -1)
|
||||
{
|
||||
perror("setsockopt (SO_REUSEADDR): ");
|
||||
goto exiterr;
|
||||
}
|
||||
|
||||
//Mark that this socket can be used for transparent proxying
|
||||
//This allows the socket to accept connections for non-local IPs
|
||||
if (params.proxy_type==CONN_TYPE_TRANSPARENT)
|
||||
{
|
||||
#ifdef __linux__
|
||||
if (setsockopt(listen_fd[i], SOL_IP, IP_TRANSPARENT, &yes, sizeof(yes)) == -1)
|
||||
{
|
||||
perror("setsockopt (IP_TRANSPARENT): ");
|
||||
goto exiterr;
|
||||
}
|
||||
#elif defined(BSD) && defined(SO_BINDANY)
|
||||
if (setsockopt(listen_fd[i], SOL_SOCKET, SO_BINDANY, &yes, sizeof(yes)) == -1)
|
||||
{
|
||||
perror("setsockopt (SO_BINDANY): ");
|
||||
goto exiterr;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
if (!set_socket_buffers(listen_fd[i], params.local_rcvbuf, params.local_sndbuf))
|
||||
goto exiterr;
|
||||
if (!params.local_rcvbuf)
|
||||
{
|
||||
// HACK : dont know why but if dont set RCVBUF explicitly RCVBUF of accept()-ed socket can be very large. may be linux bug ?
|
||||
int v;
|
||||
socklen_t sz=sizeof(int);
|
||||
if (!getsockopt(listen_fd[i],SOL_SOCKET,SO_RCVBUF,&v,&sz))
|
||||
{
|
||||
v/=2;
|
||||
setsockopt(listen_fd[i],SOL_SOCKET,SO_RCVBUF,&v,sizeof(int));
|
||||
}
|
||||
}
|
||||
if (bind(listen_fd[i], (struct sockaddr *)&list[i].salisten, list[i].salisten_len) == -1) {
|
||||
perror("bind: ");
|
||||
goto exiterr;
|
||||
}
|
||||
if (listen(listen_fd[i], BACKLOG) == -1) {
|
||||
perror("listen: ");
|
||||
goto exiterr;
|
||||
}
|
||||
}
|
||||
|
||||
set_ulimit();
|
||||
|
||||
if (params.droproot && !droproot(params.uid,params.gid))
|
||||
goto exiterr;
|
||||
print_id();
|
||||
|
||||
//splice() causes the process to receive the SIGPIPE-signal if one part (for
|
||||
//example a socket) is closed during splice(). I would rather have splice()
|
||||
//fail and return -1, so blocking SIGPIPE.
|
||||
if (block_sigpipe() == -1) {
|
||||
fprintf(stderr, "Could not block SIGPIPE signal\n");
|
||||
goto exiterr;
|
||||
}
|
||||
|
||||
printf(params.proxy_type==CONN_TYPE_SOCKS ? "socks mode\n" : "transparent proxy mode\n");
|
||||
if (!params.tamper) printf("TCP proxy mode (no tampering)\n");
|
||||
|
||||
signal(SIGHUP, onhup);
|
||||
|
||||
retval = event_loop(listen_fd,params.binds_last+1);
|
||||
exit_v = retval < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
|
||||
printf("Exiting\n");
|
||||
|
||||
exiterr:
|
||||
redir_close();
|
||||
for(i=0;i<=params.binds_last;i++) if (listen_fd[i]!=-1) close(listen_fd[i]);
|
||||
cleanup_params();
|
||||
return exit_v;
|
||||
}
|
9
tpws/tpws.h
Normal file
9
tpws/tpws.h
Normal file
@@ -0,0 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef __linux__
|
||||
#define SPLICE_PRESENT
|
||||
#endif
|
||||
|
||||
#include <sys/param.h>
|
||||
|
||||
void dohup();
|
1289
tpws/tpws_conn.c
Normal file
1289
tpws/tpws_conn.c
Normal file
File diff suppressed because it is too large
Load Diff
96
tpws/tpws_conn.h
Normal file
96
tpws/tpws_conn.h
Normal file
@@ -0,0 +1,96 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <sys/queue.h>
|
||||
#include <time.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
|
||||
|
||||
int event_loop(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
|
||||
{
|
||||
char *data;
|
||||
size_t len,pos;
|
||||
};
|
||||
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 tproxy_conn *partner; // other leg
|
||||
time_t orphan_since;
|
||||
|
||||
// socks5 state machine
|
||||
enum {
|
||||
S_WAIT_HANDSHAKE=0,
|
||||
S_WAIT_REQUEST,
|
||||
S_WAIT_CONNECTION,
|
||||
S_TCP
|
||||
} socks_state;
|
||||
uint8_t socks_ver;
|
||||
|
||||
// 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, bFlowInPrev,bFlowOutPrev, bPrevRdhup;
|
||||
|
||||
// total read,write
|
||||
size_t trd,twr;
|
||||
// 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];
|
||||
|
||||
//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);
|
1217
tpws/uthash.h
Normal file
1217
tpws/uthash.h
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user