diff --git a/README.md b/README.md
index 906942d..b237a3f 100644
--- a/README.md
+++ b/README.md
@@ -6,9 +6,9 @@ A high-performance covert channel over DNS, powered by QUIC multipath.
-
-
-
+
+
+
@@ -26,123 +26,9 @@ A high-performance covert channel over DNS, powered by QUIC multipath.
Get the latest binaries [GitHub releases](https://github.com/EndPositive/slipstream/releases/latest) or pull the latest version from the [GitHub Container Registry](https://github.com/users/EndPositive/packages?repo_name=slipstream).
-## Usage
+## Documentation
-```
-Usage: slipstream-server [OPTION...]
-slipstream-server - A high-performance covert channel over DNS (server)
-
- -a, --target-address=ADDRESS Target server address (default:
- 127.0.0.1:5201)
- -c, --cert=CERT Certificate file path (default: certs/cert.pem)
- -d, --domain=DOMAIN Domain name this server is authoritative for
- (Required)
- -k, --key=KEY Private key file path (default: certs/key.pem)
- -l, --dns-listen-port=PORT DNS listen port (default: 53)
-```
-```
-Usage: slipstream-client [OPTION...]
-slipstream-client - A high-performance covert channel over DNS (client)
-
- -c, --congestion-control=ALGO Congestion control algorithm (bbr, dcubic)
- (default: dcubic)
- -d, --domain=DOMAIN Domain name used for the covert channel (Required)
-
- -g, --gso[=BOOL] GSO enabled (true/false) (default: false). Use
- --gso or --gso=true to enable.
- -l, --tcp-listen-port=PORT Listen port (default: 5201)
- -r, --resolver=RESOLVER Slipstream server resolver address (e.g., 1.1.1.1
- or 8.8.8.8:53). Can be specified multiple times.
- (Required)
-```
-
-## Quickstart
-
-### Server setup
-
-The server listens for DNS messages and attempts to decode QUIC message from them.
-Any new QUIC streams opened will be forwarded to a specified TCP service.
-For example, we can start a simple nc listener and configure the slipstream server to connect to it.
-
-```shell
-$ nc -l -p 5201
-$ slipstream-server \
- --dns-listen-port=8853 \
- --cert=certs/cert.pem \
- --key=certs/key.pem \
- --target-address=127.0.0.1:5201 \
- --domain=test.com
-```
-
-### Client setup
-
-The client listens on a TCP port for incoming connections.
-It opens a QUIC connection through the resolver specified.
-For every TCP connection it accepts, a new QUIC stream will be opened.
-In this example, we connect to the slipstream server running on port 8853.
-
-```shell
-$ slipstream-client \
- --tcp-listen-port=7000 \
- --resolver=127.0.0.1:8853 \
- --domain=test.com
-Adding 127.0.0.1:8853
-Starting connection to 127.0.0.1
-Initial connection ID: 54545454
-Listening on port 7000...
-Connection completed, almost ready.
-Connection confirmed.
-```
-
-### Usage
-
-You can then connect to the slipstream client on port 7000 as if you were connecting to the nc client on port 5201.
-
-```shell
-$ base64 /dev/urandom | head -c 5000000 | nc 127.0.0.1 7000
-
-# slipstream client wakes up
-[0:9] accept: connection
-[0:9] wakeup
-[0:9] activate: stream
-[0:9] recv->quic_send: empty, disactivate
-[0:9] wakeup
-[0:9] activate: stream
-[0:9] recv->quic_send: empty, disactivate
-[0:9] wakeup
-[0:9] activate: stream
-[0:9] recv->quic_send: empty, disactivate
-[0:9] recv: closed stream
-
-# base64 data arrives on the server
-S9w3u5up+c39u6vrkBtxKbSxOJA2UElczDgc3x4h3TtZtzvgMX05Ig4whEYDvY5MP8g4dJ1QsXX1
-fSDm0y6mOlQ4fQhYchkyKt18fV0tpBkLrPwv6MkW+IaksKe7Qo61s3gxu2jrPBlC1yxML+rYZU93
-MYNB7rFC6s3a0eHmfdsfbtBbFIF809X91fqd6gYiKPtWAHc0J5OsEyqMI3QcUGSDJd4Sw+iAC5X7
-```
-
-## Real network scenario
-
-You can try this out on a real network (if you have permission).
-First, you need to have a server outside of the network you want to escape.
-For a domain name you own, setup the DNS records to point to your nameserver.
-This ensures that queries for subdomains of `test.com` will be forwarded to your server.
-
-```
-test.com NS ns.test.com
-ns.test.com A 12.23.34.45
-```
-
-Then run the slipstream server on port 53 (requires elevated privileges) and instruct the client to use a real DNS resolver.
-
-# Benchmarks
-
-Comparison of slipstream and other existing DNS tunneling tools can be found in the [EndPositive/dns-tunneling-benchmark](https://github.com/EndPositive/dns-tunneling-benchmark) repository.
-
-Main findings:
-
-* 42x faster than dnstt for direct connections
-* 23/19 Mbps upload/download speed for direction connections
-* automatically maximizes query rate according to resolver rate-limit
+slipstream's documentation is available at [endpositive.github.io/slipstream](https://endpositive.github.io/slipstream/).
# Acknowledgements
diff --git a/docs/.gitignore b/docs/.gitignore
new file mode 100644
index 0000000..2ca8682
--- /dev/null
+++ b/docs/.gitignore
@@ -0,0 +1,4 @@
+_site/
+.sass-cache/
+.jekyll-cache/
+.jekyll-metadata
diff --git a/docs/404.html b/docs/404.html
new file mode 100644
index 0000000..3a16ab5
--- /dev/null
+++ b/docs/404.html
@@ -0,0 +1,25 @@
+---
+permalink: /404.html
+layout: page
+---
+
+
+
+
+ Exfiltrating a 10 MB file over a single DNS resolver.
+
diff --git a/docs/index.md b/docs/index.md
new file mode 100644
index 0000000..069ad82
--- /dev/null
+++ b/docs/index.md
@@ -0,0 +1,27 @@
+---
+title: Introduction
+nav_order: 0
+---
+
+# Introduction
+
+slipstream is a novel DNS tunnel leveraging the QUIC protocol under the hood.
+
+DNS tunneling is a technique that allows for data to be transmitted over DNS queries and responses.
+Such a tunnel can be set up in any network with access to a DNS resolver, even when the client does not have direct access to the internet.
+DNS tunneling enables attackers to exfiltrate data, establish Command and Control communication, or bypass network restrictions.
+Unfortunately, the DNS protocol is quite limited, with a maximum message size, the use of a request-response model, and employed rate-limiting by resolvers.
+To overcome these limitations, tools such as Iodine, or OzymanDNS design custom reliable protocols on top of DNS to allow for streaming continuous data over the tunnel.
+However, they are often limited, with throughput often not exceeding several hundred kilobytes per second.
+By carefully implementing QUIC compatibility layers, we're able to leverage QUIC's reliability guarantees and introduce a high-performance DNS tunnel.
+
+## Quick start
+
+Follow the [installation](/installation/) and [usage instruction](/usage/) guides.
+
+## Benchmarks
+
+In our [benchmark](/benchmark/), we show that slipstream achieves a 10-fold time decrease in exfiltrating a 10 MB file, while using up to 15% less queries.
+For downloading files into the restricted network, slipstream uses up to 37% less queries.
+Using QUIC congestion control, it accurately determines the query rate of the used DNS resolver.
+Finally, QUIC multipath allows slipstream to combine the bandwidth of multiple resolvers to largely improve its performance, even when those resolvers have different round-trip times, loss rates, or rate limits.
diff --git a/docs/installation/build.md b/docs/installation/build.md
new file mode 100644
index 0000000..ab82728
--- /dev/null
+++ b/docs/installation/build.md
@@ -0,0 +1,44 @@
+---
+title: Local build
+parent: Installation
+nav_order: 23
+---
+
+# Building slipstream locally
+
+To build slipstream locally on debian-based distros, you need the following dependencies installed:
+
+* cmake
+* git
+* pkg-config
+* libssl-dev
+* ninja-build
+* clang
+
+Clone the slipstream repo and its submodules recursively.
+This fetches slipstream, [SPCDNS](https://github.com/spc476/SPCDNS), [lua-resty-base-encoding](https://github.com/spacewander/lua-resty-base-encoding), and our [picoquic fork](https://github.com/EndPositive/slipstream-picoquic/).
+
+```shell
+$ git clone --recurse-submodules https://github.com/EndPositive/slipstream.git
+```
+
+You can then configure slipstream by running the following command:
+
+```shell
+# Configure CMake
+$ cmake \
+ -DCMAKE_BUILD_TYPE=Release \
+ -DCMAKE_MAKE_PROGRAM=ninja \
+ -DCMAKE_C_COMPILER=clang \
+ -DCMAKE_CXX_COMPILER=clang++ \
+ -G Ninja \
+ -S . \
+ -B build
+# Build the client and server binaries
+$ cmake \
+ --build build \
+ --target slipstream-client slipstream-server
+```
+
+This will place the client and server binaries in the `build/` directory.
+These are dynamically linked binaries against OpenSSL and GNU C.
diff --git a/docs/installation/docker.md b/docs/installation/docker.md
new file mode 100644
index 0000000..2cd320d
--- /dev/null
+++ b/docs/installation/docker.md
@@ -0,0 +1,45 @@
+---
+title: Docker image
+parent: Installation
+nav_order: 22
+---
+
+# GitHub Container Registry
+
+Docker images are published to the GHCR at every release.
+The client and server are packaged in different images.
+
+* [ghcr.io/endpositive/slipstream-client](https://ghcr.io/endpositive/slipstream-client)
+* [ghcr.io/endpositive/slipstream-server](https://ghcr.io/endpositive/slipstream-server)
+
+### Tags
+
+* latest
+* vX.X.X
+
+# Usage
+
+The client requires port 5201 to be forwarded to the host.
+The server requires port 53 to be forwarded.
+
+```shell
+$ docker run \
+ --rm \
+ -p 53:53 \
+ ghcr.io/endpositive/slipstream-server:v0.0.1 \
+ --target-address=x.x.x.x:yy \
+ --domain=test.com
+```
+
+```shell
+$ docker run \
+ --rm \
+ -p 5201:5201 \
+ ghcr.io/endpositive/slipstream-server:v0.0.1 \
+ --domain=test.com \
+ --resolver=1.1.1.1:53
+```
+
+Any TCP connections on the client's port `5201` will now be forwarded to `x.x.x.x:yy`.
+You could also run a slipstream on a different port than 53, but then a public resolver won't be able to reach the server.
+This may be useful in scenarios where you setup a direct connection between the client and server rather than through public DNS infrastructure.
diff --git a/docs/installation/gh.md b/docs/installation/gh.md
new file mode 100644
index 0000000..ab2a555
--- /dev/null
+++ b/docs/installation/gh.md
@@ -0,0 +1,18 @@
+---
+title: GitHub releases
+parent: Installation
+nav_order: 21
+---
+
+# GitHub releases
+
+slipstream binaries are published to GitHub at every in release.
+These are dynamically linked binaries against OpenSSL and GNU C.
+Visit the latest GitHub release [here](https://github.com/EndPositive/slipstream/releases/latest).
+
+```shell
+$ wget https://github.com/EndPositive/slipstream/releases/download/v0.0.1/slipstream-client-v0.0.1-linux-x86_64
+```
+```shell
+$ wget https://github.com/EndPositive/slipstream/releases/download/v0.0.1/slipstream-server-v0.0.1-linux-x86_64
+```
diff --git a/docs/installation/index.md b/docs/installation/index.md
new file mode 100644
index 0000000..ca9b8e8
--- /dev/null
+++ b/docs/installation/index.md
@@ -0,0 +1,8 @@
+---
+title: Installation
+nav_order: 20
+---
+
+# Installation
+
+Download slipstream through GitHub releases, Docker (GitHub Container Registry), or build slipstream locally.
diff --git a/docs/usage.md b/docs/usage.md
new file mode 100644
index 0000000..27ef65e
--- /dev/null
+++ b/docs/usage.md
@@ -0,0 +1,114 @@
+---
+title: Usage instructions
+nav_order: 30
+---
+
+# Usage instructions
+
+slipstream is designed to tunnel TCP traffic over DNS messages.
+Since DNS is a distributed system, we can abuse existing DNS infrastructure in the tunnel.
+For example, the figure below shows multiple network routes using a public DNS resolver to pass through the local or country scoped firewall.
+This is especially useful when a network mandates the use of a DNS resolver as assigned in the DHCP configuration.
+
+
+
+slipstream consists of a server and client binary.
+
+#### Server
+
+The server is the one to be placed on the outside of the restricted network.
+It will act as the authoritative nameserver for a given domain.
+It will forward received connections to a TCP service specified in the CLI arguments.
+
+```shell
+$ slipstream-server
+ --target-address=x.x.x.x:yy \ # TCP address of the service to access
+ --domain=test.com
+```
+
+#### Client
+
+The client is placed inside the restricted network.
+
+
+```shell
+$ slipstream-client \
+ --resolver-address=x.x.x.x:yy \ # Address of public DNS resolver or DHCP assigned resolver
+ --domain=test.com
+```
+
+
+### Configuration of DNS records
+
+Assumming you own `test.com`, you should configure the DNS records such that your slipstream server is configured as the authoritative nameserver of that domain.
+For example, add a NS record for `test.com` pointing to `ns.test.com`.
+Then add an A record on `ns.test.com` pointing to your slipstream server IP.
+
+```
+@ IN NS ns.test.com.
+ns IN A x.x.x.x:yy ; # Address of slipstream server
+```
+
+### Direct connection
+
+It is also possible to setup a direct connection between the client and the server.
+This allows to impersonate DNS traffic on port 53 without actually using any public infrastructure.
+This is a similar trick to using WireGuard on port 53, additionally encoding as DNS traffic.
+
+```shell
+$ slipstream-client \
+ --congestion-control=bbr \ # Faster better than dcubic in direct connections
+ --resolver-address=x.x.x.x:yy \ # Address of slipstream server
+ --domain=test.com
+```
+
+## Example data transfer
+
+An example of a sending data from the client to the server over a direct slipstream connection.
+
+```shell
+$ nc -l -p 5201
+$ slipstream-server \
+ --dns-listen-port=8853 \
+ --target-address=127.0.0.1:5201 \
+ --domain=test.com
+```
+
+```shell
+$ slipstream-client \
+ --congestion-control=bbr \
+ --tcp-listen-port=7000 \
+ --resolver=127.0.0.1:8853 \
+ --domain=test.com
+Adding 127.0.0.1:8853
+Starting connection to 127.0.0.1
+Initial connection ID: 54545454
+Listening on port 7000...
+Connection completed, almost ready.
+Connection confirmed.
+```
+
+You can then connect to the slipstream client on port 7000 as if you were connecting to the nc client on port 5201.
+
+```shell
+$ base64 /dev/urandom | head -c 5000000 | nc 127.0.0.1 7000
+
+# slipstream client wakes up
+[0:9] accept: connection
+[0:9] wakeup
+[0:9] activate: stream
+[0:9] recv->quic_send: empty, disactivate
+[0:9] wakeup
+[0:9] activate: stream
+[0:9] recv->quic_send: empty, disactivate
+[0:9] wakeup
+[0:9] activate: stream
+[0:9] recv->quic_send: empty, disactivate
+[0:9] recv: closed stream
+```
+```shell
+# base64 data arrives on the server
+S9w3u5up+c39u6vrkBtxKbSxOJA2UElczDgc3x4h3TtZtzvgMX05Ig4whEYDvY5MP8g4dJ1QsXX1
+fSDm0y6mOlQ4fQhYchkyKt18fV0tpBkLrPwv6MkW+IaksKe7Qo61s3gxu2jrPBlC1yxML+rYZU93
+MYNB7rFC6s3a0eHmfdsfbtBbFIF809X91fqd6gYiKPtWAHc0J5OsEyqMI3QcUGSDJd4Sw+iAC5X7
+```