diff options
Diffstat (limited to 'src/client.c')
-rw-r--r-- | src/client.c | 460 |
1 files changed, 460 insertions, 0 deletions
diff --git a/src/client.c b/src/client.c new file mode 100644 index 0000000..00f7a89 --- /dev/null +++ b/src/client.c @@ -0,0 +1,460 @@ +#include <ev.h> +#include <limits.h> +#include <netdb.h> +#include <netinet/in.h> +#include <netinet/tcp.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/socket.h> +#include <sys/types.h> +#include <sys/uio.h> +#include <unistd.h> + +#ifdef CRYPTO +#include <sodium/core.h> +#include <sodium/crypto_secretbox.h> +#include <sodium/randombytes.h> +#endif + +#include "common.h" +#include "client.h" +#include "uthash.h" + +struct o_c_rsock { + struct sockaddr *r_addr; + struct o_c_sock *o_socks_by_lport; + struct c_data *c_data; + ev_io io_w; + ev_timer tm_w; + UT_hash_handle hh; + int fd; + socklen_t r_addrlen; +}; + +struct o_c_sock { + struct sockaddr *c_address; + struct o_c_rsock *rsock; + char *pending_data; + size_t pending_data_size; + ev_timer tm_w; + UT_hash_handle hh_lp; + UT_hash_handle hh_ca; + uint16_t seq_num; + in_port_t l_port; + in_port_t r_port; + uint8_t status; +}; + +#define PORTS_IN_INT sizeof(int) * CHAR_BIT + +struct c_data { + const char *r_host; + const char *r_port; + struct o_c_sock *o_socks_by_caddr; + struct o_c_rsock *o_rsocks; + struct sockaddr_storage pkt_addr; + socklen_t s_addrlen; + unsigned int used_ports[32768 / PORTS_IN_INT]; + int s_sock; +}; + +static struct c_data *global_c_data; + +/* reserve a local TCP port (local addr, remote addr, remote port are usually + * fixed in the tuple) */ +static uint16_t reserve_port(unsigned int *used_ports) { + // pick a starting port for the search + uint16_t spoff = random() % 32768; + size_t smoff = spoff / PORTS_IN_INT; + unsigned int ioff; + size_t moff; + + /* two step process: + * +-----------------------------+-----------------------+ + * | 32768 32769 32770 32771 ... | 32800 32801 32802 ... | + * +-----------------------------+-----------------------+ + * 1. ^^^^^ ^^^^^ ... + * 2. ffs: ^^^^^ ^^^^^ ^^^^^ ... + */ + + // do the rest of the integer + for (ioff = spoff % PORTS_IN_INT; ioff <= PORTS_IN_INT; ioff++) { + if (used_ports[smoff] & (1 << ioff)) { + used_ports[smoff] |= 1 << ioff; + return 32768 + spoff + ioff; + } + } + + // go one integer at a time + for (moff = smoff + 1; moff != smoff; moff++) { + if ((ioff = ffs(~used_ports[moff]))) { + used_ports[moff] |= 1 << (ioff - 1); + return 32768 + smoff * PORTS_IN_INT + (ioff - 1); + } + } + + return 0; +} + +static void free_port(unsigned int *used_ports, uint16_t port_num) { + used_ports[port_num / PORTS_IN_INT] ^= 1 << (port_num % PORTS_IN_INT); +} + +static void c_tm_cb(EV_P_ ev_timer *w, int revents __attribute__((unused))) { + struct o_c_sock *sock = w->data; + DBG("timing out socket @ %p", sock); + struct tcphdr buf = { + .th_sport = sock->l_port, + .th_dport = sock->r_port, + .th_seq = htonl(sock->seq_num), + .th_off = 5, + .th_flags = TH_FIN + }; + ssize_t sz = send(sock->rsock->c_data->s_sock, &buf, sizeof(buf), 0); + if (sz < 0) { + perror("send"); + ev_break(EV_A_ EVBREAK_ONE); + return; + } else if ((size_t)sz != sizeof(buf)) { + fprintf(stderr, "send %s our packet: tried %lu, sent %zd\n", (size_t)sz > sizeof(buf) ? "expanded" : "truncated", sizeof(buf), sz); + } + free_port(sock->rsock->c_data->used_ports, sock->l_port); + ev_timer_stop(EV_A_ w); + + HASH_DELETE(hh_lp, sock->rsock->o_socks_by_lport, sock); + + if (!sock->rsock->o_socks_by_lport) { + close(sock->rsock->fd); + + ev_io_stop(EV_A_ &sock->rsock->io_w); + ev_timer_stop(EV_A_ &sock->rsock->tm_w); + + HASH_DEL(sock->rsock->c_data->o_rsocks, sock->rsock); + + free(sock->rsock->r_addr); + free(sock->rsock); + } + + free(sock); +} + +static void cc_cb(struct ev_loop *loop __attribute__((unused)), ev_io *w, int revents __attribute__((unused))) { + DBG("-- entering cc_cb --"); + + struct o_c_rsock *rsock = w->data; + char rbuf[65536]; + socklen_t pkt_addrlen = sizeof(struct sockaddr_in6); + ssize_t should_ssz, rsz, ssz; + + if ((rsz = recvfrom(w->fd, rbuf, sizeof(rbuf), 0, (struct sockaddr *)&rsock->c_data->pkt_addr, &pkt_addrlen)) == -1) { + perror("recvfrom"); + ev_break(EV_A_ EVBREAK_ONE); + return; + } + + DBG("received %zd raw bytes on client", rsz); + + if (pkt_addrlen > sizeof(struct sockaddr_in6)) + abort(); + + if ((size_t)rsz < sizeof(struct tcphdr)) + return; + + struct tcphdr *rhdr = (struct tcphdr *)rbuf; + + struct o_c_sock *sock; + + HASH_FIND(hh_lp, rsock->o_socks_by_lport, &rhdr->th_dport, sizeof(in_port_t), sock); + + if (!sock) { + DBG("could not find conn with lport %hu", ntohs(rhdr->th_dport)); + return; + } + + if (sock->status == TCP_SYN_SENT && rhdr->th_flags == (TH_SYN | TH_ACK)) { + DBG("SYN/ACK received, connection established"); + + sock->status = TCP_ESTABLISHED; + + struct tcphdr shdr = { + .th_sport = sock->l_port, + .th_dport = sock->r_port, + .th_seq = htonl(sock->seq_num), + .th_ack = rhdr->th_seq, + .th_win = 65535, + .th_flags = TH_ACK, + .th_off = 5 + }; + + sock->seq_num += sock->pending_data_size; + + struct iovec iovs[2] = { + { .iov_base = &shdr, .iov_len = sizeof(shdr) }, + { .iov_base = sock->pending_data, .iov_len = sock->pending_data_size } + }; + + struct msghdr msghdr = { + .msg_name = NULL, + .msg_namelen = 0, + .msg_iov = iovs, + .msg_iovlen = sizeof(iovs) / sizeof(iovs[0]) + }; + + should_ssz = sizeof(shdr) + sock->pending_data_size; + ssz = sendmsg(rsock->fd, &msghdr, 0); + + if (ssz < 0) { + perror("sendmsg"); + ev_break(EV_A_ EVBREAK_ONE); + return; + } else if ((size_t)ssz != should_ssz) { + fprintf(stderr, "sendmsg %s our packet: tried %lu, sent %zd\n", (size_t)ssz > should_ssz ? "expanded" : "truncated", should_ssz, ssz); + } + + free(sock->pending_data); + } + + should_ssz = rsz - ntohs(rhdr->th_off) * 32 / CHAR_BIT; + if (should_ssz > 0) { + DBG("sending %zd bytes to client", should_ssz); + ssz = sendto(rsock->c_data->s_sock, rbuf + ntohs(rhdr->th_off) * 32 / CHAR_BIT, should_ssz, 0, sock->c_address, rsock->c_data->s_addrlen); + + if (ssz < 0) { + perror("sendto"); + ev_break(EV_A_ EVBREAK_ONE); + return; + } else if ((size_t)ssz != should_ssz) { + fprintf(stderr, "sendto %s our packet: tried %lu, sent %zd\n", (size_t)ssz > should_ssz ? "expanded" : "truncated", should_ssz, ssz); + } + } +} + +static void cs_cb(EV_P_ ev_io *w, int revents __attribute__((unused))) { + DBG("-- entering cs_cb --"); + struct c_data *c_data = w->data; + socklen_t addresslen = c_data->s_addrlen; + ssize_t sz; + char rbuf[65536]; + + if ((sz = recvfrom(w->fd, rbuf, sizeof(rbuf), 0, (struct sockaddr *)&c_data->pkt_addr, &addresslen)) == -1) { + perror("recvfrom"); + ev_break(EV_A_ EVBREAK_ONE); + return; + } + + DBG("received %zd bytes on server", sz); + + if (addresslen != c_data->s_addrlen) + abort(); + + struct o_c_sock *sock; + HASH_FIND(hh_ca, c_data->o_socks_by_caddr, &c_data->pkt_addr, addresslen, sock); + + if (!sock) { + DBG("could not locate matching socket for client, initializing new connection"); + sock = calloc(1, sizeof(*sock)); + + uint16_t l_port = reserve_port(c_data->used_ports); + DBG("using port %hu", l_port); + if (!l_port) { + fputs("we ran out of ports?\n", stderr); + ev_break(EV_A_ EVBREAK_ONE); + return; + } + sock->l_port = htons(l_port); + + struct addrinfo *res; + DBG("looking up %s:%s", c_data->r_host, c_data->r_port); + // TODO: make this asynchronous + int r = getaddrinfo(c_data->r_host, c_data->r_port, NULL, &res); + if (r) { + fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(r)); + ev_break(EV_A_ EVBREAK_ONE); + return; + } + + sock->c_address = malloc(addresslen); + memcpy(sock->c_address, &c_data->pkt_addr, addresslen); + + struct o_c_rsock *rsock; + + HASH_FIND(hh, c_data->o_rsocks, res->ai_addr, res->ai_addrlen, rsock); + + if (!rsock) { + DBG("could not locate remote socket to host, initializing new raw socket"); + rsock = malloc(sizeof(*rsock)); + rsock->r_addr = malloc(res->ai_addrlen); + + memcpy(rsock->r_addr, res->ai_addr, res->ai_addrlen); + rsock->r_addrlen = res->ai_addrlen; + freeaddrinfo(res); + rsock->o_socks_by_lport = NULL; + rsock->c_data = c_data; + + rsock->fd = socket(rsock->r_addr->sa_family, SOCK_RAW, IPPROTO_TCP); + if (!rsock->fd) { + perror("socket"); + ev_break(EV_A_ EVBREAK_ONE); + return; + } + + if (connect(rsock->fd, rsock->r_addr, rsock->r_addrlen) == -1) { + perror("connect"); + ev_break(EV_A_ EVBREAK_ONE); + return; + } + + ev_io_init(&rsock->io_w, cc_cb, rsock->fd, EV_READ); + rsock->io_w.data = rsock; + ev_io_start(EV_A_ &rsock->io_w); + + HASH_ADD_KEYPTR(hh, c_data->o_rsocks, rsock->r_addr, rsock->r_addrlen, rsock); + } + + sock->r_port = ((struct sockaddr_in *)rsock->r_addr)->sin_port; + + HASH_ADD_KEYPTR(hh_ca, c_data->o_socks_by_caddr, sock->c_address, addresslen, sock); + HASH_ADD(hh_lp, rsock->o_socks_by_lport, l_port, sizeof(in_port_t), sock); + + sock->rsock = rsock; + + sock->seq_num = random(); + + struct tcphdr buf = { + .th_sport = sock->l_port, + .th_dport = sock->r_port, + .th_seq = htonl(sock->seq_num++), + .th_flags = TH_SYN, + .th_off = 5 + }; + + sock->pending_data = malloc(sz); + memcpy(sock->pending_data, rbuf, sz); + sock->pending_data_size = sz; + + DBG("sending SYN to remote"); + sz = send(rsock->fd, &buf, sizeof(buf), 0); + if (sz < 0) { + perror("send"); + ev_break(EV_A_ EVBREAK_ONE); + return; + } else if ((size_t)sz != sizeof(buf)) { + fprintf(stderr, "send %s our packet: tried %lu, sent %zd\n", (size_t)sz > sizeof(buf) ? "expanded" : "truncated", sizeof(buf), sz); + } + + // resend SYN + + //ev_timer_init(&sock->tm_w, c_tm_cb, 0., 60. * 60. * 3.); + //sock->tm_w.data = sock; + //ev_timer_start(EV_A_ &sock->tm_w); + + sock->status = TCP_SYN_SENT; + + return; + } + + struct tcphdr tcp_hdr = { + .th_sport = sock->l_port, + .th_dport = sock->r_port, + .th_seq = htonl(sock->seq_num), + .th_off = 5, + .th_win = 65535, + .th_flags = TH_PUSH + }; + + sock->seq_num += sz; + + struct iovec iovs[2] = { + { .iov_base = &tcp_hdr, .iov_len = sizeof(tcp_hdr) }, + { .iov_base = rbuf, .iov_len = sz } + }; + + struct msghdr msghdr = { + .msg_name = NULL, + .msg_namelen = 0, + .msg_iov = iovs, + .msg_iovlen = sizeof(iovs) / sizeof(iovs[0]) + }; + + size_t should_send_size = sizeof(tcp_hdr) + sz; + DBG("sending %zd raw bytes containing %zd bytes payload to remote", should_send_size, sz); + sz = sendmsg(sock->rsock->fd, &msghdr, 0); + if (sz < 0) { + perror("sendmsg"); + ev_break(EV_A_ EVBREAK_ONE); + return; + } else if ((size_t)sz != should_send_size) { + fprintf(stderr, "sendmsg %s our packet: tried %lu, sent %zd\n", (size_t)sz > should_send_size ? "expanded" : "truncated", should_send_size, sz); + } + ev_timer_again(EV_A_ &sock->tm_w); +} + +static void c_cleanup() { + if (!global_c_data) + return; + + DBG("cleaning up"); + struct o_c_sock *sock; + for (sock = global_c_data->o_socks_by_caddr; sock != NULL; sock = sock->hh_ca.next) { + switch (sock->status) { + case TCP_ESTABLISHED: + // send TCP FIN + break; + case TCP_CLOSE: + break; + default: + ; + // send TCP RST + } + // don't bother freeing anything because we're about to exit anyways + } + + global_c_data = NULL; +} + +int start_client(const char *s_host, const char *s_port, const char *r_host, const char *r_port) { + struct addrinfo *res; + int r = getaddrinfo(s_host, s_port, NULL, &res); + if (r) { + fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(r)); + return 3; + } + + struct c_data c_data = { 0 }; + + c_data.s_sock = socket(res->ai_family, SOCK_DGRAM, 0); + if (c_data.s_sock == -1) { + perror("socket"); + return 1; + } + + if (bind(c_data.s_sock, res->ai_addr, res->ai_addrlen) == -1) { + perror("bind"); + return 2; + } + + c_data.s_addrlen = res->ai_addrlen; + c_data.r_host = r_host; + c_data.r_port = r_port; + + freeaddrinfo(res); + + global_c_data = &c_data; + atexit(c_cleanup); + + struct ev_loop *loop = EV_DEFAULT; + ev_io s_watcher; + + s_watcher.data = &c_data; + + ev_io_init(&s_watcher, cs_cb, c_data.s_sock, EV_READ); + ev_io_start(loop, &s_watcher); + + DBG("initialization complete, starting event loop"); + r = ev_run(loop, 0); + + c_cleanup(); + return r; +} |