Compare commits

...

6 commits
v0.0.1 ... main

Author SHA1 Message Date
Jop Zitman
282764a879
Merge pull request #3 from EndPositive/fix/ptr-nullify
Initialize encoding/decoding buffers with null pointers closes #1
2025-06-09 13:42:06 +08:00
Jop Zitman
c2386d344a Initialize encoding/decoding buffers with null pointers
closes #1

> the caller has no way to know if they need to free it or not

while this is not necessarily true--the buffer is only allocated on succesful return codes--it is indeed better practice to ensure the pointers are nullified. This prevents future accidental mal-use leading to corruption when freeing an uninitialized pointer.
2025-06-05 09:42:46 +08:00
Pepijn van der Stap
3ada0c8059 feat(dotter): Ensure null termination and robust buffer handling
-    Explicitly null-terminating the output strings in both slipstream_inline_dotify and slipstream_inline_undotify.
-    Improving the buffer size check in slipstream_inline_dotify to correctly account for the null terminator.
-    Enhancing the handling of empty input strings in slipstream_inline_dotify.
2025-06-05 09:31:01 +08:00
Jop Zitman
c0248deec3 Update docs with protocol design and fix urls 2025-05-21 15:53:49 +08:00
Jop Zitman
afdff239d7 Customizable keep-alive-interval 2025-05-21 12:49:13 +08:00
Jop Zitman
e65f055ab0 Add GitHub pages 2025-05-20 17:00:48 +08:00
26 changed files with 875 additions and 148 deletions

124
README.md
View file

@ -6,9 +6,9 @@ A high-performance covert channel over DNS, powered by QUIC multipath.
<p align="center"> <p align="center">
<picture align="center"> <picture align="center">
<source media="(prefers-color-scheme: dark)" srcset="docs/file_transfer_times_dark.png"> <source media="(prefers-color-scheme: dark)" srcset="docs/assets/file_transfer_times_dark.png">
<source media="(prefers-color-scheme: light)" srcset="docs/file_transfer_times_light.png"> <source media="(prefers-color-scheme: light)" srcset="docs/assets/file_transfer_times_light.png">
<img alt="Shows a bar chart with benchmark results." src="docs/file_transfer_times_light.png"> <img alt="Shows a bar chart with benchmark results." src="docs/assets/file_transfer_times_light.png">
</picture> </picture>
</p> </p>
@ -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). 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
``` slipstream's documentation is available at [endpositive.github.io/slipstream](https://endpositive.github.io/slipstream/).
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
# Acknowledgements # Acknowledgements

4
docs/.gitignore vendored Normal file
View file

@ -0,0 +1,4 @@
_site/
.sass-cache/
.jekyll-cache/
.jekyll-metadata

25
docs/404.html Normal file
View file

@ -0,0 +1,25 @@
---
permalink: /404.html
layout: page
---
<style type="text/css" media="screen">
.container {
margin: 10px auto;
max-width: 600px;
text-align: center;
}
h1 {
margin: 30px 0;
font-size: 4em;
line-height: 1;
letter-spacing: -1px;
}
</style>
<div class="container">
<h1>404</h1>
<p><strong>Page not found :(</strong></p>
<p>The requested page could not be found.</p>
</div>

32
docs/Gemfile Normal file
View file

@ -0,0 +1,32 @@
source "https://rubygems.org"
# Hello! This is where you manage which Jekyll version is used to run.
# When you want to use a different version, change it below, save the
# file and run `bundle install`. Run Jekyll with `bundle exec`, like so:
#
# bundle exec jekyll serve
#
# This will help ensure the proper Jekyll version is running.
# Happy Jekylling!
# gem "jekyll", "~> 4.4.1"
# This is the default theme for new Jekyll sites. You may change this to anything you like.
# If you want to use GitHub Pages, remove the "gem "jekyll"" above and
# uncomment the line below. To upgrade, run `bundle update github-pages`.
gem "github-pages", group: :jekyll_plugins
# If you have any plugins, put them here!
group :jekyll_plugins do
gem "jekyll-remote-theme", "~> 0.4.3"
end
# Windows and JRuby does not include zoneinfo files, so bundle the tzinfo-data gem
# and associated library.
platforms :mingw, :x64_mingw, :mswin, :jruby do
gem "tzinfo", ">= 1", "< 3"
gem "tzinfo-data"
end
# Performance-booster for watching directories on Windows
gem "wdm", "~> 0.1", :platforms => [:mingw, :x64_mingw, :mswin]
# Lock `http_parser.rb` gem to `v0.6.x` on JRuby builds since newer versions of the gem
# do not have a Java counterpart.
gem "http_parser.rb", "~> 0.6.0", :platforms => [:jruby]

314
docs/Gemfile.lock Normal file
View file

@ -0,0 +1,314 @@
GEM
remote: https://rubygems.org/
specs:
activesupport (8.0.2)
base64
benchmark (>= 0.3)
bigdecimal
concurrent-ruby (~> 1.0, >= 1.3.1)
connection_pool (>= 2.2.5)
drb
i18n (>= 1.6, < 2)
logger (>= 1.4.2)
minitest (>= 5.1)
securerandom (>= 0.3)
tzinfo (~> 2.0, >= 2.0.5)
uri (>= 0.13.1)
addressable (2.8.7)
public_suffix (>= 2.0.2, < 7.0)
base64 (0.2.0)
benchmark (0.4.0)
bigdecimal (3.1.9)
coffee-script (2.4.1)
coffee-script-source
execjs
coffee-script-source (1.12.2)
colorator (1.1.0)
commonmarker (0.23.11)
concurrent-ruby (1.3.5)
connection_pool (2.5.3)
csv (3.3.4)
dnsruby (1.72.4)
base64 (~> 0.2.0)
logger (~> 1.6.5)
simpleidn (~> 0.2.1)
drb (2.2.1)
em-websocket (0.5.3)
eventmachine (>= 0.12.9)
http_parser.rb (~> 0)
ethon (0.16.0)
ffi (>= 1.15.0)
eventmachine (1.2.7)
execjs (2.10.0)
faraday (2.13.1)
faraday-net_http (>= 2.0, < 3.5)
json
logger
faraday-net_http (3.4.0)
net-http (>= 0.5.0)
ffi (1.17.2-aarch64-linux-gnu)
ffi (1.17.2-aarch64-linux-musl)
ffi (1.17.2-arm-linux-gnu)
ffi (1.17.2-arm-linux-musl)
ffi (1.17.2-arm64-darwin)
ffi (1.17.2-x86_64-darwin)
ffi (1.17.2-x86_64-linux-gnu)
ffi (1.17.2-x86_64-linux-musl)
forwardable-extended (2.6.0)
gemoji (4.1.0)
github-pages (232)
github-pages-health-check (= 1.18.2)
jekyll (= 3.10.0)
jekyll-avatar (= 0.8.0)
jekyll-coffeescript (= 1.2.2)
jekyll-commonmark-ghpages (= 0.5.1)
jekyll-default-layout (= 0.1.5)
jekyll-feed (= 0.17.0)
jekyll-gist (= 1.5.0)
jekyll-github-metadata (= 2.16.1)
jekyll-include-cache (= 0.2.1)
jekyll-mentions (= 1.6.0)
jekyll-optional-front-matter (= 0.3.2)
jekyll-paginate (= 1.1.0)
jekyll-readme-index (= 0.3.0)
jekyll-redirect-from (= 0.16.0)
jekyll-relative-links (= 0.6.1)
jekyll-remote-theme (= 0.4.3)
jekyll-sass-converter (= 1.5.2)
jekyll-seo-tag (= 2.8.0)
jekyll-sitemap (= 1.4.0)
jekyll-swiss (= 1.0.0)
jekyll-theme-architect (= 0.2.0)
jekyll-theme-cayman (= 0.2.0)
jekyll-theme-dinky (= 0.2.0)
jekyll-theme-hacker (= 0.2.0)
jekyll-theme-leap-day (= 0.2.0)
jekyll-theme-merlot (= 0.2.0)
jekyll-theme-midnight (= 0.2.0)
jekyll-theme-minimal (= 0.2.0)
jekyll-theme-modernist (= 0.2.0)
jekyll-theme-primer (= 0.6.0)
jekyll-theme-slate (= 0.2.0)
jekyll-theme-tactile (= 0.2.0)
jekyll-theme-time-machine (= 0.2.0)
jekyll-titles-from-headings (= 0.5.3)
jemoji (= 0.13.0)
kramdown (= 2.4.0)
kramdown-parser-gfm (= 1.1.0)
liquid (= 4.0.4)
mercenary (~> 0.3)
minima (= 2.5.1)
nokogiri (>= 1.16.2, < 2.0)
rouge (= 3.30.0)
terminal-table (~> 1.4)
webrick (~> 1.8)
github-pages-health-check (1.18.2)
addressable (~> 2.3)
dnsruby (~> 1.60)
octokit (>= 4, < 8)
public_suffix (>= 3.0, < 6.0)
typhoeus (~> 1.3)
html-pipeline (2.14.3)
activesupport (>= 2)
nokogiri (>= 1.4)
http_parser.rb (0.8.0)
i18n (1.14.7)
concurrent-ruby (~> 1.0)
jekyll (3.10.0)
addressable (~> 2.4)
colorator (~> 1.0)
csv (~> 3.0)
em-websocket (~> 0.5)
i18n (>= 0.7, < 2)
jekyll-sass-converter (~> 1.0)
jekyll-watch (~> 2.0)
kramdown (>= 1.17, < 3)
liquid (~> 4.0)
mercenary (~> 0.3.3)
pathutil (~> 0.9)
rouge (>= 1.7, < 4)
safe_yaml (~> 1.0)
webrick (>= 1.0)
jekyll-avatar (0.8.0)
jekyll (>= 3.0, < 5.0)
jekyll-coffeescript (1.2.2)
coffee-script (~> 2.2)
coffee-script-source (~> 1.12)
jekyll-commonmark (1.4.0)
commonmarker (~> 0.22)
jekyll-commonmark-ghpages (0.5.1)
commonmarker (>= 0.23.7, < 1.1.0)
jekyll (>= 3.9, < 4.0)
jekyll-commonmark (~> 1.4.0)
rouge (>= 2.0, < 5.0)
jekyll-default-layout (0.1.5)
jekyll (>= 3.0, < 5.0)
jekyll-feed (0.17.0)
jekyll (>= 3.7, < 5.0)
jekyll-gist (1.5.0)
octokit (~> 4.2)
jekyll-github-metadata (2.16.1)
jekyll (>= 3.4, < 5.0)
octokit (>= 4, < 7, != 4.4.0)
jekyll-include-cache (0.2.1)
jekyll (>= 3.7, < 5.0)
jekyll-mentions (1.6.0)
html-pipeline (~> 2.3)
jekyll (>= 3.7, < 5.0)
jekyll-optional-front-matter (0.3.2)
jekyll (>= 3.0, < 5.0)
jekyll-paginate (1.1.0)
jekyll-readme-index (0.3.0)
jekyll (>= 3.0, < 5.0)
jekyll-redirect-from (0.16.0)
jekyll (>= 3.3, < 5.0)
jekyll-relative-links (0.6.1)
jekyll (>= 3.3, < 5.0)
jekyll-remote-theme (0.4.3)
addressable (~> 2.0)
jekyll (>= 3.5, < 5.0)
jekyll-sass-converter (>= 1.0, <= 3.0.0, != 2.0.0)
rubyzip (>= 1.3.0, < 3.0)
jekyll-sass-converter (1.5.2)
sass (~> 3.4)
jekyll-seo-tag (2.8.0)
jekyll (>= 3.8, < 5.0)
jekyll-sitemap (1.4.0)
jekyll (>= 3.7, < 5.0)
jekyll-swiss (1.0.0)
jekyll-theme-architect (0.2.0)
jekyll (> 3.5, < 5.0)
jekyll-seo-tag (~> 2.0)
jekyll-theme-cayman (0.2.0)
jekyll (> 3.5, < 5.0)
jekyll-seo-tag (~> 2.0)
jekyll-theme-dinky (0.2.0)
jekyll (> 3.5, < 5.0)
jekyll-seo-tag (~> 2.0)
jekyll-theme-hacker (0.2.0)
jekyll (> 3.5, < 5.0)
jekyll-seo-tag (~> 2.0)
jekyll-theme-leap-day (0.2.0)
jekyll (> 3.5, < 5.0)
jekyll-seo-tag (~> 2.0)
jekyll-theme-merlot (0.2.0)
jekyll (> 3.5, < 5.0)
jekyll-seo-tag (~> 2.0)
jekyll-theme-midnight (0.2.0)
jekyll (> 3.5, < 5.0)
jekyll-seo-tag (~> 2.0)
jekyll-theme-minimal (0.2.0)
jekyll (> 3.5, < 5.0)
jekyll-seo-tag (~> 2.0)
jekyll-theme-modernist (0.2.0)
jekyll (> 3.5, < 5.0)
jekyll-seo-tag (~> 2.0)
jekyll-theme-primer (0.6.0)
jekyll (> 3.5, < 5.0)
jekyll-github-metadata (~> 2.9)
jekyll-seo-tag (~> 2.0)
jekyll-theme-slate (0.2.0)
jekyll (> 3.5, < 5.0)
jekyll-seo-tag (~> 2.0)
jekyll-theme-tactile (0.2.0)
jekyll (> 3.5, < 5.0)
jekyll-seo-tag (~> 2.0)
jekyll-theme-time-machine (0.2.0)
jekyll (> 3.5, < 5.0)
jekyll-seo-tag (~> 2.0)
jekyll-titles-from-headings (0.5.3)
jekyll (>= 3.3, < 5.0)
jekyll-watch (2.2.1)
listen (~> 3.0)
jemoji (0.13.0)
gemoji (>= 3, < 5)
html-pipeline (~> 2.2)
jekyll (>= 3.0, < 5.0)
json (2.12.0)
kramdown (2.4.0)
rexml
kramdown-parser-gfm (1.1.0)
kramdown (~> 2.0)
liquid (4.0.4)
listen (3.9.0)
rb-fsevent (~> 0.10, >= 0.10.3)
rb-inotify (~> 0.9, >= 0.9.10)
logger (1.6.6)
mercenary (0.3.6)
minima (2.5.1)
jekyll (>= 3.5, < 5.0)
jekyll-feed (~> 0.9)
jekyll-seo-tag (~> 2.1)
minitest (5.25.5)
net-http (0.6.0)
uri
nokogiri (1.18.8-aarch64-linux-gnu)
racc (~> 1.4)
nokogiri (1.18.8-aarch64-linux-musl)
racc (~> 1.4)
nokogiri (1.18.8-arm-linux-gnu)
racc (~> 1.4)
nokogiri (1.18.8-arm-linux-musl)
racc (~> 1.4)
nokogiri (1.18.8-arm64-darwin)
racc (~> 1.4)
nokogiri (1.18.8-x86_64-darwin)
racc (~> 1.4)
nokogiri (1.18.8-x86_64-linux-gnu)
racc (~> 1.4)
nokogiri (1.18.8-x86_64-linux-musl)
racc (~> 1.4)
octokit (4.25.1)
faraday (>= 1, < 3)
sawyer (~> 0.9)
pathutil (0.16.2)
forwardable-extended (~> 2.6)
public_suffix (5.1.1)
racc (1.8.1)
rb-fsevent (0.11.2)
rb-inotify (0.11.1)
ffi (~> 1.0)
rexml (3.4.1)
rouge (3.30.0)
rubyzip (2.4.1)
safe_yaml (1.0.5)
sass (3.7.4)
sass-listen (~> 4.0.0)
sass-listen (4.0.0)
rb-fsevent (~> 0.9, >= 0.9.4)
rb-inotify (~> 0.9, >= 0.9.7)
sawyer (0.9.2)
addressable (>= 2.3.5)
faraday (>= 0.17.3, < 3)
securerandom (0.4.1)
simpleidn (0.2.3)
terminal-table (1.8.0)
unicode-display_width (~> 1.1, >= 1.1.1)
typhoeus (1.4.1)
ethon (>= 0.9.0)
tzinfo (2.0.6)
concurrent-ruby (~> 1.0)
unicode-display_width (1.8.0)
uri (1.0.3)
webrick (1.9.1)
PLATFORMS
aarch64-linux-gnu
aarch64-linux-musl
arm-linux-gnu
arm-linux-musl
arm64-darwin
x86_64-darwin
x86_64-linux-gnu
x86_64-linux-musl
DEPENDENCIES
github-pages
http_parser.rb (~> 0.6.0)
jekyll-remote-theme (~> 0.4.3)
tzinfo (>= 1, < 3)
tzinfo-data
wdm (~> 0.1)
BUNDLED WITH
2.6.9

12
docs/_config.yml Normal file
View file

@ -0,0 +1,12 @@
title: slipstream
email: jop-zitman@hotmail.com@hotmail.com
description: >-
High-performance multi-path covert channel over DNS.
baseurl: "/slipstream" # the subpath of your site, e.g. /blog
url: "https://endpositive.github.io" # the base hostname & protocol for your site, e.g. http://example.com
twitter_username: jekyllrb
github_username: jekyll
remote_theme: just-the-docs/just-the-docs
plugins:
- jekyll-remote-theme

BIN
docs/assets/bytefield.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

View file

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Before After
Before After

BIN
docs/assets/network.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 143 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

BIN
docs/assets/socket_loop.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

19
docs/benchmark.md Normal file
View file

@ -0,0 +1,19 @@
---
title: Benchmarks
nav_order: 50
---
# 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.
<p align="center">
<picture align="center">
<source srcset="/slipstream/assets/file_transfer_times_light.png">
<img alt="Shows a bar chart with benchmark results." src="/slipstream/assets/file_transfer_times_light.png">
</picture>
</p>
<p align="center">
<i>Exfiltrating a 10 MB file over a single DNS resolver.</i>
</p>

27
docs/index.md Normal file
View file

@ -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](/slipstream/installation/) and [usage instruction](/slipstream/usage/) guides.
## Benchmarks
In our [benchmark](/slipstream/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.

View file

@ -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.

View file

@ -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.

18
docs/installation/gh.md Normal file
View file

@ -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
```

View file

@ -0,0 +1,8 @@
---
title: Installation
nav_order: 20
---
# Installation
Download slipstream through GitHub releases, Docker (GitHub Container Registry), or build slipstream locally.

157
docs/protocol.md Normal file
View file

@ -0,0 +1,157 @@
---
title: Protocol Design
nav_order: 40
---
## Protocol Design
slipstream acts as a TCP proxy server, tunneling the streaming data between a client and a server over DNS, supporting the use of different DNS resolvers in parallel.
A major aspect of slipstream is the use of QUIC[^QUIC] multipath[^QUICMP] as the transport protocol.
[^QUIC]: [QUIC: A UDP-Based Multiplexed and Secure Transport (RFC 9000)](https://datatracker.ietf.org/doc/html/rfc9000)
[^QUICMP]: [Multipath Extension for QUIC (draft-ietf-quic-multipath-14)](https://datatracker.ietf.org/doc/draft-ietf-quic-multipath/14/)
### Protocol Stack
Following [dnstt](https://www.bamsoftware.com/software/dnstt/)'s approach outlined in TurboTunnel[^TurboTunnel], we decouple the encoding layer from the session and reliability layers.
Compared to dnstt, QUIC combines the [KCP](https://github.com/xtaci/kcp-go), [SMUX](https://github.com/xtaci/smux), and [Noise](https://github.com/flynn/noise) layers into a single protocol, as shown below.
slipstream itself does not implement any reliability based on DNS message status codes, keeping reliability management completely within QUIC.
A DNS message with a failure response code does not necessarily mean that the message was not delivered to the DNS tunneling server.
[^TurboTunnel]: [David Fifield. Turbo Tunnel, a good way to design censorship circumvention protocols. FOCI 2020.](https://www.usenix.org/conference/foci20/presentation/fifield)
<p align="center">
<picture align="center">
<source srcset="/slipstream/assets/protocol_stack.png">
<img alt="Shows a two stacks of protocol names. Dnstt shows KCP, SMUX, and Noise, and TurboTunnel. Slipstream shows QUIC and slipstream." src="/slipstream/assets/protocol_stack.png">
</picture>
</p>
<p align="center">
<i>Protocol stack comparison between slipstream and dnstt.</i>
</p>
QUIC implementations are abundant, allowing us to pick and choose an implementation that fits the pull interface proposed in TurboTunnel.
The main selection criteria for a QUIC implementation is support for a pull interface and the multipath QUIC extension.
The latter deemed especially challenging as this RFC is going through many changes in each version of the proposal.
As of writing, the only libraries supporting multipath QUIC are [mp-quic](https://github.com/qdeconinck/mp-quic), [TQUIC](https://github.com/Tencent/tquic) (draft v5), [XQUIC](https://github.com/alibaba/xquic) (draft v10), and [picoquic](https://github.com/private-octopus/picoquic) (draft v11).
We've found picoquic to be the simplest implementation to work with, supporting the latest proposals and providing a pull interface for sending and receiving packets.
### Reduced Header Overhead
QUIC optimizes the header size. Compared to dnstt, this allows slipstream to reduce the header overhead from 59 bytes to 24 bytes (see the detailed bytefield diagram below).
QUIC uses variable-length integers to encode certain fields, allowing for more efficient encoding of small numbers.
Using a single protocol also ensures that there is no redundancy in the header, which is an issue as found in the repeated length fields in the combination of TurboTunnel, KCP, SMUX, and Noise in dnstt.
For encoding and decoding QUIC packets in the DNS message, slipstream follows dnstt and encodes data into the domain name using base32 and puts raw data in TXT response records.
slipstream does not add any additional headers or fields on top of QUIC.
While dnstt adds a packet length marker, we rely on QUIC to add packet length markers when needed (e.g. when coallescing multiple packets into a single DNS message).
For dnstt's random padding cache-breaking solution, we also rely on QUIC to use unique packet IDs (see QUIC[^QUIC] section 12.3.).
QUIC frames to be retransmitted are wrapped in new QUIC packets (see QUIC[^QUIC] section 13.2.1.), which ensures that retransmissions are unique too, preventing the need for additional random padding as seen in dnstt.
Finally, QUIC already tracks a connection ID in the header, so we do not need to introduce an additional connection ID field in the header either.
<p align="center">
<picture align="center">
<source srcset="/slipstream/assets/bytefield.png">
<img alt="Shows the minimal size headers of both slipstream and dnstt. Slipstream consists only of QUIC headers, while dnstt has many layers of different headers belonging to KCP, SMUX, noise, and TurboTunnel." src="/slipstream/assets/bytefield.png">
</picture>
</p>
<p align="center">
<i>Comparison of slipstream and dnstt packet formats.</i>
</p>
Another consideration is that QUIC expects the `Initial` packets to have a minimum size of 1200 bytes to prevent amplification attacks.
Since DNS messages are so limited in size, we have to reduce this limit on both the client and the server side depending on the size of the domain name used.
Of course, we could still integrate response rate-limiting on unauthenticated client IDs.
We could also further delay the server response until a certain number of initial frames have been received, for example based on the expected crypto message size.
### Congestion Control
Achieving high performance in DNS tunneling requires careful management of query rates to maximize throughput while avoiding rate limits imposed by DNS resolvers.
In addition, with larger RTTs, DNS tunnels suffer from low performance[^nussbaumRobustCovertChannels2009], showing the importance of a good congestion control algorithm.
Since the bandwidth of a single stream is mostly based on the RTT and window size (i.e. related to bandwidth-delay-product), managing the window size is crucial.
While QUIC supports congestion control algorithms, we need to make several changes:
* On the client side, we need to choose a congestion control algorithm. As a DNS resolver hits its rate-limit, it drops or responds to the client without forwarding the message to the server. In addition, as a DNS tunneling server is processing a high number of DNS requests, it may experience congestion and slow down the response time. Thus, we choose a congestion control algorithm that balances latency and loss signals (DCUBIC in picoquic).
* On the server side, we disable the congestion control algorithm entirely, and remove the congestion window. This allows the server to always respond to incoming DNS queries, maximizing its use of limited opportunities to send responses. We assume that if a DNS query can reach the server, it must be able to reach back too, as the DNS resolver's rate-limit may be the primary bottleneck in the network path.
* While QUIC attempts to prevent an “infinite feedback loop of acknowledgments” (see QUIC[^QUIC] section 13.2.1.) on non-ACK-eliciting frames by delaying acknowledgments up to `max_ack_delay`, the server may not be able to respond to the client's acknowledgments in time if no further DNS query arrives. We ensure that this loss signal is not considered by the QUIC congestion control algorithm as to prevent unnecessary lowering of the congestion window.
* As a DNS resolver may use multiple threads, DNS messages may not be delivered in order. In preliminary testing, we observed that DNS messages may be received out of order by hundreds of packets. To ensure that this signal does not influence the congestion control algorithm, we ignore out-of-order loss signals and instead rely solely on RACK signals[^RACK].
[^nussbaumRobustCovertChannels2009]: Lucas Nussbaum, Pierre Neyron and Olivier Richard. On Robust Covert Channels Inside DNS. 24th IFIP International Security Conference. 2009.
[^RACK]: [The RACK-TLP Loss Detection Algorithm for TCP (RFC 8985)](https://datatracker.ietf.org/doc/html/rfc8985)
### Connection IDs and Multipath
QUIC relies on the connection ID to track individual connections, rather than a source address.
The QUIC multipath extension[^QUICMP] in addition proposes to use multiple network paths simultaneously using this connection ID, effectively allowing to aggregate the individual bandwidth limits of multiple paths.
In the case of DNS tunneling, each path corresponds to a different DNS resolver, each with its own network conditions and rate limit.
Although QUIC primarily uses a connection ID for tracking individual connections, it relies on source addresses for path management (i.e. migration).
Since DNS packets may arrive at the DNS tunneling server from any source address, we hard-code a dummy address into every received packet before passing it to QUIC.
As a result, several QUIC features should be manually disabled.
For example, “A `PATH_RESPONSE` frame MUST be sent on the network path where the `PATH_CHALLENGE` frame was received.” (see QUIC[^QUIC] section 8.2.2.) ensures that a path is valid in both directions, while our DNS tunnel server is only ever aware of a single path.
And, “An endpoint can migrate a connection to a new local address by sending packets containing non-probing frames from that address.” (see QUIC[^QUIC] section 9.2.) implies that the server would be aware if the client changes its source address, while our DNS tunnel server only ever receives a dummy address (instead of constantly changing DNS resolver addresses).
### Polling
The polling mechanism in slipstream builds upon dnstt's strategy of replying to every server reply with a poll message.
To ensure that poll messages are considered as part of QUIC's congestion control counters in slipstream, we introduce a new QUIC frame type for polling.
The poll frame is a frame with size 0, such that the only space used is the frame type.
To prevent an infinitely growing number of poll messages pinging back-and-forth, QUIC should consider poll frames as non-ACK-eliciting frames, allowing QUIC to ignore responding to poll messages when the server has no data to send, in turn preventing new poll messages from being sent at the client.
If possible, we replace an outgoing poll message with a real frame that contains data.
This ensures that when the server is sending data, the poll messages won't take precedence over the client's outgoing data frames.
We enable the QUIC keep-alive option to ensure that even when there is no data between the client and the server, the server may still initiate data transmission by responding to a keep-alive packet.
This keep-alive period largely influences the server-to-client latency while idle and the number of packets sent while idle and should be manually tuned based on the user's requirements.
### Encryption
QUIC provides built-in encryption, mutual authentication, and integrity checks.
This means that we do not need to implement a custom solution, reducing the complexity of the tunneling protocol.
### Timings
DNS resolvers may keep track of round-trip-times of authoritative nameservers and drop or retransmit packets for responses that arrive too late.
For example, Unbound DNS resolver keeps track of ping, variance, and RTT for its timeout management[^UnboundTimeoutManagement].
Thus, we ensure that the DNS tunnel server responds as soon as possible to a DNS query and does not buffer any messages for responding to later.
[^UnboundTimeoutManagement]: [Unbound Timeout Information](https://www.nlnetlabs.nl/documentation/unbound/info-timeout/)
### Performant Encoding
In high performance tunneling where the bottleneck becomes the tunnel performance, the speed of encoding and decoding is crucial.
There is a trade-off between the encoding and decoding speed and the features of the DNS encoding library.
To support future use of different record types in the encoding, we choose to use a DNS library allowing for encoding and decoding of arbitrary DNS records.
While initially using the [c-ares](https://github.com/c-ares/c-ares) library, but it was found to be decreasing the performance of the tunnel.
We settled on the [SPCDNS](https://github.com/spc476/SPCDNS) library, which keeps the processing time of encoding and decoding to a minimum.
Although the library is very bare-bones, it is quite sufficient for our use-case.
Similarly, the performance of base32 encoding is crucial.
In slipstream, we use the implementation of [lua-resty-base-encoding](https://github.com/spacewander/lua-resty-base-encoding), which has proven to be one of the fastest implementations available.
### Socket loop
For tunneling purposes, we need to poll on 2 sockets: one for the TCP stream and one for the DNS messages.
When DNS messages arrive, we decode the message and pass it to the QUIC library using `picoquic_incoming_packet_ex`.
To improve the performance, we allow multiple packets to be received before attempting to respond.
For sending retransmissions, acknowledgments, or new data on the TCP stream, we poll on the QUIC library using `picoquic_prepare_next_packet_ex`.
In the server side, we buffer the multiple incoming DNS messages in a FIFO queue, such that we can respond to them in order.
Before passing on the QUIC packet, we spoof the source address of the incoming DNS message to a dummy address, and annotate the DNS message with the original source address and connection ID.
Once ready to send a response, we pull the next QUIC packet on the related connection, encode it in the DNS response, and send it over the UDP socket to the correct destination address.
For sending data, picoquic requires the program to keep track of active and inactive streams, only requesting data from the stream when it is active.
This is a bit cumbersome, as our tunnel constantly needs to poll the TCP socket in a separate thread and wake up the main picoquic when there is data to send.
<p align="center">
<picture align="center">
<source srcset="/slipstream/assets/socket_loop.png">
<img alt="Shows the data flow from the TCP socket in the client all the way to the server TCP socket. The DNS tunneling client sends DNS messages over multiple paths, which all eventually end up at the DNS tunneling server." src="/slipstream/assets/socket_loop.png">
</picture>
</p>
<p align="center">
<i>The socket loop's data flow.</i>
</p>

114
docs/usage.md Normal file
View file

@ -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.
![DNS tunnel network setup](/slipstream/assets/network.png)
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
```

View file

@ -23,7 +23,7 @@ typedef struct st_address_t {
} address_t; } address_t;
int picoquic_slipstream_client(int listen_port, struct st_address_t* server_addresses, size_t server_address_count, const char* domain_name, int picoquic_slipstream_client(int listen_port, struct st_address_t* server_addresses, size_t server_address_count, const char* domain_name,
const char* cc_algo_id, bool gso); const char* cc_algo_id, bool gso, size_t keep_alive_interval);
int picoquic_slipstream_server(int server_port, const char* pem_cert, const char* pem_key, int picoquic_slipstream_server(int server_port, const char* pem_cert, const char* pem_key,
struct sockaddr_storage* target_address, const char* domain_name); struct sockaddr_storage* target_address, const char* domain_name);

View file

@ -91,6 +91,8 @@ ssize_t client_encode_segment(dns_packet_t* packet, size_t* packet_len, const un
} }
ssize_t client_encode(void* slot_p, void* callback_ctx, unsigned char** dest_buf, const unsigned char* src_buf, size_t src_buf_len, size_t* segment_len, struct sockaddr_storage* peer_addr, struct sockaddr_storage* local_addr) { ssize_t client_encode(void* slot_p, void* callback_ctx, unsigned char** dest_buf, const unsigned char* src_buf, size_t src_buf_len, size_t* segment_len, struct sockaddr_storage* peer_addr, struct sockaddr_storage* local_addr) {
*dest_buf = NULL;
// optimize path for single segment // optimize path for single segment
if (src_buf_len <= *segment_len) { if (src_buf_len <= *segment_len) {
#ifdef NOENCODE #ifdef NOENCODE
@ -658,9 +660,6 @@ static int slipstream_connect(struct sockaddr_storage* server_address,
return -1; return -1;
} }
// 400ms
picoquic_enable_keep_alive(*cnx, 400000);
/* Document connection in client's context */ /* Document connection in client's context */
client_ctx->cnx = *cnx; client_ctx->cnx = *cnx;
/* Set the client callback context */ /* Set the client callback context */
@ -683,7 +682,7 @@ static int slipstream_connect(struct sockaddr_storage* server_address,
return ret; return ret;
} }
int picoquic_slipstream_client(int listen_port, struct st_address_t* server_addresses, size_t server_address_count, const char* domain_name, const char* cc_algo_id, bool gso) { int picoquic_slipstream_client(int listen_port, struct st_address_t* server_addresses, size_t server_address_count, const char* domain_name, const char* cc_algo_id, bool gso, const size_t keep_alive_interval) {
/* Start: start the QUIC process */ /* Start: start the QUIC process */
int ret = 0; int ret = 0;
uint64_t current_time = 0; uint64_t current_time = 0;
@ -747,6 +746,12 @@ int picoquic_slipstream_client(int listen_port, struct st_address_t* server_addr
return -1; return -1;
} }
if (keep_alive_interval != 0) {
picoquic_enable_keep_alive(cnx, keep_alive_interval * 1000);
} else {
picoquic_disable_keep_alive(cnx);
}
// Create listening socket // Create listening socket
client_ctx.listen_sock = socket(AF_INET, SOCK_STREAM, 0); client_ctx.listen_sock = socket(AF_INET, SOCK_STREAM, 0);
if (client_ctx.listen_sock < 0) { if (client_ctx.listen_sock < 0) {

View file

@ -22,6 +22,7 @@ static struct argp_option options[] = {
{"congestion-control", 'c', "ALGO", 0, "Congestion control algorithm (bbr, dcubic) (default: dcubic)", 0}, {"congestion-control", 'c', "ALGO", 0, "Congestion control algorithm (bbr, dcubic) (default: dcubic)", 0},
{"gso", 'g', "BOOL", OPTION_ARG_OPTIONAL, "GSO enabled (true/false) (default: false). Use --gso or --gso=true to enable.", 0}, {"gso", 'g', "BOOL", OPTION_ARG_OPTIONAL, "GSO enabled (true/false) (default: false). Use --gso or --gso=true to enable.", 0},
{"domain", 'd', "DOMAIN", 0, "Domain name used for the covert channel (Required)", 0}, {"domain", 'd', "DOMAIN", 0, "Domain name used for the covert channel (Required)", 0},
{"keep-alive-interval", 't', "MS", 0, "Send keep alive pings at this interval (default: 400, disabled: 0)", 0},
{0} // End of options {0} // End of options
}; };
@ -33,6 +34,7 @@ struct arguments {
size_t resolver_count; size_t resolver_count;
char* cc_algo_id; char* cc_algo_id;
bool gso; bool gso;
size_t keep_alive_interval;
}; };
/* Client mode parser */ /* Client mode parser */
@ -97,6 +99,13 @@ static error_t parse_opt(int key, char* arg, struct argp_state* state) {
argp_error(state, "Invalid boolean value for --gso: '%s'. Use 'true' or 'false'.", arg); argp_error(state, "Invalid boolean value for --gso: '%s'. Use 'true' or 'false'.", arg);
} }
break; break;
case 't':
arguments->keep_alive_interval = atoi(arg);
if (arguments->keep_alive_interval < 0) {
argp_error(state, "Invalid keep alive interval: %s", arg);
}
break;
case ARGP_KEY_ARG: case ARGP_KEY_ARG:
// No positional arguments expected // No positional arguments expected
argp_usage(state); argp_usage(state);
@ -130,6 +139,7 @@ int main(int argc, char** argv) {
arguments.gso = false; // Default GSO state arguments.gso = false; // Default GSO state
arguments.resolver_addresses = NULL; arguments.resolver_addresses = NULL;
arguments.resolver_count = 0; arguments.resolver_count = 0;
arguments.keep_alive_interval = 400; // Default keep alive interval
// Ensure output buffers are flushed immediately (useful for debugging/logging) // Ensure output buffers are flushed immediately (useful for debugging/logging)
setbuf(stdout, NULL); setbuf(stdout, NULL);
@ -165,7 +175,8 @@ int main(int argc, char** argv) {
arguments.resolver_count, arguments.resolver_count,
arguments.domain_name, arguments.domain_name,
arguments.cc_algo_id, arguments.cc_algo_id,
arguments.gso arguments.gso,
arguments.keep_alive_interval
); );
// Free allocated memory for resolver addresses // Free allocated memory for resolver addresses

View file

@ -1,29 +1,35 @@
#include "slipstream_inline_dots.h" #include "slipstream_inline_dots.h"
size_t slipstream_inline_dotify(char * __restrict__ buf, size_t buflen, size_t len) { size_t slipstream_inline_dotify(char * __restrict__ buf, size_t buflen, size_t len) {
size_t dots = len / 57; // Number of dots to insert if (len == 0) { // If there's nothing to do, we do nothing. Efficient, right?
size_t new_len = len + dots; if (buflen > 0) buf[0] = '\0';
return 0;
// Check if result would exceed buffer
if (new_len > buflen) {
return -1; // Error condition
} }
// Start from the end and work backwards size_t dots = len / 57; // Every 57 bytes, a dot. It's the law.
char *src = buf + len - 1; // Points to last char of original string size_t new_len = len + dots;
char *dst = buf + new_len - 1; // Points to where last char will end up
if (new_len + 1 > buflen) {
return (size_t)-1; // Err -> .h? Meh.
}
buf[new_len] = '\0'; // Amen.
char *src = buf + len - 1; // Points to last char of original string
char *dst = buf + new_len - 1; // Points to where last char will end up
// Avoid modulo operation in tight loop // Avoid modulo operation in tight loop
size_t next_dot = len - (len % 57); size_t next_dot = len - (len % 57);
size_t current_pos = len; size_t current_pos = len;
// Move characters right-to-left, inserting dots // Move characters right-to-left, inserting dots
while (current_pos > 0) { while (current_pos > 0) {
if (current_pos == next_dot) { if (current_pos == next_dot) {
*dst-- = '.'; *dst-- = '.'; // Dot. Because rules are rules, even for dots.
next_dot -= 57; next_dot -= 57; // Next dot is 57 chars back.
current_pos--; current_pos--; // Account for the char space the dot took.
continue; continue; // Skip the copy for this iteration, already placed dot.
} }
*dst-- = *src--; *dst-- = *src--;
current_pos--; current_pos--;
@ -36,14 +42,12 @@ size_t slipstream_inline_undotify(char * __restrict__ buf, size_t len) {
char *reader = buf; char *reader = buf;
char *writer = buf; char *writer = buf;
// For ~255 byte buffer with dots every ~50 chars for (size_t i = 0; i < len; ++i) {
// Simple loop is most efficient since dots are sparse
while (len--) {
char c = *reader++; char c = *reader++;
if (c != '.') { if (c != '.')
*writer++ = c; *writer++ = c; // Filter out the noise. Only the good stuff.
}
} }
*writer = '\0'; // Rewind the tape. The future is clean.
return writer - buf; return writer - buf;
} }

View file

@ -31,6 +31,8 @@ char* server_domain_name = NULL;
size_t server_domain_name_len = 0; size_t server_domain_name_len = 0;
ssize_t server_encode(void* slot_p, void* callback_ctx, unsigned char** dest_buf, const unsigned char* src_buf, size_t src_buf_len, size_t* segment_len, struct sockaddr_storage* peer_addr, struct sockaddr_storage* local_addr) { ssize_t server_encode(void* slot_p, void* callback_ctx, unsigned char** dest_buf, const unsigned char* src_buf, size_t src_buf_len, size_t* segment_len, struct sockaddr_storage* peer_addr, struct sockaddr_storage* local_addr) {
*dest_buf = NULL;
// we don't support segmentation in the server // we don't support segmentation in the server
assert(segment_len == NULL || *segment_len == 0 || *segment_len == src_buf_len); assert(segment_len == NULL || *segment_len == 0 || *segment_len == src_buf_len);

View file

@ -126,7 +126,7 @@ int slipstream_packet_loop_(picoquic_network_thread_ctx_t* thread_ctx, picoquic_
slot->path_id = -1; slot->path_id = -1;
nb_slots_written++; nb_slots_written++;
unsigned char* decoded; unsigned char* decoded = NULL;
bytes_recv = param->decode(slot, thread_ctx->loop_callback_ctx, &decoded, bytes_recv = param->decode(slot, thread_ctx->loop_callback_ctx, &decoded,
(const unsigned char*)buffer, bytes_recv, &peer_addr, &local_addr); (const unsigned char*)buffer, bytes_recv, &peer_addr, &local_addr);
if (bytes_recv < 0) { if (bytes_recv < 0) {
@ -208,7 +208,7 @@ int slipstream_packet_loop_(picoquic_network_thread_ctx_t* thread_ctx, picoquic_
int sock_err = 0; int sock_err = 0;
int bytes_sent; int bytes_sent;
unsigned char* encoded; unsigned char* encoded = NULL;
size_t segment_len = send_msg_size == 0 ? send_length : send_msg_size; size_t segment_len = send_msg_size == 0 ? send_length : send_msg_size;
ssize_t encoded_len = param->encode(slot, loop_callback_ctx, &encoded, ssize_t encoded_len = param->encode(slot, loop_callback_ctx, &encoded,
(const unsigned char*)send_buffer, send_length, &segment_len, &peer_addr, &local_addr); (const unsigned char*)send_buffer, send_length, &segment_len, &peer_addr, &local_addr);
@ -279,7 +279,7 @@ int slipstream_packet_loop_(picoquic_network_thread_ctx_t* thread_ctx, picoquic_
int sock_err = 0; int sock_err = 0;
int bytes_sent; int bytes_sent;
unsigned char* encoded; unsigned char* encoded = NULL;
size_t segment_len = send_msg_size == 0 ? send_length : send_msg_size; size_t segment_len = send_msg_size == 0 ? send_length : send_msg_size;
ssize_t encoded_len = param->encode(slot, loop_callback_ctx, &encoded, ssize_t encoded_len = param->encode(slot, loop_callback_ctx, &encoded,
(const unsigned char*)send_buffer, send_length, &segment_len, &peer_addr, &local_addr); (const unsigned char*)send_buffer, send_length, &segment_len, &peer_addr, &local_addr);