From 47f3ea58e3a8cf75e0a71d61c2edfb8ab953b7e7 Mon Sep 17 00:00:00 2001 From: jsh77 Date: Thu, 13 May 2021 14:12:35 +0000 Subject: [PATCH 1/2] Update on Overleaf. --- 2_Preparation/preparation.tex | 85 +-------- 3_Implementation/implementation.tex | 234 ++++++++++++------------- A1_LanguageSamples/languagesamples.tex | 18 +- A2_LayeredSecurity/layeredsecurity.tex | 88 ++++++++++ thesis-info.tex | 2 +- thesis.tex | 7 +- 6 files changed, 220 insertions(+), 214 deletions(-) create mode 100644 A2_LayeredSecurity/layeredsecurity.tex diff --git a/2_Preparation/preparation.tex b/2_Preparation/preparation.tex index 7473c9a..75cf7fb 100644 --- a/2_Preparation/preparation.tex +++ b/2_Preparation/preparation.tex @@ -86,90 +86,7 @@ When applying message authentication, it was sufficient to authenticate messages It was previously mentioned that my solution is transparent to the higher layer security encapsulated by proxied packets. Further to this, my solution provides transparent security in the other direction, where proxied packets are encapsulated by another security solution. Consider the case of a satellite office that employs both a whole network corporate VPN and my solution. The network can be configured in each of two cases: the multipath proxy runs behind the VPN, or the VPN runs behind the multipath proxy. -Packet structures for proxied packets in each of these cases are given in Figure \ref{fig:whole-network-vpn-behind} and Figure \ref{fig:whole-network-vpn-infront}, for the VPN Wireguard \citep{donenfeld_wireguard_2017}. In Figure \ref{fig:whole-network-vpn-infront}, the portals are only accessible via the VPN protected network. It can be seen that the packet in Figure \ref{fig:whole-network-vpn-infront} is shorter, given the removal of the message authentication code and the data sequence number. The data sequence number is unnecessary, given that Wireguard uses the same anti-replay algorithm, and thus replayed packets would have been caught entering the secure network. Further, the message authentication code is unnecessary, as the authenticity of packets is now guaranteed by Wireguard. - -\begin{figure} - \begin{leftfullpage} - \centering - \begin{bytefield}[bitwidth=0.6em]{32} - \bitheader{0-31} \\ - \wordbox[tlr]{1}{IPv4 Header} \\ - \wordbox[blr]{1}{$\cdots$} \\ - \begin{rightwordgroup}{UDP\\Header} - \bitbox{16}{Source port} & \bitbox{16}{Destination port} \\ - \bitbox{16}{Length} & \bitbox{16}{Checksum} - \end{rightwordgroup} \\ - \begin{rightwordgroup}{CC\\Header} - \bitbox{32}{Acknowledgement number} \\ - \bitbox{32}{Negative acknowledgement number} \\ - \bitbox{32}{Sequence number} - \end{rightwordgroup} \\ - \begin{rightwordgroup}{Proxied\\Wireguard\\Packet} - \wordbox[tlr]{1}{IPv4 Header} \\ - \wordbox[blr]{1}{$\cdots$} \\ - \begin{leftwordgroup}{UDP Header} - \bitbox{16}{Source port} & \bitbox{16}{Destination port} \\ - \bitbox{16}{Length} & \bitbox{16}{Checksum} - \end{leftwordgroup} \\ - \begin{leftwordgroup}{Wireguard\\Header} - \bitbox{8}{type} & \bitbox{24}{reserved} \\ - \wordbox{1}{receiver} \\ - \wordbox{2}{counter} - \end{leftwordgroup} \\ - \wordbox[tlr]{1}{Proxied IP packet} \\ - \skippedwords\\ - \wordbox[blr]{1}{} - \end{rightwordgroup} \\ - \begin{rightwordgroup}{Security\\Footer} - \bitbox{32}{Data sequence number} \\ - \wordbox[tlr]{1}{Message authentication code} \\ - \wordbox[blr]{1}{$\cdots$} - \end{rightwordgroup} - \end{bytefield} - - \caption{Packet structure for a configuration with a Wireguard client behind my multipath proxy.} - \label{fig:whole-network-vpn-behind} - \end{leftfullpage} -\end{figure} - -\begin{figure} - \begin{fullpage} - \centering - \begin{bytefield}[bitwidth=0.6em]{32} - \bitheader{0-31} \\ - \wordbox[tlr]{1}{IPv4 Header} \\ - \wordbox[blr]{1}{$\cdots$}\\ - \begin{rightwordgroup}{UDP\\Header} - \bitbox{16}{Source port} & \bitbox{16}{Destination port} \\ - \bitbox{16}{Length} & \bitbox{16}{Checksum} - \end{rightwordgroup} \\ - \begin{rightwordgroup}{Wireguard\\Header} - \bitbox{8}{type} & \bitbox{24}{reserved} \\ - \wordbox{1}{receiver} \\ - \wordbox{2}{counter} - \end{rightwordgroup} \\ - \begin{rightwordgroup}{Tunnelled\\Proxy\\Packet} - \wordbox[tlr]{1}{IPv4 Header} \\ - \wordbox[blr]{1}{$\cdots$}\\ - \begin{leftwordgroup}{UDP Header} - \bitbox{16}{Source port} & \bitbox{16}{Destination port} \\ - \bitbox{16}{Length} & \bitbox{16}{Checksum} - \end{leftwordgroup} \\ - \begin{leftwordgroup}{CC\\Header} - \bitbox{32}{Acknowledgement number} \\ - \bitbox{32}{Negative acknowledgement number} \\ - \bitbox{32}{Sequence number} - \end{leftwordgroup} \\ - \wordbox[tlr]{1}{Proxied IP packet} \\ - \skippedwords\\ - \wordbox[blr]{1}{} - \end{rightwordgroup} - \end{bytefield} - - \caption{Packet structure for a configuration with a Wireguard client in front of my multipath proxy.} - \label{fig:whole-network-vpn-infront} - \end{fullpage} -\end{figure} +Packet structures for proxied packets in each of these cases are given in Appendix \ref{appendix:layered-security}, with Figure \ref{fig:whole-network-vpn-behind} and Figure \ref{fig:whole-network-vpn-infront} for the VPN Wireguard \citep{donenfeld_wireguard_2017}. In Figure \ref{fig:whole-network-vpn-infront}, the portals are only accessible via the VPN protected network. It can be seen that the packet in Figure \ref{fig:whole-network-vpn-infront} is shorter, given the removal of the message authentication code and the data sequence number. The data sequence number is unnecessary, given that Wireguard uses the same anti-replay algorithm, and thus replayed packets would have been caught entering the secure network. Further, the message authentication code is unnecessary, as the authenticity of packets is now guaranteed by Wireguard. Supporting and encouraging this layering of protocols provides a second benefit: if the security in my solution degrades with time, there are two options to repair it. One can either fix the open source application, or compose it with a security solution that is not broken, but perhaps provides redundant security guarantees, translating to additional overhead. To this end, the security features mentioned are all configurable. This allows for flexibility in implementation. diff --git a/3_Implementation/implementation.tex b/3_Implementation/implementation.tex index 63f7576..e3ac0c0 100644 --- a/3_Implementation/implementation.tex +++ b/3_Implementation/implementation.tex @@ -14,7 +14,7 @@ Implementation of the proxy is in two parts: software that provides a multipath layer 3 tunnel between two hosts, and the system configuration necessary to utilise this tunnel as a proxy. An overview of the software and system is presented in figure \ref{fig:dataflow-overview}. -This chapter will detail this implementation in three sections. The software will be described in sections \ref{section:implementation-software-structure} and \ref{section:implementation-producer-consumer}. Section \ref{section:implementation-software-structure} explains the software's structure and dataflow. Section \ref{section:implementation-producer-consumer} details the implementation of both TCP and UDP methods of transporting the tunnelled packets between the hosts. The system configuration will be described in section \ref{section:implementation-system-configuration}, along with a discussion of some of the oddities of multipath routing, such that a reader would have enough knowledge to implement the proxy. +This chapter will detail this implementation in three sections. The software will be described in sections \ref{section:implementation-packet-transport} and \ref{section:implementation-producer-consumer}. Section \ref{section:implementation-software-structure} explains the software's structure and dataflow. Section \ref{section:implementation-producer-consumer} details the implementation of both TCP and UDP methods of transporting the tunnelled packets between the hosts. The system configuration will be described in section \ref{section:implementation-system-configuration}, along with a discussion of some of the oddities of multipath routing, such that a reader would have enough knowledge to implement the proxy. \begin{sidewaysfigure} \includegraphics[width=\textheight]{overview.png} @@ -22,6 +22,122 @@ This chapter will detail this implementation in three sections. The software wil \label{fig:dataflow-overview} \end{sidewaysfigure} +% -------------------------------------------------------------------------- % +% -------------------------- Packet Transport ------------------------------ % +% -------------------------------------------------------------------------- % +\section{Packet Transport} +\label{section:implementation-packet-transport} + +As shown in figure \ref{fig:dataflow-overview} and described in section \ref{section:implementation-software-structure}, the interfaces through which transport for packets is provided between the two hosts are producers and consumers. A transport pair is then created between a consumer on one host and a producer on the other, where packets enter the consumer and exit the corresponding producer. Two methods for producers and consumers are implemented: TCP and UDP. As the greedy load balancing of this proxy relies on congestion control, TCP provided a base for a proof of concept, while UDP expands on this proof of concept to produce a usable solution. This section discusses, in section \ref{section:implementation-tcp}, the method of transporting discrete packets across the continuous byte stream of a TCP flow. Then, in section \ref{section:implementation-udp}, it goes on to discuss adding congestion control to UDP datagrams, while avoiding retransmissions. + +\subsection{TCP} +\label{section:implementation-tcp} + +The base implementation for producers and consumers takes advantage of TCP. The requirements for the load balancing given above to function are simple: flow control and congestion control. TCP provides both of these, so was an obvious initial solution. However, TCP also provides unnecessary overhead, which will go on to be discussed further. + +TCP is a stream-oriented connection, while the packets to be sent are discrete datagrams. That is, a TCP flow cannot be connected directly to a TUN adapter, as the TUN adapter expects discrete and formatted IP packets while the TCP connection sends a stream of bytes. To resolve this, each packet sent across a TCP flow is prefixed with the length of the packet. On the sending side, this involves writing the 32-bit length of the packet, followed by the packet itself. For the receiver, first 4 bytes are read to recover the length of the next packet, after which that many bytes are read. This successfully punctuates the stream-oriented connection into a packet-carrying connection. + +However, using TCP to tunnel TCP packets (known as TCP-over-TCP) can cause a degradation in performance in non-ideal circumstances \citep{honda_understanding_2005}. Further, using TCP to tunnel IP packets provides a superset of the required guarantees, in that reliable delivery and ordering are guaranteed. Reliable delivery can cause a decrease in performance for tunnelled flows which do not require reliable delivery, such as a live video stream - a live stream does not wish to wait for a packet to be redelivered from a portion that is already played, and thus will spend longer buffering than if it received the up to date packets instead. Ordering can limit performance when tunnelling multiple streams, as a packet for a phone call could already be received, but instead has to wait in a buffer for a packet for a download to arrive, increasing latency unnecessarily. + +Although the TCP implementation provides an excellent proof of concept and basic implementation, work moved to a second UDP implementation, aiming to solve some of these problems. However, the TCP implementation is functionally correct, so is left as an option, furthering the idea of flexibility maintained throughout this project. In cases where a connection that suffers particularly high packet loss is combined with one which is more stable, TCP could be employed on the high loss connection to limit overall packet loss. The effectiveness of such a solution would be implementation specific, so is left for the architect to decide. + +% --------------------------------- UDP ------------------------------------ % +\subsection{UDP} +\label{section:implementation-udp} + +To resolve the issues seen with TCP, an implementation using UDP was built as an alternative. UDP differs from TCP in that it provides almost no guarantees, and is based on sending discrete datagrams as opposed to a stream of bytes. However, UDP datagrams don't provide the congestion control or flow control required, so this must be built on top of the protocol. As the flow itself can be managed in userspace, opposed to the TCP flow which is managed in kernel space, more flexibility is available in implementation. This allows received packets to be immediately dispatched, with little regard for ordering. + +\subsection{Congestion Control} + +Congestion control is most commonly applied in the context of reliable delivery. This provides a significant benefit to TCP congestion control protocols: cumulative acknowledgements. As all of the bytes should always arrive eventually, unless the connection has faulted, the acknowledgement number (ACK) can simply be set to the highest received byte. Therefore, some adaptations are necessary for TCP congestion control algorithms to apply in an unreliable context. Firstly, for a packet based connection, ACKing specific bytes makes little sense - a packet is atomic, and is lost as a whole unit. To account for this, sequence numbers and their respective acknowledgements will be for entire packets, as opposed to per byte. Secondly, for an unreliable protocol, cumulative acknowledgements are not as simple. As packets are now allowed to never arrive within the correct function of the flow, a situation where a packet is never received would cause deadlock with an ACK that is simply set to the highest received sequence number, demonstrated in figure \ref{fig:sequence-ack-discontinuous}. Neither side can progress once the window is full, as the sender will not receive an ACK to free up space within the window, and the receiver will not receive the missing packet to increase the ACK. + +\begin{figure} + \hfill + \begin{subfigure}[t]{0.3\textwidth} + \centering + \begin{tabular}{|c|c|} + SEQ & ACK \\ + 1 & 0 \\ + 2 & 0 \\ + 3 & 2 \\ + 4 & 2 \\ + 5 & 2 \\ + 6 & 5 \\ + 6 & 6 + \end{tabular} + \caption{ACKs only responding to in order sequence numbers} + \label{fig:sequence-ack-continuous} + \end{subfigure}\hfill + \begin{subfigure}[t]{0.3\textwidth} + \centering + \begin{tabular}{|c|c|} + SEQ & ACK \\ + 1 & 0 \\ + 2 & 0 \\ + 3 & 2 \\ + 5 & 3 \\ + 6 & 3 \\ + 7 & 3 \\ + 7 & 3 + \end{tabular} + \caption{ACKs only responding to a missing sequence number} + \label{fig:sequence-ack-discontinuous} + \end{subfigure}\hfill + \begin{subfigure}[t]{0.35\textwidth} + \centering + \begin{tabular}{|c|c|c|} + SEQ & ACK & NACK \\ + 1 & 0 & 0 \\ + 2 & 0 & 0 \\ + 3 & 2 & 0 \\ + 5 & 2 & 0 \\ + 6 & 2 & 0 \\ + 7 & 6 & 4 \\ + 7 & 7 & 4 + \end{tabular} + \caption{ACKs and NACKs responding to a missing sequence number} + \label{fig:sequence-ack-nack-discontinuous} + \end{subfigure} + \caption{Congestion control responding to correct and missing sequence numbers of packets.} + \label{fig:sequence-ack-nack-comparison} + \hfill +\end{figure} + +I present a solution based on Negative Acknowledgements (NACKs). When the receiver believes that it will never receive a packet, it increases the NACK to the highest missing sequence number, and sets the ACK to one above the NACK. The ACK algorithm is then performed to grow the ACK as high as possible. This is simplified to any change in NACK representing at least one lost packet, which can be used by the specific congestion control algorithms to react. Though this usage of the NACK appears to provide a close approximation to ACKs on reliable delivery, the choice of how to use the ACK and NACK fields is delegated to the congestion controller implementation, allowing for different implementations if they better suit the method of congestion control. + +Given the decision to use ACKs and NACKs, the packet structure for UDP datagrams can now be designed. The chosen structure is given in figure \ref{fig:udp-packet-structure}. The congestion control header consists of the sequence number and the ACK and NACK, each 32-bit unsigned integers. + +\begin{figure} + \centering + \begin{bytefield}[bitwidth=0.6em]{32} + \bitheader{0-31} \\ + \begin{rightwordgroup}{UDP\\Header} + \bitbox{16}{Source port} & \bitbox{16}{Destination port} \\ + \bitbox{16}{Length} & \bitbox{16}{Checksum} + \end{rightwordgroup} \\ + \begin{rightwordgroup}{CC\\Header} + \bitbox{32}{Acknowledgement number} \\ + \bitbox{32}{Negative acknowledgement number} \\ + \bitbox{32}{Sequence number} + \end{rightwordgroup} \\ + \wordbox[tlr]{1}{Proxied IP packet} \\ + \skippedwords \\ + \wordbox[blr]{1}{} \\ + \begin{rightwordgroup}{Security\\Footer} + \wordbox[tlr]{1}{Security footer} \\ + \wordbox[blr]{1}{$\cdots$} + \end{rightwordgroup} + \end{bytefield} + \caption{UDP packet structure} + \label{fig:udp-packet-structure} +\end{figure} + +\subsubsection{New Reno} + +The first algorithm to be implemented for UDP Congestion Control is based on TCP New Reno. TCP New Reno is a well understood and powerful congestion control protocol. RTT estimation is performed by applying $RTT_{AVG} = RTT_{AVG}*(1-x) + RTT_{SAMPLE}*x$ for each newly received packet. Packet loss is measured in two ways: negative acknowledgements when a receiver receives a later packet than expected and has not received the preceding for $0.5*RTT$, and a sender timeout of $3*RTT$. The sender timeout exists to ensure that even if the only packet containing a NACK is dropped, the sender does not deadlock, though this case should be rare with a busy connection. + +To achieve the same curve as New Reno, there are two phases: exponential growth and congestion avoidance. On flow start, using a technique known as slow start, for every packet that is acknowledged, the window size is increased by one. When a packet loss is detected (using either of the two aforementioned methods), slow start ends, and the window size is halved. Now in congestion avoidance, the window size is increased by one for every full window of packets acknowledged without loss, instead of each individual packet. When a packet loss is detected, the window size is half, and congestion avoidance continues. + % -------------------------------------------------------------------------- % % ------------------------- Software Structure ----------------------------- % % -------------------------------------------------------------------------- % @@ -185,122 +301,6 @@ A directory tree of the repository is provided in figure \ref{fig:repository-str \label{fig:repository-structure} \end{figure} -% -------------------------------------------------------------------------- % -% -------------------------- Packet Transport ------------------------------ % -% -------------------------------------------------------------------------- % -\section{Packet Transport} -\label{section:implementation-producer-consumer} - -As shown in figure \ref{fig:dataflow-overview} and described in section \ref{section:implementation-software-structure}, the interfaces through which transport for packets is provided between the two hosts are producers and consumers. A transport pair is then created between a consumer on one host and a producer on the other, where packets enter the consumer and exit the corresponding producer. Two methods for producers and consumers are implemented: TCP and UDP. As the greedy load balancing of this proxy relies on congestion control, TCP provided a base for a proof of concept, while UDP expands on this proof of concept to produce a usable solution. This section discusses, in section \ref{section:implementation-tcp}, the method of transporting discrete packets across the continuous byte stream of a TCP flow. Then, in section \ref{section:implementation-udp}, it goes on to discuss adding congestion control to UDP datagrams, while avoiding retransmissions. - -\subsection{TCP} -\label{section:implementation-tcp} - -The base implementation for producers and consumers takes advantage of TCP. The requirements for the load balancing given above to function are simple: flow control and congestion control. TCP provides both of these, so was an obvious initial solution. However, TCP also provides unnecessary overhead, which will go on to be discussed further. - -TCP is a stream-oriented connection, while the packets to be sent are discrete datagrams. That is, a TCP flow cannot be connected directly to a TUN adapter, as the TUN adapter expects discrete and formatted IP packets while the TCP connection sends a stream of bytes. To resolve this, each packet sent across a TCP flow is prefixed with the length of the packet. On the sending side, this involves writing the 32-bit length of the packet, followed by the packet itself. For the receiver, first 4 bytes are read to recover the length of the next packet, after which that many bytes are read. This successfully punctuates the stream-oriented connection into a packet-carrying connection. - -However, using TCP to tunnel TCP packets (known as TCP-over-TCP) can cause a degradation in performance in non-ideal circumstances \citep{honda_understanding_2005}. Further, using TCP to tunnel IP packets provides a superset of the required guarantees, in that reliable delivery and ordering are guaranteed. Reliable delivery can cause a decrease in performance for tunnelled flows which do not require reliable delivery, such as a live video stream - a live stream does not wish to wait for a packet to be redelivered from a portion that is already played, and thus will spend longer buffering than if it received the up to date packets instead. Ordering can limit performance when tunnelling multiple streams, as a packet for a phone call could already be received, but instead has to wait in a buffer for a packet for a download to arrive, increasing latency unnecessarily. - -Although the TCP implementation provides an excellent proof of concept and basic implementation, work moved to a second UDP implementation, aiming to solve some of these problems. However, the TCP implementation is functionally correct, so is left as an option, furthering the idea of flexibility maintained throughout this project. In cases where a connection that suffers particularly high packet loss is combined with one which is more stable, TCP could be employed on the high loss connection to limit overall packet loss. The effectiveness of such a solution would be implementation specific, so is left for the architect to decide. - -% --------------------------------- UDP ------------------------------------ % -\subsection{UDP} -\label{section:implementation-udp} - -To resolve the issues seen with TCP, an implementation using UDP was built as an alternative. UDP differs from TCP in that it provides almost no guarantees, and is based on sending discrete datagrams as opposed to a stream of bytes. However, UDP datagrams don't provide the congestion control or flow control required, so this must be built on top of the protocol. As the flow itself can be managed in userspace, opposed to the TCP flow which is managed in kernel space, more flexibility is available in implementation. This allows received packets to be immediately dispatched, with little regard for ordering. - -\subsection{Congestion Control} - -Congestion control is most commonly applied in the context of reliable delivery. This provides a significant benefit to TCP congestion control protocols: cumulative acknowledgements. As all of the bytes should always arrive eventually, unless the connection has faulted, the acknowledgement number (ACK) can simply be set to the highest received byte. Therefore, some adaptations are necessary for TCP congestion control algorithms to apply in an unreliable context. Firstly, for a packet based connection, ACKing specific bytes makes little sense - a packet is atomic, and is lost as a whole unit. To account for this, sequence numbers and their respective acknowledgements will be for entire packets, as opposed to per byte. Secondly, for an unreliable protocol, cumulative acknowledgements are not as simple. As packets are now allowed to never arrive within the correct function of the flow, a situation where a packet is never received would cause deadlock with an ACK that is simply set to the highest received sequence number, demonstrated in figure \ref{fig:sequence-ack-discontinuous}. Neither side can progress once the window is full, as the sender will not receive an ACK to free up space within the window, and the receiver will not receive the missing packet to increase the ACK. - -\begin{figure} - \hfill - \begin{subfigure}[t]{0.3\textwidth} - \centering - \begin{tabular}{|c|c|} - SEQ & ACK \\ - 1 & 0 \\ - 2 & 0 \\ - 3 & 2 \\ - 4 & 2 \\ - 5 & 2 \\ - 6 & 5 \\ - 6 & 6 - \end{tabular} - \caption{ACKs only responding to in order sequence numbers} - \label{fig:sequence-ack-continuous} - \end{subfigure}\hfill - \begin{subfigure}[t]{0.3\textwidth} - \centering - \begin{tabular}{|c|c|} - SEQ & ACK \\ - 1 & 0 \\ - 2 & 0 \\ - 3 & 2 \\ - 5 & 3 \\ - 6 & 3 \\ - 7 & 3 \\ - 7 & 3 - \end{tabular} - \caption{ACKs only responding to a missing sequence number} - \label{fig:sequence-ack-discontinuous} - \end{subfigure}\hfill - \begin{subfigure}[t]{0.35\textwidth} - \centering - \begin{tabular}{|c|c|c|} - SEQ & ACK & NACK \\ - 1 & 0 & 0 \\ - 2 & 0 & 0 \\ - 3 & 2 & 0 \\ - 5 & 2 & 0 \\ - 6 & 2 & 0 \\ - 7 & 6 & 4 \\ - 7 & 7 & 4 - \end{tabular} - \caption{ACKs and NACKs responding to a missing sequence number} - \label{fig:sequence-ack-nack-discontinuous} - \end{subfigure} - \caption{Congestion control responding to correct and missing sequence numbers of packets.} - \label{fig:sequence-ack-nack-comparison} - \hfill -\end{figure} - -I present a solution based on Negative Acknowledgements (NACKs). When the receiver believes that it will never receive a packet, it increases the NACK to the highest missing sequence number, and sets the ACK to one above the NACK. The ACK algorithm is then performed to grow the ACK as high as possible. This is simplified to any change in NACK representing at least one lost packet, which can be used by the specific congestion control algorithms to react. Though this usage of the NACK appears to provide a close approximation to ACKs on reliable delivery, the choice of how to use the ACK and NACK fields is delegated to the congestion controller implementation, allowing for different implementations if they better suit the method of congestion control. - -Given the decision to use ACKs and NACKs, the packet structure for UDP datagrams can now be designed. The chosen structure is given in figure \ref{fig:udp-packet-structure}. The congestion control header consists of the sequence number and the ACK and NACK, each 32-bit unsigned integers. - -\begin{figure} - \centering - \begin{bytefield}[bitwidth=0.6em]{32} - \bitheader{0-31} \\ - \begin{rightwordgroup}{UDP\\Header} - \bitbox{16}{Source port} & \bitbox{16}{Destination port} \\ - \bitbox{16}{Length} & \bitbox{16}{Checksum} - \end{rightwordgroup} \\ - \begin{rightwordgroup}{CC\\Header} - \bitbox{32}{Acknowledgement number} \\ - \bitbox{32}{Negative acknowledgement number} \\ - \bitbox{32}{Sequence number} - \end{rightwordgroup} \\ - \wordbox[tlr]{1}{Proxied IP packet} \\ - \skippedwords \\ - \wordbox[blr]{1}{} \\ - \begin{rightwordgroup}{Security\\Footer} - \wordbox[tlr]{1}{Security footer} \\ - \wordbox[blr]{1}{$\cdots$} - \end{rightwordgroup} - \end{bytefield} - \caption{UDP packet structure} - \label{fig:udp-packet-structure} -\end{figure} - -\subsubsection{New Reno} - -The first algorithm to be implemented for UDP Congestion Control is based on TCP New Reno. TCP New Reno is a well understood and powerful congestion control protocol. RTT estimation is performed by applying $RTT_{AVG} = RTT_{AVG}*(1-x) + RTT_{SAMPLE}*x$ for each newly received packet. Packet loss is measured in two ways: negative acknowledgements when a receiver receives a later packet than expected and has not received the preceding for $0.5*RTT$, and a sender timeout of $3*RTT$. The sender timeout exists to ensure that even if the only packet containing a NACK is dropped, the sender does not deadlock, though this case should be rare with a busy connection. - -To achieve the same curve as New Reno, there are two phases: exponential growth and congestion avoidance. On flow start, using a technique known as slow start, for every packet that is acknowledged, the window size is increased by one. When a packet loss is detected (using either of the two aforementioned methods), slow start ends, and the window size is halved. Now in congestion avoidance, the window size is increased by one for every full window of packets acknowledged without loss, instead of each individual packet. When a packet loss is detected, the window size is half, and congestion avoidance continues. - % -------------------------------------------------------------------------- % % ------------------------ System Configuration ---------------------------- % % -------------------------------------------------------------------------- % diff --git a/A1_LanguageSamples/languagesamples.tex b/A1_LanguageSamples/languagesamples.tex index 36abe8c..7216ed7 100644 --- a/A1_LanguageSamples/languagesamples.tex +++ b/A1_LanguageSamples/languagesamples.tex @@ -5,24 +5,24 @@ \label{appendix:language-samples} \begin{figure} - \inputminted[firstline=1,lastline=48]{cpp}{LanguageSamples/Samples/main.cpp} + \inputminted{rust}{A1_LanguageSamples/Samples/main.rs} + \caption{A sample script written in Rust to collect packets from a TUN interface and print them from multiple threads} + \label{fig:rust-tun-sample} +\end{figure} + +\begin{figure} + \inputminted[firstline=1,lastline=48]{cpp}{A1_LanguageSamples/Samples/main.cpp} \caption{A sample script written in C++ to collect packets from a TUN interface and print them from multiple threads} \label{fig:cpp-tun-sample} \end{figure} \begin{figure} \ContinuedFloat - \inputminted[firstline=49]{cpp}{LanguageSamples/Samples/main.cpp} + \inputminted[firstline=49]{cpp}{A1_LanguageSamples/Samples/main.cpp} \end{figure} \begin{figure} - \inputminted{rust}{LanguageSamples/Samples/main.rs} - \caption{A sample script written in Rust to collect packets from a TUN interface and print them from multiple threads} - \label{fig:rust-tun-sample} -\end{figure} - -\begin{figure} - \inputminted{go}{LanguageSamples/Samples/main.go} + \inputminted{go}{A1_LanguageSamples/Samples/main.go} \caption{A sample script written in Go to collect packets from a TUN interface and print them from multiple threads} \label{fig:go-tun-sample} \end{figure} diff --git a/A2_LayeredSecurity/layeredsecurity.tex b/A2_LayeredSecurity/layeredsecurity.tex new file mode 100644 index 0000000..ed137db --- /dev/null +++ b/A2_LayeredSecurity/layeredsecurity.tex @@ -0,0 +1,88 @@ +%!TEX root = ../thesis.tex +% ********************** Thesis Appendix B - Layered Security ************************* + +\chapter{Layered Security Packet Diagrams} +\label{appendix:layered-security} + +\begin{figure} + \begin{leftfullpage} + \centering + \begin{bytefield}[bitwidth=0.6em]{32} + \bitheader{0-31} \\ + \wordbox[tlr]{1}{IPv4 Header} \\ + \wordbox[blr]{1}{$\cdots$} \\ + \begin{rightwordgroup}{UDP\\Header} + \bitbox{16}{Source port} & \bitbox{16}{Destination port} \\ + \bitbox{16}{Length} & \bitbox{16}{Checksum} + \end{rightwordgroup} \\ + \begin{rightwordgroup}{CC\\Header} + \bitbox{32}{Acknowledgement number} \\ + \bitbox{32}{Negative acknowledgement number} \\ + \bitbox{32}{Sequence number} + \end{rightwordgroup} \\ + \begin{rightwordgroup}{Proxied\\Wireguard\\Packet} + \wordbox[tlr]{1}{IPv4 Header} \\ + \wordbox[blr]{1}{$\cdots$} \\ + \begin{leftwordgroup}{UDP Header} + \bitbox{16}{Source port} & \bitbox{16}{Destination port} \\ + \bitbox{16}{Length} & \bitbox{16}{Checksum} + \end{leftwordgroup} \\ + \begin{leftwordgroup}{Wireguard\\Header} + \bitbox{8}{type} & \bitbox{24}{reserved} \\ + \wordbox{1}{receiver} \\ + \wordbox{2}{counter} + \end{leftwordgroup} \\ + \wordbox[tlr]{1}{Proxied IP packet} \\ + \skippedwords\\ + \wordbox[blr]{1}{} + \end{rightwordgroup} \\ + \begin{rightwordgroup}{Security\\Footer} + \bitbox{32}{Data sequence number} \\ + \wordbox[tlr]{1}{Message authentication code} \\ + \wordbox[blr]{1}{$\cdots$} + \end{rightwordgroup} + \end{bytefield} + + \caption{Packet structure for a configuration with a Wireguard client behind my multipath proxy.} + \label{fig:whole-network-vpn-behind} + \end{leftfullpage} +\end{figure} + +\begin{figure} + \begin{fullpage} + \centering + \begin{bytefield}[bitwidth=0.6em]{32} + \bitheader{0-31} \\ + \wordbox[tlr]{1}{IPv4 Header} \\ + \wordbox[blr]{1}{$\cdots$}\\ + \begin{rightwordgroup}{UDP\\Header} + \bitbox{16}{Source port} & \bitbox{16}{Destination port} \\ + \bitbox{16}{Length} & \bitbox{16}{Checksum} + \end{rightwordgroup} \\ + \begin{rightwordgroup}{Wireguard\\Header} + \bitbox{8}{type} & \bitbox{24}{reserved} \\ + \wordbox{1}{receiver} \\ + \wordbox{2}{counter} + \end{rightwordgroup} \\ + \begin{rightwordgroup}{Tunnelled\\Proxy\\Packet} + \wordbox[tlr]{1}{IPv4 Header} \\ + \wordbox[blr]{1}{$\cdots$}\\ + \begin{leftwordgroup}{UDP Header} + \bitbox{16}{Source port} & \bitbox{16}{Destination port} \\ + \bitbox{16}{Length} & \bitbox{16}{Checksum} + \end{leftwordgroup} \\ + \begin{leftwordgroup}{CC\\Header} + \bitbox{32}{Acknowledgement number} \\ + \bitbox{32}{Negative acknowledgement number} \\ + \bitbox{32}{Sequence number} + \end{leftwordgroup} \\ + \wordbox[tlr]{1}{Proxied IP packet} \\ + \skippedwords\\ + \wordbox[blr]{1}{} + \end{rightwordgroup} + \end{bytefield} + + \caption{Packet structure for a configuration with a Wireguard client in front of my multipath proxy.} + \label{fig:whole-network-vpn-infront} + \end{fullpage} +\end{figure} diff --git a/thesis-info.tex b/thesis-info.tex index 43cb02c..878d568 100644 --- a/thesis-info.tex +++ b/thesis-info.tex @@ -62,7 +62,7 @@ %\renewcommand{\submissiontext}{change the default text here if needed} %% Full title of the Degree -\degreetitle{Bachelor of Arts} +\degreetitle{Computer Science Tripos - Part II} %% College affiliation (optional) \college{Queens' College} diff --git a/thesis.tex b/thesis.tex index 8e7691a..51eba5b 100644 --- a/thesis.tex +++ b/thesis.tex @@ -175,9 +175,10 @@ %TC:ignore \begin{appendices} % Using appendices environment for more functunality -\include{LanguageSamples/languagesamples} -\include{OutboundGraphs/outboundgraphs} -\include{Proposal/proposal} +\include{A1_LanguageSamples/languagesamples} +\include{A2_LayeredSecurity/layeredsecurity} +\include{A3_OutboundGraphs/outboundgraphs} +\include{A4_ProjectProposal/proposal} \end{appendices} From 3775d4523d1238ef581e7ab76035ee0bdf7f72a8 Mon Sep 17 00:00:00 2001 From: jsh77 Date: Thu, 13 May 2021 14:19:15 +0000 Subject: [PATCH 2/2] Update on Overleaf. --- 3_Implementation/implementation.tex | 232 ++++++++++++++-------------- 1 file changed, 116 insertions(+), 116 deletions(-) diff --git a/3_Implementation/implementation.tex b/3_Implementation/implementation.tex index e3ac0c0..a4e94f9 100644 --- a/3_Implementation/implementation.tex +++ b/3_Implementation/implementation.tex @@ -22,122 +22,6 @@ This chapter will detail this implementation in three sections. The software wil \label{fig:dataflow-overview} \end{sidewaysfigure} -% -------------------------------------------------------------------------- % -% -------------------------- Packet Transport ------------------------------ % -% -------------------------------------------------------------------------- % -\section{Packet Transport} -\label{section:implementation-packet-transport} - -As shown in figure \ref{fig:dataflow-overview} and described in section \ref{section:implementation-software-structure}, the interfaces through which transport for packets is provided between the two hosts are producers and consumers. A transport pair is then created between a consumer on one host and a producer on the other, where packets enter the consumer and exit the corresponding producer. Two methods for producers and consumers are implemented: TCP and UDP. As the greedy load balancing of this proxy relies on congestion control, TCP provided a base for a proof of concept, while UDP expands on this proof of concept to produce a usable solution. This section discusses, in section \ref{section:implementation-tcp}, the method of transporting discrete packets across the continuous byte stream of a TCP flow. Then, in section \ref{section:implementation-udp}, it goes on to discuss adding congestion control to UDP datagrams, while avoiding retransmissions. - -\subsection{TCP} -\label{section:implementation-tcp} - -The base implementation for producers and consumers takes advantage of TCP. The requirements for the load balancing given above to function are simple: flow control and congestion control. TCP provides both of these, so was an obvious initial solution. However, TCP also provides unnecessary overhead, which will go on to be discussed further. - -TCP is a stream-oriented connection, while the packets to be sent are discrete datagrams. That is, a TCP flow cannot be connected directly to a TUN adapter, as the TUN adapter expects discrete and formatted IP packets while the TCP connection sends a stream of bytes. To resolve this, each packet sent across a TCP flow is prefixed with the length of the packet. On the sending side, this involves writing the 32-bit length of the packet, followed by the packet itself. For the receiver, first 4 bytes are read to recover the length of the next packet, after which that many bytes are read. This successfully punctuates the stream-oriented connection into a packet-carrying connection. - -However, using TCP to tunnel TCP packets (known as TCP-over-TCP) can cause a degradation in performance in non-ideal circumstances \citep{honda_understanding_2005}. Further, using TCP to tunnel IP packets provides a superset of the required guarantees, in that reliable delivery and ordering are guaranteed. Reliable delivery can cause a decrease in performance for tunnelled flows which do not require reliable delivery, such as a live video stream - a live stream does not wish to wait for a packet to be redelivered from a portion that is already played, and thus will spend longer buffering than if it received the up to date packets instead. Ordering can limit performance when tunnelling multiple streams, as a packet for a phone call could already be received, but instead has to wait in a buffer for a packet for a download to arrive, increasing latency unnecessarily. - -Although the TCP implementation provides an excellent proof of concept and basic implementation, work moved to a second UDP implementation, aiming to solve some of these problems. However, the TCP implementation is functionally correct, so is left as an option, furthering the idea of flexibility maintained throughout this project. In cases where a connection that suffers particularly high packet loss is combined with one which is more stable, TCP could be employed on the high loss connection to limit overall packet loss. The effectiveness of such a solution would be implementation specific, so is left for the architect to decide. - -% --------------------------------- UDP ------------------------------------ % -\subsection{UDP} -\label{section:implementation-udp} - -To resolve the issues seen with TCP, an implementation using UDP was built as an alternative. UDP differs from TCP in that it provides almost no guarantees, and is based on sending discrete datagrams as opposed to a stream of bytes. However, UDP datagrams don't provide the congestion control or flow control required, so this must be built on top of the protocol. As the flow itself can be managed in userspace, opposed to the TCP flow which is managed in kernel space, more flexibility is available in implementation. This allows received packets to be immediately dispatched, with little regard for ordering. - -\subsection{Congestion Control} - -Congestion control is most commonly applied in the context of reliable delivery. This provides a significant benefit to TCP congestion control protocols: cumulative acknowledgements. As all of the bytes should always arrive eventually, unless the connection has faulted, the acknowledgement number (ACK) can simply be set to the highest received byte. Therefore, some adaptations are necessary for TCP congestion control algorithms to apply in an unreliable context. Firstly, for a packet based connection, ACKing specific bytes makes little sense - a packet is atomic, and is lost as a whole unit. To account for this, sequence numbers and their respective acknowledgements will be for entire packets, as opposed to per byte. Secondly, for an unreliable protocol, cumulative acknowledgements are not as simple. As packets are now allowed to never arrive within the correct function of the flow, a situation where a packet is never received would cause deadlock with an ACK that is simply set to the highest received sequence number, demonstrated in figure \ref{fig:sequence-ack-discontinuous}. Neither side can progress once the window is full, as the sender will not receive an ACK to free up space within the window, and the receiver will not receive the missing packet to increase the ACK. - -\begin{figure} - \hfill - \begin{subfigure}[t]{0.3\textwidth} - \centering - \begin{tabular}{|c|c|} - SEQ & ACK \\ - 1 & 0 \\ - 2 & 0 \\ - 3 & 2 \\ - 4 & 2 \\ - 5 & 2 \\ - 6 & 5 \\ - 6 & 6 - \end{tabular} - \caption{ACKs only responding to in order sequence numbers} - \label{fig:sequence-ack-continuous} - \end{subfigure}\hfill - \begin{subfigure}[t]{0.3\textwidth} - \centering - \begin{tabular}{|c|c|} - SEQ & ACK \\ - 1 & 0 \\ - 2 & 0 \\ - 3 & 2 \\ - 5 & 3 \\ - 6 & 3 \\ - 7 & 3 \\ - 7 & 3 - \end{tabular} - \caption{ACKs only responding to a missing sequence number} - \label{fig:sequence-ack-discontinuous} - \end{subfigure}\hfill - \begin{subfigure}[t]{0.35\textwidth} - \centering - \begin{tabular}{|c|c|c|} - SEQ & ACK & NACK \\ - 1 & 0 & 0 \\ - 2 & 0 & 0 \\ - 3 & 2 & 0 \\ - 5 & 2 & 0 \\ - 6 & 2 & 0 \\ - 7 & 6 & 4 \\ - 7 & 7 & 4 - \end{tabular} - \caption{ACKs and NACKs responding to a missing sequence number} - \label{fig:sequence-ack-nack-discontinuous} - \end{subfigure} - \caption{Congestion control responding to correct and missing sequence numbers of packets.} - \label{fig:sequence-ack-nack-comparison} - \hfill -\end{figure} - -I present a solution based on Negative Acknowledgements (NACKs). When the receiver believes that it will never receive a packet, it increases the NACK to the highest missing sequence number, and sets the ACK to one above the NACK. The ACK algorithm is then performed to grow the ACK as high as possible. This is simplified to any change in NACK representing at least one lost packet, which can be used by the specific congestion control algorithms to react. Though this usage of the NACK appears to provide a close approximation to ACKs on reliable delivery, the choice of how to use the ACK and NACK fields is delegated to the congestion controller implementation, allowing for different implementations if they better suit the method of congestion control. - -Given the decision to use ACKs and NACKs, the packet structure for UDP datagrams can now be designed. The chosen structure is given in figure \ref{fig:udp-packet-structure}. The congestion control header consists of the sequence number and the ACK and NACK, each 32-bit unsigned integers. - -\begin{figure} - \centering - \begin{bytefield}[bitwidth=0.6em]{32} - \bitheader{0-31} \\ - \begin{rightwordgroup}{UDP\\Header} - \bitbox{16}{Source port} & \bitbox{16}{Destination port} \\ - \bitbox{16}{Length} & \bitbox{16}{Checksum} - \end{rightwordgroup} \\ - \begin{rightwordgroup}{CC\\Header} - \bitbox{32}{Acknowledgement number} \\ - \bitbox{32}{Negative acknowledgement number} \\ - \bitbox{32}{Sequence number} - \end{rightwordgroup} \\ - \wordbox[tlr]{1}{Proxied IP packet} \\ - \skippedwords \\ - \wordbox[blr]{1}{} \\ - \begin{rightwordgroup}{Security\\Footer} - \wordbox[tlr]{1}{Security footer} \\ - \wordbox[blr]{1}{$\cdots$} - \end{rightwordgroup} - \end{bytefield} - \caption{UDP packet structure} - \label{fig:udp-packet-structure} -\end{figure} - -\subsubsection{New Reno} - -The first algorithm to be implemented for UDP Congestion Control is based on TCP New Reno. TCP New Reno is a well understood and powerful congestion control protocol. RTT estimation is performed by applying $RTT_{AVG} = RTT_{AVG}*(1-x) + RTT_{SAMPLE}*x$ for each newly received packet. Packet loss is measured in two ways: negative acknowledgements when a receiver receives a later packet than expected and has not received the preceding for $0.5*RTT$, and a sender timeout of $3*RTT$. The sender timeout exists to ensure that even if the only packet containing a NACK is dropped, the sender does not deadlock, though this case should be rare with a busy connection. - -To achieve the same curve as New Reno, there are two phases: exponential growth and congestion avoidance. On flow start, using a technique known as slow start, for every packet that is acknowledged, the window size is increased by one. When a packet loss is detected (using either of the two aforementioned methods), slow start ends, and the window size is halved. Now in congestion avoidance, the window size is increased by one for every full window of packets acknowledged without loss, instead of each individual packet. When a packet loss is detected, the window size is half, and congestion avoidance continues. - % -------------------------------------------------------------------------- % % ------------------------- Software Structure ----------------------------- % % -------------------------------------------------------------------------- % @@ -301,6 +185,122 @@ A directory tree of the repository is provided in figure \ref{fig:repository-str \label{fig:repository-structure} \end{figure} +% -------------------------------------------------------------------------- % +% -------------------------- Packet Transport ------------------------------ % +% -------------------------------------------------------------------------- % +\section{Packet Transport} +\label{section:implementation-packet-transport} + +As shown in figure \ref{fig:dataflow-overview} and described in section \ref{section:implementation-software-structure}, the interfaces through which transport for packets is provided between the two hosts are producers and consumers. A transport pair is then created between a consumer on one host and a producer on the other, where packets enter the consumer and exit the corresponding producer. Two methods for producers and consumers are implemented: TCP and UDP. As the greedy load balancing of this proxy relies on congestion control, TCP provided a base for a proof of concept, while UDP expands on this proof of concept to produce a usable solution. This section discusses, in section \ref{section:implementation-tcp}, the method of transporting discrete packets across the continuous byte stream of a TCP flow. Then, in section \ref{section:implementation-udp}, it goes on to discuss adding congestion control to UDP datagrams, while avoiding retransmissions. + +\subsection{TCP} +\label{section:implementation-tcp} + +The base implementation for producers and consumers takes advantage of TCP. The requirements for the load balancing given above to function are simple: flow control and congestion control. TCP provides both of these, so was an obvious initial solution. However, TCP also provides unnecessary overhead, which will go on to be discussed further. + +TCP is a stream-oriented connection, while the packets to be sent are discrete datagrams. That is, a TCP flow cannot be connected directly to a TUN adapter, as the TUN adapter expects discrete and formatted IP packets while the TCP connection sends a stream of bytes. To resolve this, each packet sent across a TCP flow is prefixed with the length of the packet. On the sending side, this involves writing the 32-bit length of the packet, followed by the packet itself. For the receiver, first 4 bytes are read to recover the length of the next packet, after which that many bytes are read. This successfully punctuates the stream-oriented connection into a packet-carrying connection. + +However, using TCP to tunnel TCP packets (known as TCP-over-TCP) can cause a degradation in performance in non-ideal circumstances \citep{honda_understanding_2005}. Further, using TCP to tunnel IP packets provides a superset of the required guarantees, in that reliable delivery and ordering are guaranteed. Reliable delivery can cause a decrease in performance for tunnelled flows which do not require reliable delivery, such as a live video stream - a live stream does not wish to wait for a packet to be redelivered from a portion that is already played, and thus will spend longer buffering than if it received the up to date packets instead. Ordering can limit performance when tunnelling multiple streams, as a packet for a phone call could already be received, but instead has to wait in a buffer for a packet for a download to arrive, increasing latency unnecessarily. + +Although the TCP implementation provides an excellent proof of concept and basic implementation, work moved to a second UDP implementation, aiming to solve some of these problems. However, the TCP implementation is functionally correct, so is left as an option, furthering the idea of flexibility maintained throughout this project. In cases where a connection that suffers particularly high packet loss is combined with one which is more stable, TCP could be employed on the high loss connection to limit overall packet loss. The effectiveness of such a solution would be implementation specific, so is left for the architect to decide. + +% --------------------------------- UDP ------------------------------------ % +\subsection{UDP} +\label{section:implementation-udp} + +To resolve the issues seen with TCP, an implementation using UDP was built as an alternative. UDP differs from TCP in that it provides almost no guarantees, and is based on sending discrete datagrams as opposed to a stream of bytes. However, UDP datagrams don't provide the congestion control or flow control required, so this must be built on top of the protocol. As the flow itself can be managed in userspace, opposed to the TCP flow which is managed in kernel space, more flexibility is available in implementation. This allows received packets to be immediately dispatched, with little regard for ordering. + +\subsection{Congestion Control} + +Congestion control is most commonly applied in the context of reliable delivery. This provides a significant benefit to TCP congestion control protocols: cumulative acknowledgements. As all of the bytes should always arrive eventually, unless the connection has faulted, the acknowledgement number (ACK) can simply be set to the highest received byte. Therefore, some adaptations are necessary for TCP congestion control algorithms to apply in an unreliable context. Firstly, for a packet based connection, ACKing specific bytes makes little sense - a packet is atomic, and is lost as a whole unit. To account for this, sequence numbers and their respective acknowledgements will be for entire packets, as opposed to per byte. Secondly, for an unreliable protocol, cumulative acknowledgements are not as simple. As packets are now allowed to never arrive within the correct function of the flow, a situation where a packet is never received would cause deadlock with an ACK that is simply set to the highest received sequence number, demonstrated in figure \ref{fig:sequence-ack-discontinuous}. Neither side can progress once the window is full, as the sender will not receive an ACK to free up space within the window, and the receiver will not receive the missing packet to increase the ACK. + +\begin{figure} + \hfill + \begin{subfigure}[t]{0.3\textwidth} + \centering + \begin{tabular}{|c|c|} + SEQ & ACK \\ + 1 & 0 \\ + 2 & 0 \\ + 3 & 2 \\ + 4 & 2 \\ + 5 & 2 \\ + 6 & 5 \\ + 6 & 6 + \end{tabular} + \caption{ACKs only responding to in order sequence numbers} + \label{fig:sequence-ack-continuous} + \end{subfigure}\hfill + \begin{subfigure}[t]{0.3\textwidth} + \centering + \begin{tabular}{|c|c|} + SEQ & ACK \\ + 1 & 0 \\ + 2 & 0 \\ + 3 & 2 \\ + 5 & 3 \\ + 6 & 3 \\ + 7 & 3 \\ + 7 & 3 + \end{tabular} + \caption{ACKs only responding to a missing sequence number} + \label{fig:sequence-ack-discontinuous} + \end{subfigure}\hfill + \begin{subfigure}[t]{0.35\textwidth} + \centering + \begin{tabular}{|c|c|c|} + SEQ & ACK & NACK \\ + 1 & 0 & 0 \\ + 2 & 0 & 0 \\ + 3 & 2 & 0 \\ + 5 & 2 & 0 \\ + 6 & 2 & 0 \\ + 7 & 6 & 4 \\ + 7 & 7 & 4 + \end{tabular} + \caption{ACKs and NACKs responding to a missing sequence number} + \label{fig:sequence-ack-nack-discontinuous} + \end{subfigure} + \caption{Congestion control responding to correct and missing sequence numbers of packets.} + \label{fig:sequence-ack-nack-comparison} + \hfill +\end{figure} + +I present a solution based on Negative Acknowledgements (NACKs). When the receiver believes that it will never receive a packet, it increases the NACK to the highest missing sequence number, and sets the ACK to one above the NACK. The ACK algorithm is then performed to grow the ACK as high as possible. This is simplified to any change in NACK representing at least one lost packet, which can be used by the specific congestion control algorithms to react. Though this usage of the NACK appears to provide a close approximation to ACKs on reliable delivery, the choice of how to use the ACK and NACK fields is delegated to the congestion controller implementation, allowing for different implementations if they better suit the method of congestion control. + +Given the decision to use ACKs and NACKs, the packet structure for UDP datagrams can now be designed. The chosen structure is given in figure \ref{fig:udp-packet-structure}. The congestion control header consists of the sequence number and the ACK and NACK, each 32-bit unsigned integers. + +\begin{figure} + \centering + \begin{bytefield}[bitwidth=0.6em]{32} + \bitheader{0-31} \\ + \begin{rightwordgroup}{UDP\\Header} + \bitbox{16}{Source port} & \bitbox{16}{Destination port} \\ + \bitbox{16}{Length} & \bitbox{16}{Checksum} + \end{rightwordgroup} \\ + \begin{rightwordgroup}{CC\\Header} + \bitbox{32}{Acknowledgement number} \\ + \bitbox{32}{Negative acknowledgement number} \\ + \bitbox{32}{Sequence number} + \end{rightwordgroup} \\ + \wordbox[tlr]{1}{Proxied IP packet} \\ + \skippedwords \\ + \wordbox[blr]{1}{} \\ + \begin{rightwordgroup}{Security\\Footer} + \wordbox[tlr]{1}{Security footer} \\ + \wordbox[blr]{1}{$\cdots$} + \end{rightwordgroup} + \end{bytefield} + \caption{UDP packet structure} + \label{fig:udp-packet-structure} +\end{figure} + +\subsubsection{New Reno} + +The first algorithm to be implemented for UDP Congestion Control is based on TCP New Reno. TCP New Reno is a well understood and powerful congestion control protocol. RTT estimation is performed by applying $RTT_{AVG} = RTT_{AVG}*(1-x) + RTT_{SAMPLE}*x$ for each newly received packet. Packet loss is measured in two ways: negative acknowledgements when a receiver receives a later packet than expected and has not received the preceding for $0.5*RTT$, and a sender timeout of $3*RTT$. The sender timeout exists to ensure that even if the only packet containing a NACK is dropped, the sender does not deadlock, though this case should be rare with a busy connection. + +To achieve the same curve as New Reno, there are two phases: exponential growth and congestion avoidance. On flow start, using a technique known as slow start, for every packet that is acknowledged, the window size is increased by one. When a packet loss is detected (using either of the two aforementioned methods), slow start ends, and the window size is halved. Now in congestion avoidance, the window size is increased by one for every full window of packets acknowledged without loss, instead of each individual packet. When a packet loss is detected, the window size is half, and congestion avoidance continues. + % -------------------------------------------------------------------------- % % ------------------------ System Configuration ---------------------------- % % -------------------------------------------------------------------------- %