summaryrefslogtreecommitdiff
path: root/proxy.c
diff options
context:
space:
mode:
Diffstat (limited to 'proxy.c')
-rw-r--r--proxy.c135
1 files changed, 135 insertions, 0 deletions
diff --git a/proxy.c b/proxy.c
new file mode 100644
index 0000000..04ac9f2
--- /dev/null
+++ b/proxy.c
@@ -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);
+}
+