summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBobby Bingham <koorogi@koorogi.info>2016-01-31 16:29:03 -0600
committerBobby Bingham <koorogi@koorogi.info>2016-01-31 16:29:03 -0600
commit24da062c6f75a3fa229d74359150f653b63d3c04 (patch)
treea92a3e6cea31c18f004ffc028aa689151f5d93a3
initial commit
-rw-r--r--Makefile18
-rw-r--r--latency.c97
-rw-r--r--proxy.c135
-rw-r--r--proxy.h2
4 files changed, 252 insertions, 0 deletions
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..d091e68
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,18 @@
+CFLAGS = -D_POSIX_C_SOURCE=200809L -O2 -Wall
+
+SRCS = $(sort $(wildcard *.c))
+OBJS = $(SRCS:.c=.o)
+
+all: latency
+
+clean:
+ rm -f latency
+ rm -f $(OBJS)
+
+latency: $(OBJS)
+ $(CC) $(CFLAGS) $^ -o $@
+
+%.o: %.c
+ $(CC) $(CFLAGS) -c $< -o $@
+
+.PHONY: all clean
diff --git a/latency.c b/latency.c
new file mode 100644
index 0000000..6181e84
--- /dev/null
+++ b/latency.c
@@ -0,0 +1,97 @@
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/uio.h>
+#include <errno.h>
+#include <netdb.h>
+#include <stdio.h>
+#include <unistd.h>
+
+#include "proxy.h"
+
+static int parse_host(const char *host, const char *port, struct addrinfo **addr)
+{
+ struct addrinfo hints = {
+ .ai_family = AF_UNSPEC,
+ .ai_socktype = SOCK_STREAM,
+ .ai_flags = AI_PASSIVE,
+ };
+
+ int err;
+ if ((err = getaddrinfo(host, port, &hints, addr))) {
+ fprintf(stderr, "getaddrinfo failed: %s\n", gai_strerror(err));
+ return -1;
+ }
+
+ return 0;
+}
+
+static int listen_source(struct addrinfo *source)
+{
+ int fd = -1;
+ for (struct addrinfo *a = source; a; a = a->ai_next) {
+ if ((fd = socket(a->ai_family, a->ai_socktype, a->ai_protocol)) == -1)
+ continue;
+ if (!bind(fd, a->ai_addr, a->ai_addrlen) && !listen(fd, 1))
+ break;
+ close(fd);
+ fd = -1;
+ }
+ return fd;
+}
+
+static int connect_target(struct addrinfo *target)
+{
+ int fd = -1;
+ for (struct addrinfo *a = target; a; a = a->ai_next) {
+ if ((fd = socket(a->ai_family, a->ai_socktype, a->ai_protocol)) == -1)
+ continue;
+ if (connect(fd, a->ai_addr, a->ai_addrlen) != -1)
+ break;
+ close(fd);
+ fd = -1;
+ }
+ return fd;
+}
+
+int main(int argc, char **argv)
+{
+ struct addrinfo *source;
+ struct addrinfo *target;
+
+ if (argc != 3) {
+ fprintf(stderr, "usage: %s targethost targetport\n", argv[0]);
+ return -1;
+ }
+
+ if (parse_host(NULL, "1234", &source))
+ return -1;
+ if (parse_host(argv[1], argv[2], &target))
+ return -1;
+
+ int listenfd = listen_source(source);
+ if (listenfd < 0) {
+ perror("listen_source");
+ return -1;
+ }
+
+ for (;;) {
+ int sourcefd = accept(listenfd, NULL, NULL);
+ if (sourcefd == -1) break;
+
+ int targetfd = connect_target(target);
+ if (targetfd == -1) {
+ perror("connect_target");
+ close(sourcefd);
+ break;
+ }
+
+ proxy(sourcefd, targetfd, 500);
+
+ close(targetfd);
+ close(sourcefd);
+ }
+
+ close(listenfd);
+ return 0;
+}
+
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);
+}
+
diff --git a/proxy.h b/proxy.h
new file mode 100644
index 0000000..7d2fcb1
--- /dev/null
+++ b/proxy.h
@@ -0,0 +1,2 @@
+void proxy(int, int, unsigned);
+