diff --git a/udpsendqueue.cpp b/udpsendqueue.cpp
new file mode 100644
index 0000000..6d3c455
--- /dev/null
+++ b/udpsendqueue.cpp
@@ -0,0 +1,120 @@
+
+#include <cstring>
+
+#include "tunnel.h"
+#include "connection.h"
+#include "udpsendqueue.h"
+
+
+
+UdpSendQueue::UdpSendQueue(Connection &connection):
+	connection(connection),
+	nextSendTime(connection.tunnel.time + connection.tunnel.udpSendDuration),
+	nextPartialSendTime(connection.tunnel.time + connection.tunnel.udpPartialSendDuration),
+	finalEntryAdded(),
+	nextIndex(),
+	freeFirst(entries),
+	busyFirst(),
+	busyLast(),
+	entries()
+{
+	Entry *e = entries;
+	Entry *end = e + sizeof(entries)/sizeof(*entries) - 1;
+	while(e < end) { e->next = e + 1; ++e; }
+}
+
+
+UdpSendQueue::~UdpSendQueue()
+	{ }
+
+
+UdpSendQueue::Entry* UdpSendQueue::allocEntry() {
+	Entry *e = freeFirst;
+	if (!e) return nullptr;
+	freeFirst = e->next;
+	e->prev = busyLast;
+	e->next = nullptr;
+	(e->prev ? e->prev->next : busyFirst) = e;
+	busyLast = e;
+	return e;
+}
+
+
+void UdpSendQueue::freeEntry(Entry *e) {
+	(e->prev ? e->prev->next : busyFirst) = e->next;
+	(e->next ? e->next->prev : busyLast ) = e->prev;
+	memset(e, 0, sizeof(*e));
+	e->next = freeFirst;
+	freeFirst = e;
+}
+
+
+void UdpSendQueue::confirm(unsigned int index, const unsigned char *bits, unsigned int bitsCount) {
+	while(busyFirst && cycleLess(busyFirst->index, index))
+		freeEntry(busyFirst);
+	Entry *e = busyFirst;
+	while(e) {
+		unsigned int bit = e->index - index;
+		if (bit > bitsCount)
+			break;
+		unsigned int byte = bit / 8;
+		bit %= 8;
+		if (bits[byte] & (1 << bit)) {
+			Entry *ee = e;
+			e = e->next;
+			freeEntry(ee);
+		} else {
+			e = e->next;
+		}
+	}
+}
+
+
+bool UdpSendQueue::sendUdp() {
+	Time time = connection.tunnel.time;
+	if (timeLess(time, nextSendTime))
+		return false;
+	
+	Entry *e = busyFirst;
+	while(e) {
+		if (timeLequal(e->resendTime, time))
+			break;
+		e = e->next;
+	}
+	
+	if (!e && !freeFirst)
+		return false;
+	
+	if (!e) {
+		int size = connection.tcpRecvQueue.busySize();
+		if (size > PACKET_SIZE) size = PACKET_SIZE;
+		if (size < 0) size = 0;
+		
+		if (size == 0) {
+			if (connection.tcpSockId >= 0 || finalEntryAdded) {
+				nextPartialSendTime = time + connection.tunnel.udpPartialSendDuration;
+				return false;
+			}
+		} else {
+			if (size < PACKET_SIZE && timeLess(time, nextPartialSendTime))
+				return false;
+		}
+		
+		if (( e = allocEntry() ))
+			return false;
+		e->index = nextIndex++;
+		e->size = size;
+		if (e->size) connection.tcpRecvQueue.pop(e->data, e->size); else finalEntryAdded = true;
+	}
+	
+	Packet packet;
+	packet.init(connection.id, e->index, CMD_DATA, e->size);
+	if (e->size) memcpy(packet.payload, e->data, e->size);
+	connection.tunnel.udpSend(packet);
+	
+	e->resendTime = time + connection.tunnel.udpResendDuration;
+	nextSendTime += connection.tunnel.udpPartialSendDuration;
+	nextPartialSendTime = time + connection.tunnel.udpPartialSendDuration;
+	return true;
+}
+
diff --git a/udpsendqueue.h b/udpsendqueue.h
new file mode 100644
index 0000000..98458d4
--- /dev/null
+++ b/udpsendqueue.h
@@ -0,0 +1,45 @@
+#ifndef UDPSENDQUEUE_H
+#define UDPSENDQUEUE_H
+
+
+#include "common.h"
+
+
+class Connection;
+
+
+class UdpSendQueue {
+public:
+	struct Entry {
+		Entry *prev, *next;
+		Time resendTime;
+		unsigned int index;
+		unsigned short size;
+		unsigned char data[PACKET_SIZE];
+	};
+	
+public:
+	Connection &connection;
+private:
+	Time nextSendTime;
+	Time nextPartialSendTime;
+	bool finalEntryAdded;
+	unsigned int nextIndex;
+	
+	Entry *freeFirst;
+	Entry *busyFirst, *busyLast;
+	Entry entries[PACKETS_COUNT];
+	
+	Entry* allocEntry();
+	void freeEntry(Entry *e);
+	
+public:
+	explicit UdpSendQueue(Connection &connection);
+	~UdpSendQueue();
+	
+	void confirm(unsigned int index, const unsigned char *bits, unsigned int bitsCount);
+	bool sendUdp();
+};
+
+
+#endif