From 8d27febc7d9f6cdb97215e6a06438931ef5b323c Mon Sep 17 00:00:00 2001
From: Erik Ekman <yarrick@kryo.se>
Date: Thu, 11 Dec 2008 22:39:06 +0000
Subject: [PATCH] Implemented new protocol for upstream data

---
 doc/proto_00000403.txt |  29 ++++----
 src/encoding.c         |   4 +-
 src/iodine.c           |  52 ++++++++------
 src/iodined.c          | 151 +++++++++++++++++++++++++++++------------
 src/user.c             |   2 +
 src/user.h             |   4 +-
 6 files changed, 161 insertions(+), 81 deletions(-)

diff --git a/doc/proto_00000403.txt b/doc/proto_00000403.txt
index c392079..cf05d94 100644
--- a/doc/proto_00000403.txt
+++ b/doc/proto_00000403.txt
@@ -38,8 +38,8 @@ Server replies:
 Switch codec:
 Client sends:
 	First byte s or S
-	One byte ASCII digit, meaning userid
-	One byte ASCII digit, with value 5 or 6, representing number of raw
+	5 bits coded as Base32 char, meaning userid
+	5 bits coded as Base32 char, with value 5 or 6, representing number of raw
 	bits per encoded byte
 Server sends:
 	Name of codec if accepted. After this all upstream data packets must 
@@ -47,28 +47,28 @@ Server sends:
 	BADCODEC if not accepted. Client must then revert to Base32
 
 Data:
-Upstream data header (encoded as 4 bytes Base32):
-	 4321 0 432 10 43 210 4321 0
-	+----+-+---+--+--+---+----+-+
-	|UUUU|L|SSS|FF|FF|DDD|GGGG|C|
-	+----+-+---+--+--+---+----+-+
+Upstream data header:
+	 3210 432 10 43 210 4321 0
+	+----+---+--+--+---+----+-+
+	|UUUU|SSS|FF|FF|DDD|GGGG|L|
+	+----+---+--+--+---+----+-+
 
 Downstream data header:
 	 7 654 3210 765 4321 0
 	+-+---+----+---+----+-+
-	|L|SSS|FFFF|DDD|GGGG|C|
+	|C|SSS|FFFF|DDD|GGGG|L|
 	+-+---+----+---+----+-+
 
 UUUU = Userid
 L = Last fragment in packet flag
-SSS = Upstream packet sequence number
+SS = Upstream packet sequence number
 FFFF = Upstream fragment number
 DDD = Downstream packet sequence number
 GGGG = Downstream fragment number
-C = Compression enabled for this packet
+C = Compression enabled for downstream packet
 
-Upstream data packet starts with 4 bytes Base32 encoded header, then comes 
-the payload data, encoded with chosen codec.
+Upstream data packet starts with 1 byte ASCII hex coded user byte, then 3 bytes 
+Base32 encoded header, then comes the payload data, encoded with chosen codec.
 
 Downstream data starts with 2 byte header. Then payload data, which may be
 compressed.
@@ -77,7 +77,10 @@ Ping:
 Client sends:
 	First byte p or P
 	Rest encoded with Base32:
-	1 byte userid
+	1 byte with 4 bits userid
+	1 byte with:
+		3 bits downstream seqno
+		4 bits downstream fragment
 	CMC
 
 The server response to Ping and Data packets is a DNS NULL type response:
diff --git a/src/encoding.c b/src/encoding.c
index d581587..c3c94fb 100644
--- a/src/encoding.c
+++ b/src/encoding.c
@@ -35,7 +35,7 @@ inline_dotify(char *buf, size_t buflen)
 	char *reader, *writer;
 
 	total = strlen(buf);
-	dots = total / 62;
+	dots = total / 57;
 
 	writer = buf;
 	writer += total;
@@ -52,7 +52,7 @@ inline_dotify(char *buf, size_t buflen)
 	pos = (unsigned) (reader - buf) + 1;
 
 	while (dots) {
-		if (pos % 62 == 0) {
+		if (pos % 57 == 0) {
 			*writer-- = '.';
 			dots--;
 		}
diff --git a/src/iodine.c b/src/iodine.c
index 6905f26..ac52e04 100644
--- a/src/iodine.c
+++ b/src/iodine.c
@@ -124,10 +124,9 @@ build_hostname(char *buf, size_t buflen,
 	size_t space;
 	char *b;
 
-
-	space = MIN(0xFF, buflen) - strlen(topdomain) - 2;
+	space = MIN(0xFF, buflen) - strlen(topdomain) - 5;
 	if (!encoder->places_dots())
-		space -= (space / 62); /* space for dots */
+		space -= (space / 57); /* space for dots */
 
 	memset(buf, 0, buflen);
 	
@@ -209,6 +208,8 @@ tunnel_tun(int tun_fd, int dns_fd)
 	packet.sentlen = 0;
 	packet.offset = 0;
 	packet.len = outlen;
+	packet.seqno++;
+	packet.fragment = 0;
 
 	send_chunk(dns_fd);
 
@@ -254,9 +255,11 @@ tunnel(int tun_fd, int dns_fd)
 		tv.tv_sec = 1;
 		tv.tv_usec = 0;
 
+
 		FD_ZERO(&fds);
-		if (!is_sending()) 
+		if (!is_sending()) {
 			FD_SET(tun_fd, &fds);
+		}
 		FD_SET(dns_fd, &fds);
 
 		i = select(MAX(tun_fd, dns_fd) + 1, &fds, NULL, NULL, &tv);
@@ -297,15 +300,21 @@ send_chunk(int fd)
 	p += packet.offset;
 	avail = packet.len - packet.offset;
 
-	packet.sentlen = build_hostname(buf + 1, sizeof(buf) - 1, p, avail, topdomain, dataenc);
+	packet.sentlen = build_hostname(buf + 4, sizeof(buf) - 4, p, avail, topdomain, dataenc);
+	packet.fragment++;
 
-	if (packet.sentlen == avail)
-		code = 1;
-	else
-		code = 0;
-		
-	code |= (userid << 1);
-	buf[0] = hex[code];
+	/* Build upstream data header (see doc/proto_xxxxxxxx.txt) */
+
+	buf[0] = hex[userid & 15]; /* First byte is 4 bits userid */
+
+	code = ((packet.seqno & 7) << 2) | ((packet.fragment & 15) >> 2);
+	buf[1] = b32_5to8(code); /* Second byte is 3 bits seqno, 2 upper bits fragment count */
+
+	code = ((packet.fragment & 3) << 3) | (0);
+	buf[2] = b32_5to8(code); /* Third byte is 2 bits lower fragment count, 3 bits downstream packet seqno */
+
+	code = (0 << 1) | (packet.sentlen == avail);
+	buf[3] = b32_5to8(code); /* Fourth byte is 4 bits downstream fragment count, 1 bit compression flag */
 
 	send_query(fd, buf);
 }
@@ -330,7 +339,7 @@ send_login(int fd, char *login, int len)
 static void
 send_ping(int fd)
 {
-	char data[3];
+	char data[4];
 	
 	if (is_sending()) {
 		packet.sentlen = 0;
@@ -339,8 +348,9 @@ send_ping(int fd)
 	}
 
 	data[0] = userid;
-	data[1] = (rand_seed >> 8) & 0xff;
-	data[2] = (rand_seed >> 0) & 0xff;
+	data[1] = 0;
+	data[2] = (rand_seed >> 8) & 0xff;
+	data[3] = (rand_seed >> 0) & 0xff;
 	
 	rand_seed++;
 
@@ -380,13 +390,9 @@ send_case_check(int fd)
 static void
 send_codec_switch(int fd, int userid, int bits)
 {
-	char buf[512] = "S00.";
-	if (userid >= 0 && userid < 9) {
-		buf[1] += userid;
-	}
-	if (bits >= 0 && bits < 9) {
-		buf[2] += bits;
-	}
+	char buf[512] = "S__.";
+	buf[1] = b32_5to8(userid);
+	buf[2] = b32_5to8(bits);
 	
 	strncat(buf, topdomain, 512 - strlen(buf));
 	send_query(fd, buf);
@@ -705,6 +711,8 @@ main(int argc, char **argv)
 	device = NULL;
 	chunkid = 0;
 
+	packet.seqno = 0;
+
 	b32 = get_base32_encoder();
 	dataenc = get_base32_encoder();
 	
diff --git a/src/iodined.c b/src/iodined.c
index 3ddb752..2750877 100644
--- a/src/iodined.c
+++ b/src/iodined.c
@@ -151,6 +151,33 @@ send_version_response(int fd, version_ack_t ack, uint32_t payload, int userid, s
 	write_dns(fd, q, out, sizeof(out));
 }
 
+static void
+update_downstream_seqno(int dns_fd, int userid, int down_seq, int down_frag)
+{
+	/* update outgoing seqno/frag */
+	if (down_seq != users[userid].out_acked_seqno) {
+		/* First ack on new outgoing packet */
+		users[userid].out_acked_seqno = down_seq;
+		users[userid].out_acked_fragment = down_frag;
+	} else {
+		if (down_frag > users[userid].out_acked_fragment) {
+			/* Ack on later fragment */
+			users[userid].out_acked_fragment = down_frag;
+		}
+	}
+
+	/* Send reply if waiting */
+	if (users[userid].outpacket.len > 0) {
+		if (debug >= 1) {
+			printf("OUT pkt seq# %d, frag %d (last=%d), fragsize %d of total %d, to user %d\n",
+				0, 0, 1, users[userid].outpacket.len, users[userid].outpacket.len, userid);
+		}
+		write_dns(dns_fd, &users[userid].q, users[userid].outpacket.data, users[userid].outpacket.len);
+		users[userid].outpacket.len = 0;
+		users[userid].q.id = 0;
+	}
+}
+
 static void
 handle_null_request(int tun_fd, int dns_fd, struct query *q, int domain_len)
 {
@@ -240,16 +267,6 @@ handle_null_request(int tun_fd, int dns_fd, struct query *q, int domain_len)
 			}
 		}
 		return;
-	} else if(in[0] == 'P' || in[0] == 'p') {
-		read = unpack_data(unpacked, sizeof(unpacked), &(in[1]), domain_len - 1, b32);
-		/* Ping packet, store userid */
-		userid = unpacked[0];
-		if (userid < 0 || userid >= USERS || ip_cmp(userid, q) != 0) {
-			write_dns(dns_fd, q, "BADIP", 5);
-			return; /* illegal id */
-		}
-		memcpy(&(users[userid].q), q, sizeof(struct query));
-		users[userid].last_pkt = time(NULL);
 	} else if(in[0] == 'Z' || in[0] == 'z') {
 		/* Check for case conservation and chars not allowed according to RFC */
 
@@ -264,14 +281,15 @@ handle_null_request(int tun_fd, int dns_fd, struct query *q, int domain_len)
 			return;
 		}
 
-		userid = in[1] & 0x7;
+		userid = b32_8to5(in[1]);
 		
 		if (ip_cmp(userid, q) != 0) {
 			write_dns(dns_fd, q, "BADIP", 5);
 			return; /* illegal id */
 		}
 		
-		codec = in[2] & 0xF;
+		codec = b32_8to5(in[2]);
+
 		switch (codec) {
 		case 5: /* 5 bits per byte = base32 */
 			enc = get_base32_encoder();
@@ -288,6 +306,25 @@ handle_null_request(int tun_fd, int dns_fd, struct query *q, int domain_len)
 			break;
 		}
 		return;
+	} else if(in[0] == 'P' || in[0] == 'p') {
+		int dn_seq;
+		int dn_frag;
+		
+		read = unpack_data(unpacked, sizeof(unpacked), &(in[1]), domain_len - 1, b32);
+		/* Ping packet, store userid */
+		userid = unpacked[0];
+		if (userid < 0 || userid >= USERS || ip_cmp(userid, q) != 0) {
+			write_dns(dns_fd, q, "BADIP", 5);
+			return; /* illegal id */
+		}
+
+		dn_seq = unpacked[1] >> 4;
+		dn_frag = unpacked[1] & 15;
+		memcpy(&(users[userid].q), q, sizeof(struct query));
+		users[userid].last_pkt = time(NULL);
+
+		/* Update seqno and maybe send immediate response packet */
+		update_downstream_seqno(dns_fd, userid, dn_seq, dn_frag);
 	} else if((in[0] >= '0' && in[0] <= '9')
 			|| (in[0] >= 'a' && in[0] <= 'f')
 			|| (in[0] >= 'A' && in[0] <= 'F')) {
@@ -298,7 +335,7 @@ handle_null_request(int tun_fd, int dns_fd, struct query *q, int domain_len)
 		if ((in[0] >= 'A' && in[0] <= 'F'))
 			code = in[0] - 'A' + 10;
 
-		userid = code >> 1;
+		userid = code;
 		if (userid < 0 || userid >= USERS) {
 			write_dns(dns_fd, q, "BADIP", 5);
 			return; /* illegal id */
@@ -308,45 +345,69 @@ handle_null_request(int tun_fd, int dns_fd, struct query *q, int domain_len)
 		if (check_ip && ip_cmp(userid, q) != 0) {
 			write_dns(dns_fd, q, "BADIP", 5);
 		} else {
-			/* decode with this users encoding */
-			read = unpack_data(unpacked, sizeof(unpacked), &(in[1]), domain_len - 1, 
-					   users[userid].encoder);
+			/* Decode data header */
+			int up_seq = (b32_8to5(in[1]) >> 2) & 7;
+			int up_frag = ((b32_8to5(in[1]) & 3) << 2) | ((b32_8to5(in[2]) >> 3) & 3);
+			int dn_seq = (b32_8to5(in[2]) & 7);
+			int dn_frag = b32_8to5(in[3]) >> 1;
+			int lastfrag = b32_8to5(in[3]) & 1;
 
+			/* Update query and time info for user */
 			users[userid].last_pkt = time(NULL);
 			memcpy(&(users[userid].q), q, sizeof(struct query));
+
+			if (up_seq != users[userid].inpacket.seqno) {
+				/* New packet has arrived */
+				users[userid].inpacket.seqno = up_seq;
+				users[userid].inpacket.len = 0;
+				users[userid].inpacket.offset = 0;
+			}
+			users[userid].inpacket.fragment = up_frag;
+
+			/* decode with this users encoding */
+			read = unpack_data(unpacked, sizeof(unpacked), &(in[4]), domain_len - 4, 
+					   users[userid].encoder);
+
+			/* copy to packet buffer, update length */
 			memcpy(users[userid].inpacket.data + users[userid].inpacket.offset, unpacked, read);
 			users[userid].inpacket.len += read;
 			users[userid].inpacket.offset += read;
 
-			if (code & 1) {
+			if (debug >= 1) {
+				printf("IN  pkt seq# %d, frag %d (last=%d), fragsize %d, total %d, from user %d\n",
+					up_seq, up_frag, lastfrag, read, users[userid].inpacket.len, userid);
+			}
+
+			if (lastfrag & 1) { /* packet is complete */
+				int ret;
 				outlen = sizeof(out);
-				uncompress((uint8_t*)out, &outlen, 
-						   (uint8_t*)users[userid].inpacket.data, users[userid].inpacket.len);
+				ret = uncompress((uint8_t*)out, &outlen, 
+					   (uint8_t*)users[userid].inpacket.data, users[userid].inpacket.len);
 
-				hdr = (struct ip*) (out + 4);
-				touser = find_user_by_ip(hdr->ip_dst.s_addr);
+				if (ret == Z_OK) {
+					hdr = (struct ip*) (out + 4);
+					touser = find_user_by_ip(hdr->ip_dst.s_addr);
 
-				if (touser == -1) {
-					/* send the uncompressed packet to tun device */
-					write_tun(tun_fd, out, outlen);
-				} else {
-					/* send the compressed packet to other client
-					 * if another packet is queued, throw away this one. TODO build queue */
-					if (users[touser].outpacket.len == 0) {
-						memcpy(users[touser].outpacket.data, users[userid].inpacket.data, users[userid].inpacket.len);
-						users[touser].outpacket.len = users[userid].inpacket.len;
+					if (touser == -1) {
+						/* send the uncompressed packet to tun device */
+						write_tun(tun_fd, out, outlen);
+					} else {
+						/* send the compressed packet to other client
+						 * if another packet is queued, throw away this one. TODO build queue */
+						if (users[touser].outpacket.len == 0) {
+							memcpy(users[touser].outpacket.data, users[userid].inpacket.data, users[userid].inpacket.len);
+							users[touser].outpacket.len = users[userid].inpacket.len;
+						}
 					}
+				} else {
+					printf("Discarded data, uncompress() result: %d\n", ret);
 				}
 				users[userid].inpacket.len = users[userid].inpacket.offset = 0;
 			}
+			/* Update seqno and maybe send immediate response packet */
+			update_downstream_seqno(dns_fd, userid, dn_seq, dn_frag);
 		}
 	}
-	/* userid must be set for a reply to be sent */
-	if (userid >= 0 && userid < USERS && ip_cmp(userid, q) == 0 && users[userid].outpacket.len > 0) {
-		write_dns(dns_fd, q, users[userid].outpacket.data, users[userid].outpacket.len);
-		users[userid].outpacket.len = 0;
-		users[userid].q.id = 0;
-	}
 }
 
 static void
@@ -361,7 +422,7 @@ handle_ns_request(int dns_fd, struct query *q)
 
 	len = dns_encode_ns_response(buf, sizeof(buf), q, topdomain);
 	
-	if (debug >= 1) {
+	if (debug >= 2) {
 		struct sockaddr_in *tempin;
 		tempin = (struct sockaddr_in *) &(q->from);
 		printf("TX: client %s, type %d, name %s, %d bytes NS reply\n", 
@@ -394,7 +455,7 @@ forward_query(int bind_fd, struct query *q)
 	memcpy(&(myaddr->sin_addr), &newaddr, sizeof(in_addr_t));
 	myaddr->sin_port = htons(bind_port);
 	
-	if (debug >= 1) {
+	if (debug >= 2) {
 		printf("TX: NS reply \n");
 	}
 
@@ -422,18 +483,18 @@ tunnel_bind(int bind_fd, int dns_fd)
 
 	id = dns_get_id(packet, r);
 	
-	if (debug >= 1) {
+	if (debug >= 2) {
 		printf("RX: Got response on query %u from DNS\n", (id & 0xFFFF));
 	}
 
 	/* Get sockaddr from id */
 	fw_query_get(id, &query);
-	if (!query && debug >= 1) {
+	if (!query && debug >= 2) {
 		printf("Lost sender of id %u, dropping reply\n", (id & 0xFFFF));
 		return 0;
 	}
 
-	if (debug >= 1) {
+	if (debug >= 2) {
 		struct sockaddr_in *in;
 		in = (struct sockaddr_in *) &(query->addr);
 		printf("TX: client %s id %u, %d bytes\n",
@@ -460,7 +521,7 @@ tunnel_dns(int tun_fd, int dns_fd, int bind_fd)
 	if ((read = read_dns(dns_fd, &q)) <= 0)
 		return 0;
 
-	if (debug >= 1) {
+	if (debug >= 2) {
 		struct sockaddr_in *tempin;
 		tempin = (struct sockaddr_in *) &(q.from);
 		printf("RX: client %s, type %d, name %s\n", 
@@ -543,6 +604,10 @@ tunnel(int tun_fd, int dns_fd, int bind_fd)
 			int j;
  			for (j = 0; j < USERS; j++) {
  				if (users[j].q.id != 0) {
+					if (debug >= 1) {
+						printf("OUT pkt seq# %d, frag %d (last=%d), fragsize %d of total %d, to user %d\n",
+							0, 0, 1, users[j].outpacket.len, users[j].outpacket.len, j);
+					}
  					write_dns(dns_fd, &(users[j].q), users[j].outpacket.data, users[j].outpacket.len);
  					users[j].outpacket.len = 0;
  					users[j].q.id = 0;
@@ -626,7 +691,7 @@ write_dns(int fd, struct query *q, char *data, int datalen)
 
 	len = dns_encode(buf, sizeof(buf), q, QR_ANSWER, data, datalen);
 	
-	if (debug >= 1) {
+	if (debug >= 2) {
 		struct sockaddr_in *tempin;
 		tempin = (struct sockaddr_in *) &(q->from);
 		printf("TX: client %s, type %d, name %s, %d bytes data\n", 
diff --git a/src/user.c b/src/user.c
index c23d9c7..cc6a883 100644
--- a/src/user.c
+++ b/src/user.c
@@ -50,6 +50,8 @@ init_users(in_addr_t my_ip)
 		users[i].inpacket.offset = 0;
 		users[i].outpacket.len = 0;
 		users[i].q.id = 0;
+		users[i].out_acked_seqno = 0;
+		users[i].out_acked_fragment = 0;
 	}
 }
 
diff --git a/src/user.h b/src/user.h
index 8f1250c..0dc7cc2 100644
--- a/src/user.h
+++ b/src/user.h
@@ -17,7 +17,7 @@
 #ifndef __USER_H__
 #define __USER_H__
 
-#define USERS 8
+#define USERS 16
 
 struct user {
 	char id;
@@ -30,6 +30,8 @@ struct user {
 	struct packet inpacket;
 	struct packet outpacket;
 	struct encoder *encoder;
+	int out_acked_seqno;
+	int out_acked_fragment;
 };
 
 extern struct user users[USERS];