diff --git a/src/common.h b/src/common.h
index 28fc2bb..dd7db6e 100644
--- a/src/common.h
+++ b/src/common.h
@@ -21,8 +21,13 @@
 #define RAW_HDR_LEN 4
 #define RAW_HDR_IDENT_LEN 3
 #define RAW_HDR_CMD 3
-#define RAW_HDR_CMD_LOGIN 0x01
-#define RAW_HDR_CMD_DATA  0x02
+#define RAW_HDR_CMD_LOGIN 0x10
+#define RAW_HDR_CMD_DATA  0x20
+
+#define RAW_HDR_CMD_MASK  0xF0
+#define RAW_HDR_USR_MASK  0x0F
+#define RAW_HDR_GET_CMD(x) ((x)[RAW_HDR_CMD] & RAW_HDR_CMD_MASK)
+#define RAW_HDR_GET_USR(x) ((x)[RAW_HDR_CMD] & RAW_HDR_USR_MASK)
 extern const unsigned char raw_header[RAW_HDR_LEN];
 
 #ifdef WINDOWS32
@@ -85,6 +90,12 @@ struct query {
 	int fromlen;
 };
 
+enum connection {
+	CONN_RAW_UDP,
+	CONN_DNS_NULL,
+	CONN_MAX
+};
+
 void check_superuser(void (*usage_fn)(void));
 int open_dns(int, in_addr_t);
 void close_dns(int);
diff --git a/src/iodine.c b/src/iodine.c
index 9b1dcba..41cb9cc 100644
--- a/src/iodine.c
+++ b/src/iodine.c
@@ -60,6 +60,7 @@ WSADATA wsa_data;
 
 static void send_ping(int fd);
 static void send_chunk(int fd);
+static void send_raw_data(int fd);
 static int build_hostname(char *buf, size_t buflen, 
 	const char *data, const size_t datalen, 
 	const char *topdomain, struct encoder *encoder);
@@ -94,6 +95,9 @@ static struct encoder *b32;
  * Defaults to Base32, can be changed after handshake */
 static struct encoder *dataenc;
 
+/* My connection mode */
+static enum connection conn;
+
 #if !defined(BSD) && !defined(__GLIBC__)
 static char *__progname;
 #endif
@@ -120,7 +124,7 @@ send_query(int fd, char *hostname)
 }
 
 static void
-send_raw(int fd, char *buf, int buflen, int cmd)
+send_raw(int fd, char *buf, int buflen, int user, int cmd)
 {
 	char packet[4096];
 	int len;
@@ -131,7 +135,7 @@ send_raw(int fd, char *buf, int buflen, int cmd)
 	memcpy(&packet[RAW_HDR_LEN], buf, len);
 
 	len += RAW_HDR_LEN;
-	packet[RAW_HDR_CMD] = cmd;
+	packet[RAW_HDR_CMD] = cmd | (user & 0x0F);
 
 	sendto(fd, packet, len, 0, (struct sockaddr*)&raw_serv, sizeof(raw_serv));
 }
@@ -263,7 +267,11 @@ tunnel_tun(int tun_fd, int dns_fd)
 	outpkt.seqno++;
 	outpkt.fragment = 0;
 
-	send_chunk(dns_fd);
+	if (conn == CONN_DNS_NULL) {
+		send_chunk(dns_fd);
+	} else {
+		send_raw_data(dns_fd);
+	}
 
 	return read;
 }
@@ -327,7 +335,7 @@ tunnel(int tun_fd, int dns_fd)
 
 
 		FD_ZERO(&fds);
-		if (!is_sending()) {
+		if ((!is_sending()) || conn == CONN_RAW_UDP) {
 			FD_SET(tun_fd, &fds);
 		}
 		FD_SET(dns_fd, &fds);
@@ -340,7 +348,7 @@ tunnel(int tun_fd, int dns_fd)
 		if (i < 0) 
 			err(1, "select");
 
-		if (i == 0) /* timeout */
+		if (i == 0 && conn == CONN_DNS_NULL) /* timeout */
 			send_ping(dns_fd);
 		else {
 			if (FD_ISSET(tun_fd, &fds)) {
@@ -357,6 +365,12 @@ tunnel(int tun_fd, int dns_fd)
 	return rv;
 }
 
+static void
+send_raw_data(int dns_fd)
+{
+	send_raw(dns_fd, outpkt.data, outpkt.len, userid, RAW_HDR_CMD_DATA);
+}
+
 static void
 send_chunk(int fd)
 {
@@ -507,11 +521,10 @@ send_ip_request(int fd, int userid)
 static void
 send_raw_udp_login(int dns_fd, int userid, int seed)
 {
-	char buf[17];
+	char buf[16];
 	login_calculate(buf, 16, password, seed + 1);
-	buf[16] = userid;
 
-	send_raw(dns_fd, buf, sizeof(buf), RAW_HDR_CMD_LOGIN);
+	send_raw(dns_fd, buf, sizeof(buf), userid, RAW_HDR_CMD_LOGIN);
 }
 
 static void
@@ -715,7 +728,7 @@ handshake_raw_udp(int dns_fd, int seed)
 	
 	if (!remoteaddr) {
 		fprintf(stderr, " failed to get IP.\n");
-		return 1;
+		return 0;
 	}
 	fprintf(stderr, " at %s: ", inet_ntoa(server));
 	fflush(stderr);
@@ -743,16 +756,15 @@ handshake_raw_udp(int dns_fd, int seed)
 		if(r > 0) {
 			/* recv() needed for windows, dont change to read() */
 			len = recv(dns_fd, in, sizeof(in), 0);
-			if (len >= (17 + RAW_HDR_LEN)) {
+			if (len >= (16 + RAW_HDR_LEN)) {
 				char hash[16];
 				login_calculate(hash, 16, password, seed - 1);
 				if (memcmp(in, raw_header, RAW_HDR_IDENT_LEN) == 0
-					&& in[RAW_HDR_CMD] == RAW_HDR_CMD_LOGIN 
-					&& memcmp(&in[RAW_HDR_LEN], hash, sizeof(hash)) == 0
-					&& in[16 + RAW_HDR_LEN] ==  userid) {
+					&& RAW_HDR_GET_CMD(in) == RAW_HDR_CMD_LOGIN 
+					&& memcmp(&in[RAW_HDR_LEN], hash, sizeof(hash)) == 0) {
 
 					fprintf(stderr, "OK\n");
-					return 0;
+					return 1;
 				}
 			}
 		}
@@ -761,7 +773,7 @@ handshake_raw_udp(int dns_fd, int seed)
 	}
 	
 	fprintf(stderr, "failed\n");
-	return 1;
+	return 0;
 }
 
 static int
@@ -1016,22 +1028,24 @@ handshake(int dns_fd, int autodetect_frag_size, int fragsize)
 		return r;
 	}
 
-	handshake_raw_udp(dns_fd, seed);
+	if (handshake_raw_udp(dns_fd, seed)) {
+		conn = CONN_RAW_UDP;
+	} else {
+		case_preserved = handshake_case_check(dns_fd);
 
-	case_preserved = handshake_case_check(dns_fd);
-
-	if (case_preserved) {
-		handshake_switch_codec(dns_fd);
-	}
-
-	if (autodetect_frag_size) {
-		fragsize = handshake_autoprobe_fragsize(dns_fd);
-		if (!fragsize) {
-			return 1;
+		if (case_preserved) {
+			handshake_switch_codec(dns_fd);
 		}
-	}
 
-	handshake_set_fragsize(dns_fd, fragsize);
+		if (autodetect_frag_size) {
+			fragsize = handshake_autoprobe_fragsize(dns_fd);
+			if (!fragsize) {
+				return 1;
+			}
+		}
+
+		handshake_set_fragsize(dns_fd, fragsize);
+	}
 
 	return 0;
 }
@@ -1178,6 +1192,7 @@ main(int argc, char **argv)
 
 	b32 = get_base32_encoder();
 	dataenc = get_base32_encoder();
+	conn = CONN_DNS_NULL;
 
 	retval = 0;
 
@@ -1312,7 +1327,11 @@ main(int argc, char **argv)
 		goto cleanup2;
 	}
 	
-	fprintf(stderr, "Sending queries for %s to %s\n", topdomain, nameserv_addr);
+	if (conn == CONN_DNS_NULL) {
+		fprintf(stderr, "Sending queries for %s to %s\n", topdomain, nameserv_addr);
+	} else {
+		fprintf(stderr, "Sending raw traffic directly to %s\n", inet_ntoa(raw_serv.sin_addr));
+	}
 
 	if (foreground == 0) 
 		do_detach();
diff --git a/src/iodined.c b/src/iodined.c
index e1f4b94..60b84ca 100644
--- a/src/iodined.c
+++ b/src/iodined.c
@@ -85,8 +85,9 @@ static int debug;
 static char *__progname;
 #endif
 
-static int read_dns(int, struct query *);
+static int read_dns(int, int, struct query *);
 static void write_dns(int, struct query *, char *, int);
+static void handle_full_packet(int, int);
 
 static void
 sigint(int sig) 
@@ -295,15 +296,12 @@ static void
 handle_null_request(int tun_fd, int dns_fd, struct query *q, int domain_len)
 {
 	struct in_addr tempip;
-	struct ip *hdr;
-	unsigned long outlen;
 	char in[512];
 	char logindata[16];
 	char out[64*1024];
 	char unpacked[64*1024];
 	char *tmp[2];
 	int userid;
-	int touser;
 	int version;
 	int code;
 	int read;
@@ -589,30 +587,7 @@ handle_null_request(int tun_fd, int dns_fd, struct query *q, int domain_len)
 			}
 
 			if (lastfrag & 1) { /* packet is complete */
-				int ret;
-				outlen = sizeof(out);
-				ret = uncompress((uint8_t*)out, &outlen, 
-					   (uint8_t*)users[userid].inpacket.data, users[userid].inpacket.len);
-
-				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;
-						}
-					}
-				} else {
-					fprintf(stderr, "Discarded data, uncompress() result: %d\n", ret);
-				}
-				users[userid].inpacket.len = users[userid].inpacket.offset = 0;
+				handle_full_packet(tun_fd, userid);
 			}
 			/* Update seqno and maybe send immediate response packet */
 			update_downstream_seqno(dns_fd, userid, dn_seq, dn_frag);
@@ -730,7 +705,7 @@ tunnel_dns(int tun_fd, int dns_fd, int bind_fd)
 	int domain_len;
 	int inside_topdomain;
 
-	if ((read = read_dns(dns_fd, &q)) <= 0)
+	if ((read = read_dns(dns_fd, tun_fd, &q)) <= 0)
 		return 0;
 
 	if (debug >= 2) {
@@ -820,15 +795,15 @@ tunnel(int tun_fd, int dns_fd, int bind_fd)
  				}
  			}
  		} else {
- 			if(FD_ISSET(tun_fd, &fds)) {
+ 			if (FD_ISSET(tun_fd, &fds)) {
  				tunnel_tun(tun_fd, dns_fd);
  				continue;
  			}
- 			if(FD_ISSET(dns_fd, &fds)) {
+ 			if (FD_ISSET(dns_fd, &fds)) {
  				tunnel_dns(tun_fd, dns_fd, bind_fd);
  				continue;
  			} 
-			if(FD_ISSET(bind_fd, &fds)) {
+			if (FD_ISSET(bind_fd, &fds)) {
 				tunnel_bind(bind_fd, dns_fd);
 				continue;
 			}
@@ -839,7 +814,42 @@ tunnel(int tun_fd, int dns_fd, int bind_fd)
 }
 
 static void
-send_raw(int fd, char *buf, int buflen, int cmd, struct query *q)
+handle_full_packet(int tun_fd, int userid)
+{
+	unsigned long outlen;
+	char out[64*1024];
+	int touser;
+	int ret;
+
+	outlen = sizeof(out);
+	ret = uncompress((uint8_t*)out, &outlen, 
+		   (uint8_t*)users[userid].inpacket.data, users[userid].inpacket.len);
+
+	if (ret == Z_OK) {
+		struct ip *hdr;
+
+		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;
+			}
+		}
+	} else {
+		fprintf(stderr, "Discarded data, uncompress() result: %d\n", ret);
+	}
+	users[userid].inpacket.len = users[userid].inpacket.offset = 0;
+}
+
+static void
+send_raw(int fd, char *buf, int buflen, int user, int cmd, struct query *q)
 {
 	char packet[4096];
 	int len;
@@ -850,51 +860,96 @@ send_raw(int fd, char *buf, int buflen, int cmd, struct query *q)
 	memcpy(&packet[RAW_HDR_LEN], buf, len);
 
 	len += RAW_HDR_LEN;
-	packet[RAW_HDR_CMD] = cmd;
+	packet[RAW_HDR_CMD] = cmd | (user & 0x0F);
 
 	sendto(fd, packet, len, 0, &q->from, q->fromlen);
 }
 
 static void
-handle_raw_login(char *packet, int len, struct query *q, int fd)
+handle_raw_login(char *packet, int len, struct query *q, int fd, int userid)
 {
-	int userid;
 	char myhash[16];
 	
-	if (len < 17) return;
+	if (len < 16) return;
 
-	userid = packet[16];
 	if (userid < 0 || userid > created_users) return;
 	if (!users[userid].active) return;
 
+	/* User sends hash of seed + 1 */
 	login_calculate(myhash, 16, password, users[userid].seed + 1);
 	if (memcmp(packet, myhash, 16) == 0) {
+		struct sockaddr_in *tempin;
+
+		/* Update query and time info for user */
+		users[userid].last_pkt = time(NULL);
+		memcpy(&(users[userid].q), q, sizeof(struct query));
+
+		/* Store remote IP number */
+		tempin = (struct sockaddr_in *) &(q->from);
+		memcpy(&(users[userid].host), &(tempin->sin_addr), sizeof(struct in_addr));
+		 
 		/* Correct hash, reply with hash of seed - 1 */
+		user_set_conn_type(userid, CONN_RAW_UDP);
 		users[userid].last_pkt = time(NULL);
 		login_calculate(myhash, 16, password, users[userid].seed - 1);
-		memcpy(packet, myhash, 16);
-		send_raw(fd, packet, 17, RAW_HDR_CMD_LOGIN, q);
+		send_raw(fd, myhash, 16, userid, RAW_HDR_CMD_LOGIN, q);
 	}
 }
 
-static int
-raw_decode(char *packet, int len, struct query *q, int fd)
+static void
+handle_raw_data(char *packet, int len, struct query *q, int dns_fd, int tun_fd, int userid)
 {
+	if (check_user_and_ip(userid, q) != 0) {
+		return;
+	}
+
+	/* Update query and time info for user */
+	users[userid].last_pkt = time(NULL);
+	memcpy(&(users[userid].q), q, sizeof(struct query));
+
+	/* copy to packet buffer, update length */
+	users[userid].inpacket.offset = 0;
+	memcpy(users[userid].inpacket.data, packet, len);
+	users[userid].inpacket.len = len;
+
+	if (debug >= 1) {
+		fprintf(stderr, "IN   pkt raw, total %d, from user %d\n",
+			users[userid].inpacket.len, userid);
+	}
+
+	handle_full_packet(tun_fd, userid);
+}
+
+static int
+raw_decode(char *packet, int len, struct query *q, int dns_fd, int tun_fd)
+{
+	int raw_user;
+
 	/* minimum length */
 	if (len < RAW_HDR_LEN) return 0;
 	/* should start with header */
 	if (memcmp(packet, raw_header, RAW_HDR_IDENT_LEN)) return 0;
 
-	if (packet[RAW_HDR_CMD] == RAW_HDR_CMD_LOGIN) {
-		handle_raw_login(&packet[RAW_HDR_LEN], len - RAW_HDR_LEN, q, fd);
-	} else {
-		warnx("Unhandled raw command %02X\n", packet[RAW_HDR_CMD]);
+	raw_user = RAW_HDR_GET_USR(packet);
+	printf("raw %02x\n", packet[RAW_HDR_CMD]);
+	switch (RAW_HDR_GET_CMD(packet)) {
+	case RAW_HDR_CMD_LOGIN:
+		/* Login challenge */
+		handle_raw_login(&packet[RAW_HDR_LEN], len - RAW_HDR_LEN, q, dns_fd, raw_user);
+		break;
+	case RAW_HDR_CMD_DATA:
+		/* Data packet */
+		handle_raw_data(&packet[RAW_HDR_LEN], len - RAW_HDR_LEN, q, dns_fd, tun_fd, raw_user);
+		break;
+	default:
+		warnx("Unhandled raw command %02X from user %d", RAW_HDR_GET_CMD(packet), raw_user);
+		break;
 	}
 	return 1;
 }
 
 static int
-read_dns(int fd, struct query *q)
+read_dns(int fd, int tun_fd, struct query *q) /* FIXME: tun_fd is because of raw_decode() below */
 {
 	struct sockaddr_in from;
 	socklen_t addrlen;
@@ -929,7 +984,7 @@ read_dns(int fd, struct query *q)
 		q->fromlen = addrlen;
 
 		/* TODO do not handle raw packets here! */
-		if (raw_decode(packet, r, q, fd)) {
+		if (raw_decode(packet, r, q, fd, tun_fd)) {
 			return 0;
 		}
 		if (dns_decode(NULL, 0, q, QR_QUERY, packet, r) < 0) {
@@ -1177,7 +1232,7 @@ main(int argc, char **argv)
 	}
 
 	topdomain = strdup(argv[1]);
-	if(strlen(topdomain) <= 128) {
+	if (strlen(topdomain) <= 128) {
 		if(check_topdomain(topdomain)) {
 			warnx("Topdomain contains invalid characters.");
 			usage();
diff --git a/src/user.c b/src/user.c
index b15665b..7250251 100644
--- a/src/user.c
+++ b/src/user.c
@@ -90,6 +90,7 @@ init_users(in_addr_t my_ip, int netbits)
 		users[i].out_acked_seqno = 0;
 		users[i].out_acked_fragment = 0;
 		users[i].fragsize = 4096;
+		users[i].conn = CONN_DNS_NULL;
 	}
 
 	return created_users;
@@ -161,6 +162,8 @@ find_available_user()
 		if ((!users[i].active || users[i].last_pkt + 60 < time(NULL)) && !users[i].disabled) {
 			users[i].active = 1;
 			users[i].last_pkt = time(NULL);
+			users[i].fragsize = 4096;
+			users[i].conn = CONN_DNS_NULL;
 			ret = i;
 			break;
 		}
@@ -176,3 +179,16 @@ user_switch_codec(int userid, struct encoder *enc)
 	
 	users[userid].encoder = enc;
 }
+
+void
+user_set_conn_type(int userid, enum connection c)
+{
+	if (userid < 0 || userid >= USERS)
+		return;
+
+	if (c < 0 || c >= CONN_MAX)
+		return;
+	
+	users[userid].conn = c;
+}
+	
diff --git a/src/user.h b/src/user.h
index 51fdfbc..ba1e696 100644
--- a/src/user.h
+++ b/src/user.h
@@ -34,6 +34,7 @@ struct user {
 	int out_acked_seqno;
 	int out_acked_fragment;
 	int fragsize;
+	enum connection conn;
 };
 
 extern struct user users[USERS];
@@ -44,5 +45,6 @@ int find_user_by_ip(uint32_t);
 int all_users_waiting_to_send();
 int find_available_user();
 void user_switch_codec(int userid, struct encoder *enc);
+void user_set_conn_type(int userid, enum connection c);
 
 #endif