FF is a proxy server which enables you to fire and forget HTTP requests. That is, sending a HTTP request to a remote server, without waiting for a response or even the network latency required to establish a connection to that server.
Additionally, FF provides the ability to protect sensitive payloads by encrypting the data in transit between both the client and upstream servers.
Disclaimer: This project was merely a learning exercise as well as my first sizeable project delving into C or even systems programming as a whole. If you somehow manage to find a use-case, please do not use this in production.
In summary, FF proxy server listens for HTTP requests over UDP and forwards them to their destination server over TCP for processing.
For the readers not familiar with the protocols discussed here, read on. HTTP requests and responses are almost always transmitted over a TCP connection. The TCP protocol ensures that data is exchanged accurately and reliably. In doing so, much of the communication between a client and server is for the purpose of verifying the has been data exchanged correctly rather than performing the data exchange itself.
To establish a TCP connection, a "handshake" must be completed between the client and the server using an exchange of SYN and ACK packets.
In ELI5 terms:
- the client says "hello, here is a random number 123456" to the server
- the server responds with a "hi there, i received your number 123456, my random number is 654321"
- finally the client says "thanks for your number, here is the data I want to send you ...".
The so-called random numbers are known as sequence numbers and correspond to the current offset of the data sent or received over the connection, relative to the initial sequence number.
However I digress, the TCP handshake process involves transferring, at minimum, two packets between the client and remote server before data is able to be exchanged across the connection, hence the client must wait for at least one network round trip before initiating a HTTP request.
On the other hand, we have the UDP protocol. UDP is very simple comparatively and does not provide the reliability guarantees of TCP. Essentially UDP supports:
- sending independent packets of data
- receiving independent packets of data
- broadcasting packets to multiple hosts (cool but not relevant here)
UDP lets us avoid the overhead of the network by removing any need for a handshake process before data can be exchanged. However HTTP web servers typically do not listen for traffic on UDP ports hence is not a viable solution on it's own.
FF Proxy makes it possible to send HTTP requests over UDP by acting as the middle man. Listening for HTTP requests over UDP and forwarding the the requests to the destination web server over TCP.
Hence FF proxy allows clients to reduce HTTP request latency to near zero at the cost not receiving the HTTP response from the remote server or any guarantees that the request was even received.
FF supports forwarding a raw HTTP request message encapsulated within a single UDP packet. However UDP packet sizes are often restricted by a path MTU, often less than 1500 bytes per packet.
To support forwarding larger HTTP requests, FF implements it's own protocol layer on top of UDP. This protocol supports the fragmentation of HTTP requests into a stream of UDP packets. These packets are then reassembled as they are received by an FF proxy and consequently, the HTTP request is forwarded over TCP once reassembly completes. The structure of an FF packet is divided into a fixed length header, followed by multiple request options and finally followed by the payload.
FF supports the protection of sensitive payloads in transit by performing encryption between the client and the proxy in combination with initiating a HTTPS request to the upstream server. Since the client and FF proxy do not perform bidirectional communication, no key negotiation can take place. Hence FF implements symmetric encryption (AES-256-GCM) using a pre-shared key between that is configured on both the client and the proxy.
The recommended installation method is via Docker:
# This will expose an FF proxy on UDP port 1234 on the host and port 100 in the container
docker run --rm -it \
-p 1234:100/udp \
timetoogo/ff \
--port 100 \
-vvv
Or it can be installed locally from the source.
Argument | Required | Description |
---|---|---|
--port <port> |
Yes | The UDP port to listen for incoming requests |
--ip-address <ip> |
No | The IP address for which to accept incoming packets, defaulting to wildcard address: 0.0.0.0 |
--pre-shared-key <key> |
No | The pre-shared key used to decrypt incoming requests |
-v , -vv , -vvv |
No | Enable verbose logging |
The most primitive interaction with FF proxy can be initiated using netcat
:
# Send an unencrypted HTTP GET request to google via a local FF proxy on port 1234
echo -e "GET / HTTP/1.1\nHost: www.google.com\n\n" | nc -uw0 127.0.0.1 1234
This project also includes client libraries which can used to initiate requests to an FF proxy. The following languages have client libraries available: