diff options
Diffstat (limited to 'proxy.c')
-rw-r--r-- | proxy.c | 135 |
1 files changed, 135 insertions, 0 deletions
@@ -0,0 +1,135 @@ +#include <errno.h> +#include <poll.h> +#include <signal.h> +#include <stdlib.h> +#include <time.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/wait.h> + +#include "proxy.h" + +#define BUFFER_SIZE (1 << 10) + +struct list { + struct list *next, *prev; +}; + +struct packet { + struct list list; + unsigned long long recvtime; + size_t length; + char data[BUFFER_SIZE]; +}; + +static unsigned long long gettime() +{ + struct timespec time; + clock_gettime(CLOCK_MONOTONIC, &time); + return time.tv_sec * 1000000000ULL + time.tv_nsec; +} + +static int read_packet(int fd, struct list *tail, unsigned long long now) +{ + struct packet *p = malloc(sizeof *p); + if (!p) return -1; + + ssize_t len; + while ((len = read(fd, p->data, BUFFER_SIZE)) == -1 && errno == EINTR) ; + if (len <= 0) { + free(p); + return -1; + } + + p->list.next = tail; + p->list.prev = tail->prev; + p->recvtime = now; + p->length = len; + p->list.next->prev = p->list.prev->next = &p->list; + return 0; +} + +static int write_packet(int fd, struct packet *p) +{ + char *data = p->data; + size_t len = p->length; + + ssize_t r; + while (len && (r = write(fd, data, len)) != -1 && errno != EINTR) { + data += r; + len -= r; + } + + if (r == -1) return -1; + struct list *l = &p->list; + l->prev->next = l->next; + l->next->prev = l->prev; + free(p); + return 0; +} + +static void proxy_one(int readfd, int writefd, unsigned long long delayns) +{ + struct list head = {0}, tail = {0}; + head.next = &tail; + tail.prev = &head; + + struct pollfd fds = { .fd = readfd, .events = POLLIN }; + int timeout = -1; + for (;;) { + while (poll(&fds, 1, timeout) == -1 && errno == EINTR) ; + if (fds.revents & (POLLERR | POLLHUP)) return; + + unsigned long long now = gettime(); + unsigned long long sendtime = now - delayns; + if (fds.revents & POLLIN && read_packet(readfd, &tail, now)) return; + + while (head.next != &tail) { + struct packet *p = (struct packet *)head.next; + timeout = (p->recvtime - sendtime) / 1000000; + if (timeout <= 0) { + if (write_packet(writefd, p)) return; + } else break; + } + if (head.next == &tail) timeout = -1; + } +} + +static pid_t make_child(int fd0, int fd1, unsigned long long delayns) +{ + pid_t child = fork(); + if (!child) { + proxy_one(fd0, fd1, delayns); + exit(0); + } + return child; +} + +static pid_t wait_one(void) +{ + pid_t p; + while ((p = wait(NULL)) == -1 && errno == EINTR) ; + return p; +} + +static void kill_wait(pid_t p) +{ + kill(p, SIGINT); + wait_one(); +} + +void proxy(int fd0, int fd1, unsigned delayms) +{ + unsigned long long delayns = delayms * 1000000ULL; + pid_t pid0, pid1; + + if ((pid0 = make_child(fd0, fd1, delayns)) < 0) return; + if ((pid1 = make_child(fd1, fd0, delayns)) < 0) { + kill_wait(pid0); + return; + } + + pid_t dead = wait_one(); + kill_wait(dead == pid0 ? pid1 : pid0); +} + |