summaryrefslogtreecommitdiff
path: root/src/client.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/client.c')
-rw-r--r--src/client.c169
1 files changed, 99 insertions, 70 deletions
diff --git a/src/client.c b/src/client.c
index 23d5eac..380bf42 100644
--- a/src/client.c
+++ b/src/client.c
@@ -20,6 +20,8 @@
#define PORTS_IN_INT (sizeof(int) * CHAR_BIT)
+#define IN_ADDR_PORT(addr) (((struct sockaddr_in *)addr)->sin_port)
+
struct c_data {
const char *r_host;
const char *r_port;
@@ -29,7 +31,6 @@ struct c_data {
int s_sock;
int i_sock;
socklen_t s_addrlen;
- uint16_t csum_p;
};
struct o_c_rsock {
@@ -61,8 +62,9 @@ struct o_c_sock {
static struct c_data *global_c_data;
-static const uint8_t tcp_syn_retry_timeouts[] = { 3, 6, 12, 24, 0 };
+static const int8_t tcp_syn_retry_timeouts[] = { 0, 3, 6, 12, 24, -1 };
+/* check if a port offset is set in a int */
static inline int check_resv_poff(unsigned int *used_ports, uint16_t poff) {
if (used_ports[poff / PORTS_IN_INT] & (1 << (poff % PORTS_IN_INT)))
return 0;
@@ -70,25 +72,23 @@ static inline int check_resv_poff(unsigned int *used_ports, uint16_t poff) {
return poff;
}
-/* reserve a local TCP port (local addr, remote addr, remote port are usually
- * fixed in the tuple) */
+/* reserve a local TCP port */
static inline uint16_t reserve_port(unsigned int *used_ports) {
long r;
- // randomly try 16 places
+ // randomly try some places, hope this will give us reasonably uniform distribution
for (int i = 1; i <= 16; i++) {
r = random();
- if (check_resv_poff(used_ports, r % 32768))
- return 32768 + (r % 32768);
-
- if (check_resv_poff(used_ports, (r >> 16) % 32768))
- return 32768 + ((r >> 16) % 32768);
+ do {
+ if (check_resv_poff(used_ports, r % 32768))
+ return 32768 + (r % 32768);
+ } while (r >>= 16);
}
// give up and go sequentially
- uint16_t ioff, spoff = (r >> 16) + 1;
+ uint16_t ioff, spoff = random();
size_t moff, smoff = spoff / PORTS_IN_INT;
/* two step process:
@@ -122,15 +122,21 @@ 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);
}
+/* prepare server address in TCP header format */
+static void c_prep_s_addr(struct o_c_sock *sock, struct tcphdr *hdr) {
+ hdr->th_sport = sock->l_port;
+ hdr->th_dport = IN_ADDR_PORT(sock->rsock->r_addr);
+ hdr->th_seq = htonl(sock->seq_num);
+ hdr->th_off = 5;
+}
+
+/* clean up a socket, don't bother freeing anything if the program is stopping */
static void c_sock_cleanup(EV_P_ struct o_c_sock *sock, int stopping) {
if (sock->status != TCP_SYN_SENT) {
struct tcphdr buf = {
- .th_sport = sock->l_port,
- .th_dport = ((struct sockaddr_in *)sock->rsock->r_addr)->sin_port,
- .th_seq = htonl(sock->seq_num),
- .th_off = 5,
.th_flags = sock->status == TCP_ESTABLISHED ? TH_FIN : TH_RST
};
+ c_prep_s_addr(sock, &buf);
ssize_t sz = send(sock->rsock->fd, &buf, sizeof(buf), 0);
if (sz < 0) {
@@ -170,24 +176,49 @@ static void c_tm_cb(EV_P_ ev_timer *w, int revents __attribute__((unused))) {
c_sock_cleanup(EV_A_ w->data, 0);
}
+static int c_send_syn(struct o_c_sock *sock) {
+ struct tcphdr buf = {
+ .th_flags = TH_SYN
+ };
+ c_prep_s_addr(sock, &buf);
+
+ uint16_t tsz = htons(sizeof(buf));
+ buf.th_sum = ~csum_partial(&buf.th_seq, 16, csum_partial(&tsz, sizeof(tsz), sock->csum_p));
+
+ DBG("sending SYN to remote");
+ ssize_t sz = send(sock->rsock->fd, &buf, sizeof(buf), 0);
+ if (sz < 0) {
+ perror("send");
+ return 0;
+ } 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);
+ }
+
+ return 1;
+}
+
static int c_adv_syn_tm(EV_P_ struct o_c_sock *sock) {
- uint8_t next_retr = tcp_syn_retry_timeouts[sock->syn_retries++];
+ int8_t next_retr = tcp_syn_retry_timeouts[sock->syn_retries++];
+
+ if (next_retr < 0 || !c_send_syn(sock))
+ return 0;
+
if (next_retr) {
ev_timer_set(&sock->tm_w, next_retr, 0.);
ev_timer_start(EV_A_ &sock->tm_w);
}
- return !!next_retr;
+
+ return 1;
}
static void c_syn_tm_cb(EV_P_ ev_timer *w, int revents __attribute__((unused))) {
- if (c_adv_syn_tm(EV_A_ w->data)) {
- // resend SYN
- } else {
+ if (!c_adv_syn_tm(EV_A_ w->data)) {
DBG("connection timed out");
c_sock_cleanup(EV_A_ w->data, 0);
}
}
+/* client raw socket callback */
static void cc_cb(struct ev_loop *loop, ev_io *w, int revents __attribute__((unused))) {
DBG("-- entering cc_cb --");
@@ -222,14 +253,14 @@ static void cc_cb(struct ev_loop *loop, ev_io *w, int revents __attribute__((unu
sock->status = TCP_ESTABLISHED;
struct tcphdr shdr = {
- .th_sport = sock->l_port,
- .th_dport = ((struct sockaddr_in *)sock->rsock->r_addr)->sin_port,
- .th_seq = htonl(sock->seq_num),
.th_ack = rhdr->th_seq,
.th_win = 65535,
- .th_flags = TH_ACK,
- .th_off = 5
+ .th_flags = TH_ACK
};
+ c_prep_s_addr(sock, &shdr);
+
+ uint16_t tsz = htons(sizeof(shdr) + sock->pending_data_size);
+ shdr.th_sum = ~csum_partial(sock->pending_data, sock->pending_data_size, csum_partial(&shdr.th_seq, 16, csum_partial(&tsz, sizeof(tsz), sock->csum_p)));
sock->seq_num += sock->pending_data_size;
@@ -267,26 +298,35 @@ static void cc_cb(struct ev_loop *loop, ev_io *w, int revents __attribute__((unu
ev_timer_start(EV_A_ &sock->tm_w);
}
- should_ssz = rsz - 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 + rhdr->th_off * 32 / CHAR_BIT, should_ssz, 0, sock->c_address, rsock->c_data->s_addrlen);
+ if (rhdr->th_flags & ~(TH_PUSH | TH_ACK)) {
+ DBG("packet has strange flags, dropping");
+ return;
+ }
- 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);
+ if (sock->status == TCP_ESTABLISHED) {
+ should_ssz = rsz - 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 + 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);
+ }
}
}
}
+
if (errno != EAGAIN) {
perror("recvfrom");
ev_break(EV_A_ EVBREAK_ONE);
}
}
+/* initialize new raw socket */
static inline struct o_c_rsock * c_rsock_init(struct addrinfo *res) {
struct o_c_rsock *rsock;
rsock = malloc(sizeof(*rsock));
@@ -324,28 +364,33 @@ static inline struct o_c_rsock * c_rsock_init(struct addrinfo *res) {
char proto[] = { 0, IPPROTO_TCP };
- if (((struct sockaddr *)rsock->r_addr)->sa_family != our_addr.ss_family)
+ if (rsock->r_addr->sa_family != our_addr.ss_family)
abort();
+ size_t addr_offset, addr_size;
+
switch (our_addr.ss_family) {
case AF_INET:
- rsock->csum_p = csum_partial(&((struct sockaddr_in *)&our_addr)->sin_addr, sizeof(in_addr_t),
- csum_partial(&((struct sockaddr_in *)rsock->r_addr)->sin_addr, sizeof(in_addr_t), 0));
+ addr_offset = offsetof(struct sockaddr_in, sin_addr);
+ addr_size = sizeof(in_addr_t);
break;
case AF_INET6:
- rsock->csum_p = csum_partial(&((struct sockaddr_in6 *)&our_addr)->sin6_addr, sizeof(struct in6_addr),
- csum_partial(&((struct sockaddr_in6 *)rsock->r_addr)->sin6_addr, sizeof(struct in6_addr), 0));
+ addr_offset = offsetof(struct sockaddr_in6, sin6_addr);
+ addr_size = sizeof(struct in6_addr);
break;
default:
abort();
}
- rsock->csum_p = csum_partial(&((struct sockaddr_in *)rsock->r_addr)->sin_port, sizeof(in_port_t),
- csum_partial(proto, sizeof(proto), rsock->csum_p));
+ rsock->csum_p = csum_partial(&IN_ADDR_PORT(rsock->r_addr), sizeof(in_port_t),
+ csum_partial(proto, sizeof(proto),
+ csum_partial((char *)&our_addr + addr_offset, addr_size,
+ csum_partial((char *)rsock->r_addr + addr_offset, addr_size, 0))));
return rsock;
}
+/* client UDP socket callback */
static void cs_cb(EV_P_ ev_io *w, int revents __attribute__((unused))) {
DBG("-- entering cs_cb --");
struct c_data *c_data = w->data;
@@ -367,7 +412,7 @@ static void cs_cb(EV_P_ ev_io *w, int revents __attribute__((unused))) {
sock = calloc(1, sizeof(*sock));
struct addrinfo *res;
- DBG("looking up %s:%s", c_data->r_host, c_data->r_port);
+ 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) {
@@ -414,34 +459,13 @@ static void cs_cb(EV_P_ ev_io *w, int revents __attribute__((unused))) {
sock->seq_num = random();
- struct tcphdr buf = {
- .th_sport = sock->l_port,
- .th_dport = ((struct sockaddr_in *)sock->rsock->r_addr)->sin_port,
- .th_seq = htonl(sock->seq_num),
- .th_flags = TH_SYN,
- .th_off = 5,
- };
- uint16_t tsz = htons(sizeof(buf));
- buf.th_sum = ~csum_partial(&buf.th_seq, 16, csum_partial(&tsz, sizeof(tsz), sock->csum_p));
-
sock->pending_data = malloc(sz);
memcpy(sock->pending_data, rbuf, sz);
sock->pending_data_size = sz;
- DBG("sending SYN to remote");
- sz = send(sock->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_syn_tm_cb, 0., tcp_syn_retry_timeouts[0]);
+ ev_init(&sock->tm_w, c_syn_tm_cb);
sock->tm_w.data = sock;
+
sock->syn_retries = 0;
c_adv_syn_tm(EV_A_ sock);
@@ -451,13 +475,13 @@ static void cs_cb(EV_P_ ev_io *w, int revents __attribute__((unused))) {
}
struct tcphdr tcp_hdr = {
- .th_sport = sock->l_port,
- .th_dport = ((struct sockaddr_in *)sock->rsock->r_addr)->sin_port,
- .th_seq = htonl(sock->seq_num),
- .th_off = 5,
.th_win = 65535,
.th_flags = TH_PUSH
};
+ c_prep_s_addr(sock, &tcp_hdr);
+
+ uint16_t tsz = htons(sizeof(tcp_hdr) + sz);
+ tcp_hdr.th_sum = ~csum_partial(rbuf, sz, csum_partial(&tcp_hdr.th_seq, 16, csum_partial(&tsz, sizeof(tsz), sock->csum_p)));
sock->seq_num += sz;
@@ -477,6 +501,10 @@ static void cs_cb(EV_P_ ev_io *w, int revents __attribute__((unused))) {
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) {
+ if (errno == ENOBUFS) {
+ fprintf(stderr, "sendmsg: out of buffer space\n");
+ return;
+ }
perror("sendmsg");
ev_break(EV_A_ EVBREAK_ONE);
return;
@@ -491,6 +519,7 @@ static void cs_cb(EV_P_ ev_io *w, int revents __attribute__((unused))) {
}
}
+/* atexit cleanup */
static void c_cleanup() {
if (!global_c_data)
return;