├── meson_options.txt ├── docs ├── .gitignore ├── assets │ ├── bytefield.png │ ├── network.png │ ├── socket_loop.png │ ├── protocol_stack.png │ ├── file_transfer_times_dark.png │ └── file_transfer_times_light.png ├── installation │ ├── index.md │ ├── gh.md │ ├── docker.md │ └── build.md ├── _config.yml ├── 404.html ├── benchmark.md ├── Gemfile ├── index.md ├── usage.md ├── Gemfile.lock └── protocol.md ├── meson.options ├── .dockerignore ├── include ├── slipstream_server_cc.h ├── slipstream_sockloop.h ├── slipstream_inline_dots.h ├── lua-resty-base-encoding-base32.h ├── slipstream_utils.h ├── slipstream_slot.h └── slipstream.h ├── .gitignore ├── .gitmodules ├── certs ├── cert.pem └── key.pem ├── Dockerfile ├── README.md ├── src ├── slipstream_inline_dots.c ├── slipstream_server_cc.c ├── slipstream_utils.c ├── slipstream_server_cli.cpp ├── slipstream_client_cli.cpp ├── slipstream_sockloop.c ├── slipstream_server.c └── slipstream_client.c ├── meson.build ├── .github └── workflows │ └── release.yaml └── LICENSE /meson_options.txt: -------------------------------------------------------------------------------- 1 | meson.options -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | _site/ 2 | .sass-cache/ 3 | .jekyll-cache/ 4 | .jekyll-metadata 5 | -------------------------------------------------------------------------------- /meson.options: -------------------------------------------------------------------------------- 1 | # Enable picoquic-log 2 | option('build_loglib', type : 'boolean', value: false) 3 | 4 | -------------------------------------------------------------------------------- /docs/assets/bytefield.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EndPositive/slipstream/HEAD/docs/assets/bytefield.png -------------------------------------------------------------------------------- /docs/assets/network.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EndPositive/slipstream/HEAD/docs/assets/network.png -------------------------------------------------------------------------------- /docs/assets/socket_loop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EndPositive/slipstream/HEAD/docs/assets/socket_loop.png -------------------------------------------------------------------------------- /docs/assets/protocol_stack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EndPositive/slipstream/HEAD/docs/assets/protocol_stack.png -------------------------------------------------------------------------------- /docs/assets/file_transfer_times_dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EndPositive/slipstream/HEAD/docs/assets/file_transfer_times_dark.png -------------------------------------------------------------------------------- /docs/assets/file_transfer_times_light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EndPositive/slipstream/HEAD/docs/assets/file_transfer_times_light.png -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | * 2 | !certs/ 3 | !extern/ 4 | !include/ 5 | !src/ 6 | !subprojects/ 7 | !meson.build 8 | !meson.options 9 | !meson_options.txt 10 | -------------------------------------------------------------------------------- /docs/installation/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Installation 3 | nav_order: 20 4 | --- 5 | 6 | # Installation 7 | 8 | Download slipstream through GitHub releases, Docker (GitHub Container Registry), or build slipstream locally. 9 | -------------------------------------------------------------------------------- /include/slipstream_server_cc.h: -------------------------------------------------------------------------------- 1 | #ifndef SLIPSTREAM_SERVER_CC_H 2 | #define SLIPSTREAM_SERVER_CC_H 3 | 4 | #include "picoquic.h" 5 | 6 | extern picoquic_congestion_algorithm_t* slipstream_server_cc_algorithm; 7 | 8 | #endif //SLIPSTREAM_SERVER_CC_H 9 | -------------------------------------------------------------------------------- /include/slipstream_sockloop.h: -------------------------------------------------------------------------------- 1 | #ifndef SLIPSTREAM_SOCKLOOP_H 2 | #define SLIPSTREAM_SOCKLOOP_H 3 | 4 | #include "picoquic_packet_loop.h" 5 | 6 | void* slipstream_packet_loop(picoquic_network_thread_ctx_t* thread_ctx); 7 | 8 | #endif //SLIPSTREAM_SOCKLOOP_H -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | cmake-build-debug/ 3 | cmake-build-release/ 4 | qlog/ 5 | .tmp.* 6 | .DS_Store 7 | build/ 8 | subprojects/.wraplock 9 | 10 | sample_ticket_store.bin 11 | sample_token_store.bin 12 | 13 | netem.sh 14 | docker-compose-resolver.yaml 15 | unbound.conf 16 | unbound2.conf 17 | 18 | .cache 19 | -------------------------------------------------------------------------------- /include/slipstream_inline_dots.h: -------------------------------------------------------------------------------- 1 | #ifndef SLIPSTREAM_INLINE_DOTS_H 2 | #define SLIPSTREAM_INLINE_DOTS_H 3 | #include 4 | 5 | size_t slipstream_inline_dotify(char * __restrict__ buf, size_t buflen, size_t len); 6 | 7 | size_t slipstream_inline_undotify(char * __restrict__ buf, size_t len); 8 | 9 | #endif // SLIPSTREAM_INLINE_DOTS_H -------------------------------------------------------------------------------- /include/lua-resty-base-encoding-base32.h: -------------------------------------------------------------------------------- 1 | #ifndef LUA_RESTY_BASE_ENCODING_BASE32_H 2 | #define LUA_RESTY_BASE_ENCODING_BASE32_H 3 | 4 | #include 5 | 6 | size_t b32_encode(char *dest, const char *src, size_t len, uint32_t no_padding, uint32_t hex); 7 | size_t b32_decode(char *dest, const char *src, size_t len, uint32_t hex); 8 | 9 | #endif // LUA_RESTY_BASE_ENCODING_BASE32_H -------------------------------------------------------------------------------- /include/slipstream_utils.h: -------------------------------------------------------------------------------- 1 | #ifndef SLIPSTREAM_UTILS_H 2 | #define SLIPSTREAM_UTILS_H 3 | 4 | #include "picoquic.h" 5 | 6 | char* picoquic_connection_id_to_string(const picoquic_connection_id_t* cid); 7 | 8 | void sockaddr_dummy(struct sockaddr_storage *addr_storage); 9 | 10 | void print_sockaddr_ip_and_port(struct sockaddr_storage *addr_storage); 11 | 12 | #endif //SLIPSTREAM_UTILS_H 13 | -------------------------------------------------------------------------------- /include/slipstream_slot.h: -------------------------------------------------------------------------------- 1 | #ifndef SLIPSTREAM_SLOT 2 | #define SLIPSTREAM_SLOT 3 | 4 | #include "SPCDNS/src/dns.h" 5 | #include "picoquic.h" 6 | 7 | typedef struct st_slot_t { 8 | dns_decoded_t dns_decoded[DNS_DECODEBUF_4K]; 9 | dns_rcode_t error; 10 | struct sockaddr_storage peer_addr; 11 | struct sockaddr_storage local_addr; 12 | picoquic_cnx_t* cnx; 13 | int path_id; 14 | bool is_poll_packet; 15 | } slot_t; 16 | 17 | #endif // SLIPSTREAM_SLOT 18 | -------------------------------------------------------------------------------- /docs/_config.yml: -------------------------------------------------------------------------------- 1 | title: slipstream 2 | email: jop-zitman@hotmail.com@hotmail.com 3 | description: >- 4 | High-performance multi-path covert channel over DNS. 5 | baseurl: "/slipstream" # the subpath of your site, e.g. /blog 6 | url: "https://endpositive.github.io" # the base hostname & protocol for your site, e.g. http://example.com 7 | twitter_username: jekyllrb 8 | github_username: jekyll 9 | 10 | remote_theme: just-the-docs/just-the-docs 11 | plugins: 12 | - jekyll-remote-theme 13 | -------------------------------------------------------------------------------- /docs/404.html: -------------------------------------------------------------------------------- 1 | --- 2 | permalink: /404.html 3 | layout: page 4 | --- 5 | 6 | 19 | 20 |
21 |

404

22 | 23 |

Page not found :(

24 |

The requested page could not be found.

25 |
26 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "subprojects/picoquic"] 2 | path = subprojects/picoquic 3 | url = https://github.com/EndPositive/slipstream-picoquic 4 | [submodule "extern/SPCDNS"] 5 | path = extern/SPCDNS 6 | url = https://github.com/spc476/SPCDNS.git 7 | [submodule "extern/lua-resty-base-encoding"] 8 | path = extern/lua-resty-base-encoding 9 | url = https://github.com/spacewander/lua-resty-base-encoding.git 10 | [submodule "extern/quick_arg_parser"] 11 | path = extern/quick_arg_parser 12 | url = https://github.com/Dugy/quick_arg_parser.git 13 | -------------------------------------------------------------------------------- /docs/installation/gh.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: GitHub releases 3 | parent: Installation 4 | nav_order: 21 5 | --- 6 | 7 | # GitHub releases 8 | 9 | slipstream binaries are published to GitHub at every in release. 10 | These are dynamically linked binaries against OpenSSL and GNU C. 11 | Visit the latest GitHub release [here](https://github.com/EndPositive/slipstream/releases/latest). 12 | 13 | ```shell 14 | $ wget https://github.com/EndPositive/slipstream/releases/download/v0.0.1/slipstream-client-v0.0.1-linux-x86_64 15 | ``` 16 | ```shell 17 | $ wget https://github.com/EndPositive/slipstream/releases/download/v0.0.1/slipstream-server-v0.0.1-linux-x86_64 18 | ``` 19 | -------------------------------------------------------------------------------- /docs/benchmark.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Benchmarks 3 | nav_order: 50 4 | --- 5 | 6 | # Benchmarks 7 | 8 | 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. 9 | 10 |

11 | 12 | 13 | Shows a bar chart with benchmark results. 14 | 15 |

16 | 17 |

18 | Exfiltrating a 10 MB file over a single DNS resolver. 19 |

20 | -------------------------------------------------------------------------------- /include/slipstream.h: -------------------------------------------------------------------------------- 1 | #ifndef SLIPSTREAM_H 2 | #define SLIPSTREAM_H 3 | /* Header file for the picoquic sample project. 4 | * It contains the definitions common to client and server */ 5 | 6 | #ifdef __cplusplus 7 | extern "C" { 8 | #endif 9 | 10 | #define SLIPSTREAM_ALPN "picoquic_sample" 11 | #define SLIPSTREAM_SNI "test.example.com" 12 | 13 | #define SLIPSTREAM_NO_ERROR 0 14 | #define SLIPSTREAM_INTERNAL_ERROR 0x101 15 | #define SLIPSTREAM_FILE_CANCEL_ERROR 0x105 16 | 17 | #define SLIPSTREAM_QLOG_DIR "./qlog"; 18 | #include 19 | 20 | typedef struct st_address_t { 21 | struct sockaddr_storage server_address; 22 | bool added; 23 | } address_t; 24 | 25 | int picoquic_slipstream_client(int listen_port, struct st_address_t* server_addresses, size_t server_address_count, const char* domain_name, 26 | const char* cc_algo_id, bool gso, size_t keep_alive_interval); 27 | 28 | int picoquic_slipstream_server(int server_port, const char* pem_cert, const char* pem_key, 29 | struct sockaddr_storage* target_address, const char* domain_name); 30 | 31 | #ifdef __cplusplus 32 | } 33 | #endif 34 | 35 | #endif 36 | -------------------------------------------------------------------------------- /certs/cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDKzCCAhOgAwIBAgIBADANBgkqhkiG9w0BAQsFADAaMRgwFgYDVQQDEw9waWNv 3 | dGxzIHRlc3QgY2EwHhcNMTgwMjIzMDIzODEyWhcNMjgwMjIxMDIzODEyWjAbMRkw 4 | FwYDVQQDExB0ZXN0LmV4YW1wbGUuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A 5 | MIIBCgKCAQEA5soWzSG7iyawQlHM1yaX2dUAATUkhpbg2WPFOEem7E3zYzc6A/Z+ 6 | bViFlfEgL37cbDUb4pnOAHrrsjGgkyBYh5i9iCTVfCk+H6SOHZJORO1Tq8X9C7Wc 7 | NcshpSdm2Pa8hmv9hsHbLSeoPNeg8NkTPwMVaMZ2GpdmiyAmhzSZ2H9mzNI7ntPW 8 | /XCchVf+ax2yt9haZ+mQE2NPYwHDjqCtdGkP5ZXXnYhJSBzSEhxfGckIiKDyOxiN 9 | kLFLvUdT4ERSFBjauP2cSI0XoOUsiBxJNwHH310AU8jZbveSTcXGYgEuu2MIuDo7 10 | Vhkq5+TCqXsIFNbjy0taOoPRvUbPsbqFlQIDAQABo3sweTAJBgNVHRMEAjAAMCwG 11 | CWCGSAGG+EIBDQQfFh1PcGVuU1NMIEdlbmVyYXRlZCBDZXJ0aWZpY2F0ZTAdBgNV 12 | HQ4EFgQUE1vXDjBT8j2etP4brfHQ9DeKnpgwHwYDVR0jBBgwFoAUv3nKl7JgeCCW 13 | qkZXnN+nsiP1JWMwDQYJKoZIhvcNAQELBQADggEBAKwARsxOCiGPXU1xhvs+pq9I 14 | 63mLi4rfnssOGzGnnAfuEaxggpozf3fOSgfyTaDbACdRPTZEStjQ5HMCcHvY7CH0 15 | 8EYA+lkmFbuXXL8uHby1JBTzbTGf8pkRUsuF/Ie0SLChoDgt8oF3mY5pyU4HUaAw 16 | Zp6HBpIRMdmbwGcwm25bl9MQYTrTX3dBfp3XPzfXbVwjJ7bsiTwAGq+dKwzwOQeM 17 | 2ZMZt4BQBoevsNopPrqG0S6kGUmJOIax0t13bKwDj21+Hp/O90HTFVCtAaDxRC56 18 | k0O8Q62ZxzjGJ7Zw6K3azXlH/BYE+CajxTUF+FKRRkkWL1GrFVUsYd9KLDAVry0= 19 | -----END CERTIFICATE----- 20 | -------------------------------------------------------------------------------- /docs/installation/docker.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Docker image 3 | parent: Installation 4 | nav_order: 22 5 | --- 6 | 7 | # GitHub Container Registry 8 | 9 | Docker images are published to the GHCR at every release. 10 | The client and server are packaged in different images. 11 | 12 | * [ghcr.io/endpositive/slipstream-client](https://ghcr.io/endpositive/slipstream-client) 13 | * [ghcr.io/endpositive/slipstream-server](https://ghcr.io/endpositive/slipstream-server) 14 | 15 | ### Tags 16 | 17 | * latest 18 | * vX.X.X 19 | 20 | # Usage 21 | 22 | The client requires port 5201 to be forwarded to the host. 23 | The server requires port 53 to be forwarded. 24 | 25 | ```shell 26 | $ docker run \ 27 | --rm \ 28 | -p 53:53 \ 29 | ghcr.io/endpositive/slipstream-server:v0.0.1 \ 30 | --target-address=x.x.x.x:yy \ 31 | --domain=test.com 32 | ``` 33 | 34 | ```shell 35 | $ docker run \ 36 | --rm \ 37 | -p 5201:5201 \ 38 | ghcr.io/endpositive/slipstream-server:v0.0.1 \ 39 | --domain=test.com \ 40 | --resolver=1.1.1.1:53 41 | ``` 42 | 43 | Any TCP connections on the client's port `5201` will now be forwarded to `x.x.x.x:yy`. 44 | 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. 45 | This may be useful in scenarios where you setup a direct connection between the client and server rather than through public DNS infrastructure. 46 | -------------------------------------------------------------------------------- /docs/Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | # Hello! This is where you manage which Jekyll version is used to run. 3 | # When you want to use a different version, change it below, save the 4 | # file and run `bundle install`. Run Jekyll with `bundle exec`, like so: 5 | # 6 | # bundle exec jekyll serve 7 | # 8 | # This will help ensure the proper Jekyll version is running. 9 | # Happy Jekylling! 10 | # gem "jekyll", "~> 4.4.1" 11 | # This is the default theme for new Jekyll sites. You may change this to anything you like. 12 | # If you want to use GitHub Pages, remove the "gem "jekyll"" above and 13 | # uncomment the line below. To upgrade, run `bundle update github-pages`. 14 | gem "github-pages", group: :jekyll_plugins 15 | # If you have any plugins, put them here! 16 | group :jekyll_plugins do 17 | gem "jekyll-remote-theme", "~> 0.4.3" 18 | end 19 | 20 | # Windows and JRuby does not include zoneinfo files, so bundle the tzinfo-data gem 21 | # and associated library. 22 | platforms :mingw, :x64_mingw, :mswin, :jruby do 23 | gem "tzinfo", ">= 1", "< 3" 24 | gem "tzinfo-data" 25 | end 26 | 27 | # Performance-booster for watching directories on Windows 28 | gem "wdm", "~> 0.1", :platforms => [:mingw, :x64_mingw, :mswin] 29 | 30 | # Lock `http_parser.rb` gem to `v0.6.x` on JRuby builds since newer versions of the gem 31 | # do not have a Java counterpart. 32 | gem "http_parser.rb", "~> 0.6.0", :platforms => [:jruby] 33 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM debian:bookworm-slim AS builder 2 | 3 | WORKDIR /usr/src/app 4 | 5 | ENV DEBIAN_FRONTEND=noninteractive 6 | ENV TZ=Etc/UTC 7 | 8 | RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ 9 | --mount=type=cache,target=/var/lib/apt,sharing=locked \ 10 | apt-get update && apt-get install -y \ 11 | python3-pip \ 12 | cmake \ 13 | git \ 14 | pkg-config \ 15 | libssl-dev \ 16 | ninja-build \ 17 | gcc g++ && \ 18 | pip3 install --user --break-system-packages meson 19 | 20 | COPY . . 21 | 22 | RUN /root/.local/bin/meson setup \ 23 | -Db_lto=true \ 24 | --buildtype=release \ 25 | --warnlevel=0 \ 26 | -Ddefault_library=static \ 27 | meson-build-release && \ 28 | ninja -C meson-build-release && \ 29 | cp /usr/src/app/meson-build-release/slipstream-client . && \ 30 | cp /usr/src/app/meson-build-release/slipstream-server . 31 | 32 | FROM gcr.io/distroless/base-debian12 AS runtime 33 | 34 | WORKDIR /usr/src/app 35 | 36 | COPY ./certs/ ./certs/ 37 | 38 | ENV PATH=/usr/src/app/:$PATH 39 | 40 | LABEL org.opencontainers.image.source=https://github.com/EndPositive/slipstream 41 | 42 | FROM runtime AS client 43 | 44 | COPY --from=builder --chmod=755 /usr/src/app/slipstream-client . 45 | 46 | ENTRYPOINT ["/usr/src/app/slipstream-client"] 47 | 48 | FROM runtime AS server 49 | 50 | COPY --from=builder --chmod=755 /usr/src/app/slipstream-server . 51 | 52 | ENTRYPOINT ["/usr/src/app/slipstream-server"] 53 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Slipstream 2 | ![GitHub Release](https://img.shields.io/github/v/release/EndPositive/slipstream?include_prereleases&sort=semver&display_name=tag) 3 | ![GitHub License](https://img.shields.io/github/license/EndPositive/slipstream) 4 | 5 | A high-performance covert channel over DNS, powered by QUIC multipath. 6 | 7 |

8 | 9 | 10 | 11 | Shows a bar chart with benchmark results. 12 | 13 |

14 | 15 |

16 | Exfiltrating a 10 MB file over a single DNS resolver. 17 |

18 | 19 | ## Highlights 20 | 21 | * Adaptive congestion control for rate-limited resolvers 22 | * Parallel routing over multiple multiple rate-limited resolvers 23 | * 60% lower header overhead than DNSTT 24 | 25 | ## Installation 26 | 27 | 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). 28 | 29 | ## Documentation 30 | 31 | slipstream's documentation is available at [endpositive.github.io/slipstream](https://endpositive.github.io/slipstream/). 32 | 33 | # Acknowledgements 34 | 35 | David Fifield's DNSTT and Turbo Tunnel concept has been a massive source of inspiration. 36 | Although slipstream inherits no code, this work could not have been possible without his ideas. 37 | -------------------------------------------------------------------------------- /certs/key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDmyhbNIbuLJrBC 3 | UczXJpfZ1QABNSSGluDZY8U4R6bsTfNjNzoD9n5tWIWV8SAvftxsNRvimc4Aeuuy 4 | MaCTIFiHmL2IJNV8KT4fpI4dkk5E7VOrxf0LtZw1yyGlJ2bY9ryGa/2GwdstJ6g8 5 | 16Dw2RM/AxVoxnYal2aLICaHNJnYf2bM0jue09b9cJyFV/5rHbK32Fpn6ZATY09j 6 | AcOOoK10aQ/lldediElIHNISHF8ZyQiIoPI7GI2QsUu9R1PgRFIUGNq4/ZxIjReg 7 | 5SyIHEk3AcffXQBTyNlu95JNxcZiAS67Ywi4OjtWGSrn5MKpewgU1uPLS1o6g9G9 8 | Rs+xuoWVAgMBAAECggEBAJZxS/XCNH/b43AH5LCnbrtH1u3yl3HIrp/nIquyQYSu 9 | t6aIXKAysW1UFBiPCz0KxGMhJ6FKQ3gaqMQLB7KAllUl4v75i9SZCe8UlLOAKNdT 10 | oYRK1s4oP8DtPmxrR+bMyE4T3TtX6SkBPfETWs1Fo/8iYnVfUaO559VvSs4+Ir91 11 | 6EVBZ+xSQ6+H4nc9TvrByd7HHMmDGFXF19eo1rtQOxbNMiZSNwch+eFdzUr+hIqT 12 | oeMiZCL+gTiWCglQIQNpRurSdc2szQSZNO85DlNU/kt2z2nxQMRrnl5mWUZdqbTM 13 | qOEMq2RD/lR+fGwC/Zp1jly4f5c3QTF8oPzAhnmog20CgYEA9FtAZlmQ/8c5gDhh 14 | n0dgBRDAVL0hmtDxjsL9RsvTVeWLXecpqUfFd0bpm/pi/QUIkoDc33jmUSh/2vCu 15 | LCG5lvKsDVNwqiT+zsdHwGFyybYPNEPk6ILccr5fL7p8Fox5t9+oE+nct+P+VI82 16 | 3TD/wN2soFPD9wDLjSDSfXMS7TsCgYEA8clXgh9AGJuUKoOHvsV9BFQ1HiFVtuXT 17 | Qa+KKCLDnreeTuzF2s4nV51PJM5QEe0zfOABP2xjPYJRe+zIQ+rMwMA1Yxv17ZH/ 18 | 81w/poziTtZBGfCRmSnRY4gf3lTajUwcJhzmZ4AdV/ZQyX7rjFTZl9jeuMyY8uNY 19 | y3SEpiw6a28CgYAKUhBWQlIte2yiTb9RyuHzVNHKwnI457pMHVA1PUafyiIoxSqt 20 | S6q7bvNO8zRbG2tRRMAPcDvKEbvUs3Wnx4TfK0C5D10i0o0wjpopNfRzMI1T18pD 21 | R8On1QKQMYAsM6KwcXHX5Xi9C5QiXiojDX6/1p0D6IXOWOo/+7LoOYQDIQKBgDJY 22 | KB5x/1igXHOVu5gfau6R0hWZ/0z8Acb1lCDTTEQqG453gqMSteJqYOZbBxUUfNoN 23 | knTwTqGqFulk3jY2F7gyzWr7kXOMKO01UhON1jlwJ1INY2Ou72h4GZqjtHYjWOEe 24 | t2LprDJ6mUu7X7RynnQdthJol5hLeluywUQQhYGFAoGAbfGV4PhKD2sbY5ZDPkiu 25 | 8sWD8fLthbK6yFV7PrWRl4sOe32Iza6bvqT/EU0hbRr54vZXFFMJtev3PzqGNz/o 26 | J5IkcpgVCXjOxIBmRognT4duuEM0EBiH1vaZF1f44x1ntnkncaW3wQ0VGp7xGURI 27 | ykArGNH50gPRuPACNWvYoKE= 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /docs/installation/build.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Local build 3 | parent: Installation 4 | nav_order: 23 5 | --- 6 | 7 | # Building slipstream locally 8 | 9 | To build slipstream locally on debian-based distros, you need the following dependencies installed: 10 | 11 | * meson 12 | * cmake 13 | * git 14 | * pkg-config 15 | * libssl-dev 16 | * ninja-build 17 | * clang (or GCC) 18 | 19 | Clone the slipstream repo and its submodules recursively. 20 | This fetches slipstream, [brotili](https://github.com/google/brotli), [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/). 21 | 22 | ```shell 23 | $ git clone --recurse-submodules https://github.com/EndPositive/slipstream.git 24 | ``` 25 | 26 | You can then configure slipstream by running the following command: 27 | 28 | ```shell 29 | # Configure Meson with debug support (for project developers) 30 | $ meson setup build 31 | # OR for a build with full optimization (recommended for end-users) 32 | $ meson setup --buildtype=release -Db_lto=true --warnlevel=0 build 33 | # Build the client and server binaries 34 | $ meson compile -C build 35 | ``` 36 | 37 | This will place the client and server binaries in the `build/` directory. 38 | OpenSSL and other libraries (e.g. GNU C and C++) are dynamically linked into the binary by default where possible. 39 | 40 | 41 | To enable static linking in as many libraries as possible: 42 | 43 | ```shell 44 | # Add default_library=static option when building 45 | $ meson setup -Ddefault_library=static ... build 46 | ``` 47 | 48 | A logging library exists within picoquic and may be enabled with a meson build option for debugging: 49 | 50 | ```shell 51 | # Configure full logging 52 | $ meson setup -Dbuild_loglib=true ... build 53 | ``` 54 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Introduction 3 | nav_order: 0 4 | --- 5 | 6 | # Introduction 7 | 8 | slipstream is a novel DNS tunnel leveraging the QUIC protocol under the hood. 9 | 10 | DNS tunneling is a technique that allows for data to be transmitted over DNS queries and responses. 11 | 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. 12 | DNS tunneling enables attackers to exfiltrate data, establish Command and Control communication, or bypass network restrictions. 13 | 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. 14 | 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. 15 | However, they are often limited, with throughput often not exceeding several hundred kilobytes per second. 16 | By carefully implementing QUIC compatibility layers, we're able to leverage QUIC's reliability guarantees and introduce a high-performance DNS tunnel. 17 | 18 | ## Quick start 19 | 20 | Follow the [installation](/slipstream/installation/) and [usage instruction](/slipstream/usage/) guides. 21 | 22 | ## Benchmarks 23 | 24 | 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. 25 | For downloading files into the restricted network, slipstream uses up to 37% less queries. 26 | Using QUIC congestion control, it accurately determines the query rate of the used DNS resolver. 27 | 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. 28 | -------------------------------------------------------------------------------- /src/slipstream_inline_dots.c: -------------------------------------------------------------------------------- 1 | #include "slipstream_inline_dots.h" 2 | 3 | size_t slipstream_inline_dotify(char * __restrict__ buf, size_t buflen, size_t len) { 4 | if (len == 0) { // If there's nothing to do, we do nothing. Efficient, right? 5 | if (buflen > 0) buf[0] = '\0'; 6 | return 0; 7 | } 8 | 9 | size_t dots = len / 57; // Every 57 bytes, a dot. It's the law. 10 | size_t new_len = len + dots; 11 | 12 | if (new_len + 1 > buflen) { 13 | return (size_t)-1; // Err -> .h? Meh. 14 | } 15 | 16 | buf[new_len] = '\0'; // Amen. 17 | 18 | char *src = buf + len - 1; // Points to last char of original string 19 | char *dst = buf + new_len - 1; // Points to where last char will end up 20 | 21 | // Avoid modulo operation in tight loop 22 | size_t next_dot = len - (len % 57); 23 | 24 | size_t current_pos = len; 25 | 26 | // Move characters right-to-left, inserting dots 27 | while (current_pos > 0) { 28 | if (current_pos == next_dot) { 29 | *dst-- = '.'; // Dot. Because rules are rules, even for dots. 30 | next_dot -= 57; // Next dot is 57 chars back. 31 | current_pos--; // Account for the char space the dot took. 32 | continue; // Skip the copy for this iteration, already placed dot. 33 | } 34 | *dst-- = *src--; 35 | current_pos--; 36 | } 37 | 38 | return new_len; 39 | } 40 | 41 | size_t slipstream_inline_undotify(char * __restrict__ buf, size_t len) { 42 | char *reader = buf; 43 | char *writer = buf; 44 | 45 | for (size_t i = 0; i < len; ++i) { 46 | char c = *reader++; 47 | if (c != '.') 48 | *writer++ = c; // Filter out the noise. Only the good stuff. 49 | } 50 | 51 | *writer = '\0'; // Rewind the tape. The future is clean. 52 | return writer - buf; 53 | } 54 | -------------------------------------------------------------------------------- /src/slipstream_server_cc.c: -------------------------------------------------------------------------------- 1 | #include "slipstream_server_cc.h" 2 | 3 | #include 4 | 5 | #include 6 | 7 | typedef enum { 8 | slipstream_server_cc_alg_none = 0, 9 | } slipstream_server_cc_alg_state_t; 10 | 11 | typedef struct st_slipstream_server_cc_t { 12 | slipstream_server_cc_alg_state_t state; 13 | } slipstream_server_cc_t; 14 | 15 | 16 | static void slipstream_server_cc_init(picoquic_cnx_t * cnx, picoquic_path_t* path_x, uint64_t current_time) 17 | { 18 | slipstream_server_cc_t* state = (slipstream_server_cc_t*)malloc(sizeof(slipstream_server_cc_t)); 19 | path_x->congestion_alg_state = (void*)state; 20 | } 21 | 22 | static void slipstream_server_cc_notify( 23 | picoquic_cnx_t* cnx, 24 | picoquic_path_t* path_x, 25 | picoquic_congestion_notification_t notification, 26 | picoquic_per_ack_state_t * ack_state, 27 | uint64_t current_time) 28 | { 29 | path_x->is_cc_data_updated = 1; 30 | path_x->cwin = UINT64_MAX; 31 | } 32 | 33 | static void slipstream_server_cc_delete(picoquic_path_t* path_x) { 34 | if (path_x->congestion_alg_state != NULL) { 35 | free(path_x->congestion_alg_state); 36 | path_x->congestion_alg_state = NULL; 37 | } 38 | } 39 | 40 | static void slipstream_server_cc_observe(picoquic_path_t* path_x, uint64_t* cc_state, uint64_t* cc_param) 41 | { 42 | slipstream_server_cc_t* state = (slipstream_server_cc_t*)path_x->congestion_alg_state; 43 | *cc_state = (uint64_t)state->state; 44 | *cc_param = UINT64_MAX; 45 | } 46 | 47 | #define picoquic_slipstream_server_cc_ID "slipstream_server" 48 | #define PICOQUIC_CC_ALGO_NUMBER_SLIPSTREAM_SERVER 10 49 | 50 | picoquic_congestion_algorithm_t slipstream_server_cc_algorithm_struct = { 51 | picoquic_slipstream_server_cc_ID, PICOQUIC_CC_ALGO_NUMBER_SLIPSTREAM_SERVER, 52 | slipstream_server_cc_init, 53 | slipstream_server_cc_notify, 54 | slipstream_server_cc_delete, 55 | slipstream_server_cc_observe 56 | }; 57 | 58 | picoquic_congestion_algorithm_t* slipstream_server_cc_algorithm = &slipstream_server_cc_algorithm_struct; 59 | -------------------------------------------------------------------------------- /src/slipstream_utils.c: -------------------------------------------------------------------------------- 1 | #include "slipstream_utils.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "picoquic_utils.h" 9 | 10 | 11 | char* picoquic_connection_id_to_string(const picoquic_connection_id_t* cid) { 12 | // Each byte needs 2 hex characters + null terminator 13 | char* str = malloc((cid->id_len * 2 + 1) * sizeof(char)); 14 | if (str == NULL) { 15 | return NULL; 16 | } 17 | 18 | // Convert each byte to hex 19 | for (int i = 0; i < cid->id_len; i++) { 20 | sprintf(&str[i * 2], "%02x", cid->id[i]); 21 | } 22 | str[cid->id_len * 2] = '\0'; 23 | 24 | return str; 25 | } 26 | 27 | // Function to create a dummy sockaddr_storage with hardcoded IPv4 and port 28 | void sockaddr_dummy(struct sockaddr_storage *addr_storage) { 29 | // Clear the entire sockaddr_storage to avoid residual data 30 | memset(addr_storage, 0, sizeof(struct sockaddr_storage)); 31 | 32 | // Cast sockaddr_storage to sockaddr_in for IPv4 33 | struct sockaddr_in *addr4 = (struct sockaddr_in *)addr_storage; 34 | 35 | // Set address family to AF_INET (IPv4) 36 | addr4->sin_family = AF_INET; 37 | 38 | // Use a hardcoded IPv4 address: 192.0.2.1 (TEST-NET-1 for testing) 39 | inet_pton(AF_INET, "192.0.2.1", &addr4->sin_addr); 40 | 41 | // Set a hardcoded port: 12345 42 | addr4->sin_port = htons(12345); 43 | 44 | #ifdef __APPLE__ // For BSD systems, set sin_len 45 | addr4->sin_len = sizeof(struct sockaddr_in); 46 | #endif 47 | } 48 | 49 | void print_sockaddr_ip_and_port(struct sockaddr_storage *addr_storage) { 50 | char ip_str[INET6_ADDRSTRLEN]; 51 | int port; 52 | 53 | if (addr_storage->ss_family == AF_INET) { 54 | struct sockaddr_in *addr4 = (struct sockaddr_in *)addr_storage; 55 | inet_ntop(AF_INET, &addr4->sin_addr, ip_str, INET6_ADDRSTRLEN); 56 | port = ntohs(addr4->sin_port); 57 | } else if (addr_storage->ss_family == AF_INET6) { 58 | struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *)addr_storage; 59 | inet_ntop(AF_INET6, &addr6->sin6_addr, ip_str, INET6_ADDRSTRLEN); 60 | port = ntohs(addr6->sin6_port); 61 | } else { 62 | DBG_PRINTF("Unknown address family", NULL); 63 | return; 64 | } 65 | 66 | DBG_PRINTF("%s:%d", ip_str, port); 67 | } 68 | -------------------------------------------------------------------------------- /src/slipstream_server_cli.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "slipstream.h" 6 | #include "quick_arg_parser.hpp" 7 | 8 | struct ServerArgs : MainArguments { 9 | using MainArguments::MainArguments; 10 | 11 | int listen_port = option("dns-listen-port", 'l', "DNS listen port (default: 53)") = 53; 12 | std::string target_address = option("target-address", 'a', "Target server address (default: 127.0.0.1:5201)") = "127.0.0.1:5201"; 13 | std::string cert = option("cert", 'c', "Certificate file path (default: certs/cert.pem)") = "certs/cert.pem"; 14 | std::string key = option("key", 'k', "Private key file path (default: certs/key.pem)") = "certs/key.pem"; 15 | std::string domain = option("domain", 'd', "Domain name this server is authoritative for (Required)"); 16 | 17 | static std::string help(const std::string& program_name) { 18 | return "slipstream-server - A high-performance covert channel over DNS (server)\n\n" 19 | "Usage: " + program_name + " [options]"; 20 | } 21 | 22 | static const std::string version; 23 | }; 24 | 25 | const std::string ServerArgs::version = "slipstream-server 0.1"; 26 | 27 | int main(int argc, char** argv) { 28 | int exit_code = 0; 29 | ServerArgs args(argc, argv); 30 | 31 | #ifdef _WINDOWS 32 | WSADATA wsaData = { 0 }; 33 | int iResult = WSAStartup(MAKEWORD(2, 2), &wsaData); 34 | if (iResult != 0) { 35 | fprintf(stderr, "WSAStartup failed: %d\n", iResult); 36 | return 1; 37 | } 38 | #endif 39 | 40 | // Ensure output buffers are flushed immediately 41 | setbuf(stdout, NULL); 42 | setbuf(stderr, NULL); 43 | 44 | /* Check mandatory server arguments */ 45 | if (args.domain.empty()) { 46 | std::cerr << "Server error: Missing required --domain option" << std::endl; 47 | exit(1); 48 | } 49 | 50 | // Process target address 51 | struct sockaddr_storage target_address; 52 | char server_name[256]; 53 | int server_port = 5201; 54 | 55 | if (sscanf(args.target_address.c_str(), "%255[^:]:%d", server_name, &server_port) < 1) { 56 | strncpy(server_name, args.target_address.c_str(), sizeof(server_name) - 1); 57 | server_name[sizeof(server_name) - 1] = '\0'; 58 | } 59 | 60 | if (server_port <= 0 || server_port > 65535) { 61 | std::cerr << "Invalid port number in target address: " << args.target_address << std::endl; 62 | exit(1); 63 | } 64 | 65 | int is_name = 0; 66 | if (picoquic_get_server_address(server_name, server_port, &target_address, &is_name) != 0) { 67 | std::cerr << "Cannot resolve target address '" << server_name << "' port " << server_port << std::endl; 68 | exit(1); 69 | } 70 | 71 | exit_code = picoquic_slipstream_server( 72 | args.listen_port, 73 | (char*)args.cert.c_str(), 74 | (char*)args.key.c_str(), 75 | &target_address, 76 | (char*)args.domain.c_str() 77 | ); 78 | 79 | #ifdef _WINDOWS 80 | WSACleanup(); 81 | #endif 82 | 83 | exit(exit_code); 84 | } 85 | -------------------------------------------------------------------------------- /src/slipstream_client_cli.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "slipstream.h" 6 | #include "quick_arg_parser.hpp" 7 | 8 | struct ClientArgs : MainArguments { 9 | using MainArguments::MainArguments; 10 | 11 | int listen_port = option("tcp-listen-port", 'l', "Listen port (default: 5201)") = 5201; 12 | std::vector resolver = option("resolver", 'r', "Slipstream server resolver address (e.g., 1.1.1.1 or 8.8.8.8:53). Can be specified multiple times. (Required)"); 13 | std::string congestion_control = option("congestion-control", 'c', "Congestion control algorithm (bbr, dcubic) (default: dcubic)") = "dcubic"; 14 | bool gso = option('g', "GSO enabled (true/false) (default: false). Use --gso or --gso=true to enable."); 15 | std::string domain = option("domain", 'd', "Domain name used for the covert channel (Required)"); 16 | int keep_alive_interval = option("keep-alive-interval", 't', "Send keep alive pings at this interval (default: 400, disabled: 0)") = 400; 17 | 18 | static std::string help(const std::string& program_name) { 19 | return "slipstream-client - A high-performance covert channel over DNS (client)\n\n" 20 | "Usage: " + program_name + " [options]"; 21 | } 22 | 23 | static const std::string version; 24 | }; 25 | 26 | const std::string ClientArgs::version = "slipstream-client 0.1"; 27 | 28 | int main(int argc, char** argv) { 29 | int exit_code = 0; 30 | ClientArgs args(argc, argv); 31 | 32 | #ifdef _WINDOWS 33 | WSADATA wsaData = { 0 }; 34 | int iResult = WSAStartup(MAKEWORD(2, 2), &wsaData); 35 | if (iResult != 0) { 36 | fprintf(stderr, "WSAStartup failed: %d\n", iResult); 37 | return 1; 38 | } 39 | #endif 40 | 41 | // Ensure output buffers are flushed immediately (useful for debugging/logging) 42 | setbuf(stdout, NULL); 43 | setbuf(stderr, NULL); 44 | 45 | /* Check mandatory client arguments */ 46 | if (args.domain.empty()) { 47 | std::cerr << "Client error: Missing required --domain option" << std::endl; 48 | exit(1); 49 | } 50 | if (args.resolver.empty()) { 51 | std::cerr << "Client error: Missing required --resolver option (at least one required)" << std::endl; 52 | exit(1); 53 | } 54 | 55 | // Process resolver addresses 56 | std::vector resolver_addresses; 57 | for (const auto& res_str : args.resolver) { 58 | st_address_t addr; 59 | char server_name[256]; 60 | int server_port = 53; 61 | 62 | if (sscanf(res_str.c_str(), "%255[^:]:%d", server_name, &server_port) < 1) { 63 | strncpy(server_name, res_str.c_str(), sizeof(server_name) - 1); 64 | server_name[sizeof(server_name) - 1] = '\0'; 65 | } 66 | 67 | if (server_port <= 0 || server_port > 65535) { 68 | std::cerr << "Invalid port number in resolver address: " << res_str << std::endl; 69 | exit(1); 70 | } 71 | 72 | int is_name = 0; 73 | if (picoquic_get_server_address(server_name, server_port, &addr.server_address, &is_name) != 0) { 74 | std::cerr << "Cannot resolve resolver address '" << server_name << "' port " << server_port << std::endl; 75 | exit(1); 76 | } 77 | resolver_addresses.push_back(addr); 78 | } 79 | 80 | exit_code = picoquic_slipstream_client( 81 | args.listen_port, 82 | resolver_addresses.data(), 83 | resolver_addresses.size(), 84 | (char*)args.domain.c_str(), 85 | (char*)args.congestion_control.c_str(), 86 | args.gso, 87 | args.keep_alive_interval 88 | ); 89 | 90 | #ifdef _WINDOWS 91 | WSACleanup(); 92 | #endif 93 | 94 | exit(exit_code); 95 | } 96 | -------------------------------------------------------------------------------- /meson.build: -------------------------------------------------------------------------------- 1 | # Set up a C and C++ project with useful defaults for debugging 2 | project( 3 | 'slipstream', 4 | ['c', 'cpp'], 5 | version: '0.1.0', 6 | license: 'Apache-2.0', 7 | meson_version: '>=1.0.0', 8 | default_options: [ 9 | 'buildtype=debugoptimized', 10 | 'cpp_std=gnu++11', 11 | 'c_std=gnu11', 12 | 'default_library=shared', 13 | 'warning_level=everything', 14 | 'b_lto=false', 15 | ], 16 | ) 17 | 18 | # Aliases to significant locations in the source tree 19 | src = 'src' 20 | extern = 'extern' 21 | lua_resty = extern / 'lua-resty-base-encoding' 22 | quick_args = extern / 'quick_arg_parser' 23 | spcdns = extern / 'SPCDNS' / src 24 | 25 | # Sources common to both client and server 26 | common_sources = files( 27 | src / 'slipstream_sockloop.c', 28 | src / 'slipstream_utils.c', 29 | src / 'slipstream_inline_dots.c', 30 | lua_resty / 'base32.c', 31 | spcdns / 'codec.c', 32 | spcdns / 'mappings.c', 33 | spcdns / 'netsimple.c', 34 | spcdns / 'output.c', 35 | ) 36 | 37 | # Various directories with headers for the sources 38 | include_dirs = include_directories('include', extern, quick_args, spcdns, lua_resty) 39 | 40 | # Default linker arguments 41 | link_args = [] 42 | 43 | # Set up static links 44 | is_static = get_option('default_library') == 'static' 45 | if is_static and host_machine.system() != 'darwin' 46 | link_args = [ 47 | '-static-libgcc', 48 | '-static-libstdc++', 49 | ] 50 | endif 51 | 52 | # Import the CMake module 53 | cmake = import('cmake') 54 | 55 | # Get the cmake options objects 56 | picoquic_opts = cmake.subproject_options() 57 | 58 | # Add necessary CMake defines 59 | picoquic_opts.add_cmake_defines( 60 | [ 61 | {'PICOQUIC_FETCH_PTLS': 'ON'}, 62 | {'BUILD_HTTP': 'OFF'}, 63 | {'ENABLE_ASAN': 'OFF'}, 64 | {'ENABLE_UBSAN': 'OFF'}, 65 | {'CMAKE_POLICY_VERSION_MINIMUM': '3.5'}, 66 | {'BUILD_DEMO': 'OFF'}, 67 | {'BUILD_TESTING': 'OFF'}, 68 | ], 69 | ) 70 | # Configure picoquic debug printing 71 | if get_option('buildtype').startswith('debug') 72 | picoquic_opts.add_cmake_defines( 73 | {'DISABLE_DEBUG_PRINTF': 'OFF'}, 74 | ) 75 | else 76 | picoquic_opts.add_cmake_defines( 77 | {'DISABLE_DEBUG_PRINTF': 'ON'}, 78 | ) 79 | endif 80 | # Configure optional picoquic logging 81 | loglib_enabled = get_option('build_loglib') 82 | if loglib_enabled 83 | picoquic_opts.add_cmake_defines( 84 | {'BUILD_LOGREADER': 'ON'}, 85 | ) 86 | else 87 | picoquic_opts.add_cmake_defines( 88 | {'BUILD_LOGREADER': 'OFF'}, 89 | ) 90 | endif 91 | 92 | # Reference picoquic as a CMake subproject 93 | picoquic_proj = cmake.subproject( 94 | 'picoquic', 95 | options: picoquic_opts, 96 | ) 97 | 98 | # Dependencies used by both executables 99 | slipstream_cli_deps = [ 100 | dependency('threads'), # for picoquic 101 | picoquic_proj.dependency('picoquic-core'), 102 | ] 103 | # Link optional picoquic logging 104 | if loglib_enabled 105 | slipstream_cli_deps += picoquic_proj.dependency('picoquic-log') 106 | endif 107 | 108 | # Sources only used by client 109 | client_sources = files( 110 | src / 'slipstream_client.c', 111 | src / 'slipstream_client_cli.cpp', 112 | ) 113 | 114 | # Sources only used by server 115 | server_sources = files( 116 | src / 'slipstream_server.c', 117 | src / 'slipstream_server_cc.c', 118 | src / 'slipstream_server_cli.cpp', 119 | ) 120 | 121 | # The final client executable 122 | executable( 123 | 'slipstream-client', 124 | client_sources + common_sources, 125 | dependencies: slipstream_cli_deps, 126 | include_directories: include_dirs, 127 | link_args: link_args, 128 | build_by_default: true, 129 | ) 130 | 131 | # The final server executable 132 | executable( 133 | 'slipstream-server', 134 | server_sources + common_sources, 135 | dependencies: slipstream_cli_deps, 136 | include_directories: include_dirs, 137 | link_args: link_args, 138 | build_by_default: true, 139 | ) 140 | -------------------------------------------------------------------------------- /docs/usage.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Usage instructions 3 | nav_order: 30 4 | --- 5 | 6 | # Usage instructions 7 | 8 | slipstream is designed to tunnel TCP traffic over DNS messages. 9 | Since DNS is a distributed system, we can abuse existing DNS infrastructure in the tunnel. 10 | For example, the figure below shows multiple network routes using a public DNS resolver to pass through the local or country scoped firewall. 11 | This is especially useful when a network mandates the use of a DNS resolver as assigned in the DHCP configuration. 12 | 13 | ![DNS tunnel network setup](/slipstream/assets/network.png) 14 | 15 | slipstream consists of a server and client binary. 16 | 17 | #### Server 18 | 19 | The server is the one to be placed on the outside of the restricted network. 20 | It will act as the authoritative nameserver for a given domain. 21 | It will forward received connections to a TCP service specified in the CLI arguments. 22 | 23 | ```shell 24 | $ slipstream-server 25 | --target-address=x.x.x.x:yy \ # TCP address of the service to access 26 | --domain=test.com 27 | ``` 28 | 29 | #### Client 30 | 31 | The client is placed inside the restricted network. 32 | 33 | 34 | ```shell 35 | $ slipstream-client \ 36 | --resolver-address=x.x.x.x:yy \ # Address of public DNS resolver or DHCP assigned resolver 37 | --domain=test.com 38 | ``` 39 | 40 | 41 | ### Configuration of DNS records 42 | 43 | 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. 44 | For example, add a NS record for `test.com` pointing to `ns.test.com`. 45 | Then add an A record on `ns.test.com` pointing to your slipstream server IP. 46 | 47 | ``` 48 | @ IN NS ns.test.com. 49 | ns IN A x.x.x.x:yy ; # Address of slipstream server 50 | ``` 51 | 52 | ### Direct connection 53 | 54 | It is also possible to setup a direct connection between the client and the server. 55 | This allows to impersonate DNS traffic on port 53 without actually using any public infrastructure. 56 | This is a similar trick to using WireGuard on port 53, additionally encoding as DNS traffic. 57 | 58 | ```shell 59 | $ slipstream-client \ 60 | --congestion-control=bbr \ # Faster better than dcubic in direct connections 61 | --resolver-address=x.x.x.x:yy \ # Address of slipstream server 62 | --domain=test.com 63 | ``` 64 | 65 | ## Example data transfer 66 | 67 | An example of a sending data from the client to the server over a direct slipstream connection. 68 | 69 | ```shell 70 | $ nc -l -p 5201 71 | $ slipstream-server \ 72 | --dns-listen-port=8853 \ 73 | --target-address=127.0.0.1:5201 \ 74 | --domain=test.com 75 | ``` 76 | 77 | ```shell 78 | $ slipstream-client \ 79 | --congestion-control=bbr \ 80 | --tcp-listen-port=7000 \ 81 | --resolver=127.0.0.1:8853 \ 82 | --domain=test.com 83 | Adding 127.0.0.1:8853 84 | Starting connection to 127.0.0.1 85 | Initial connection ID: 54545454 86 | Listening on port 7000... 87 | Connection completed, almost ready. 88 | Connection confirmed. 89 | ``` 90 | 91 | You can then connect to the slipstream client on port 7000 as if you were connecting to the nc client on port 5201. 92 | 93 | ```shell 94 | $ base64 /dev/urandom | head -c 5000000 | nc 127.0.0.1 7000 95 | 96 | # slipstream client wakes up 97 | [0:9] accept: connection 98 | [0:9] wakeup 99 | [0:9] activate: stream 100 | [0:9] recv->quic_send: empty, disactivate 101 | [0:9] wakeup 102 | [0:9] activate: stream 103 | [0:9] recv->quic_send: empty, disactivate 104 | [0:9] wakeup 105 | [0:9] activate: stream 106 | [0:9] recv->quic_send: empty, disactivate 107 | [0:9] recv: closed stream 108 | ``` 109 | ```shell 110 | # base64 data arrives on the server 111 | S9w3u5up+c39u6vrkBtxKbSxOJA2UElczDgc3x4h3TtZtzvgMX05Ig4whEYDvY5MP8g4dJ1QsXX1 112 | fSDm0y6mOlQ4fQhYchkyKt18fV0tpBkLrPwv6MkW+IaksKe7Qo61s3gxu2jrPBlC1yxML+rYZU93 113 | MYNB7rFC6s3a0eHmfdsfbtBbFIF809X91fqd6gYiKPtWAHc0J5OsEyqMI3QcUGSDJd4Sw+iAC5X7 114 | ``` 115 | -------------------------------------------------------------------------------- /.github/workflows/release.yaml: -------------------------------------------------------------------------------- 1 | name: Release slipstream 2 | 3 | on: 4 | push: 5 | tags: 6 | - "v*.*.*" 7 | 8 | env: 9 | BUILD_TYPE: Release 10 | 11 | jobs: 12 | build: 13 | runs-on: ubuntu-latest 14 | 15 | permissions: 16 | contents: write 17 | 18 | steps: 19 | - uses: actions/checkout@v4 20 | with: 21 | submodules: recursive 22 | 23 | - name: Install build dependencies 24 | run: | 25 | sudo apt-get update 26 | sudo apt install -y pkg-config libssl-dev ninja-build 27 | 28 | - name: Configure CMake 29 | run: | 30 | cmake \ 31 | -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} \ 32 | -DCMAKE_MAKE_PROGRAM=ninja \ 33 | -DCMAKE_C_COMPILER=clang \ 34 | -DCMAKE_CXX_COMPILER=clang++ \ 35 | -G Ninja \ 36 | -S ${{github.workspace}} \ 37 | -B ${{github.workspace}}/build 38 | 39 | - name: Build with CMake 40 | run: | 41 | cmake \ 42 | --build ${{github.workspace}}/build \ 43 | -j $(nproc) \ 44 | --target slipstream-client slipstream-server 45 | 46 | - name: Rename Binaries 47 | run: | 48 | VERSION=${{ github.ref_name }} 49 | mv ${{github.workspace}}/build/slipstream-client ${{github.workspace}}/build/slipstream-client-${VERSION}-linux-x86_64 50 | mv ${{github.workspace}}/build/slipstream-server ${{github.workspace}}/build/slipstream-server-${VERSION}-linux-x86_64 51 | 52 | - name: Release 53 | uses: softprops/action-gh-release@v2 54 | if: github.ref_type == 'tag' 55 | with: 56 | draft: true 57 | generate_release_notes: true 58 | files: | 59 | build/slipstream-client-* 60 | build/slipstream-server-* 61 | 62 | docker-build: 63 | runs-on: ubuntu-latest 64 | 65 | permissions: 66 | contents: read 67 | packages: write 68 | attestations: write 69 | id-token: write 70 | 71 | env: 72 | REGISTRY: ghcr.io 73 | IMAGE_NAME: ${{ github.repository }} 74 | 75 | steps: 76 | - uses: actions/checkout@v4 77 | with: 78 | submodules: recursive 79 | 80 | - name: Log in to the Container registry 81 | uses: docker/login-action@65b78e6e13532edd9afa3aa52ac7964289d1a9c1 82 | with: 83 | registry: ${{ env.REGISTRY }} 84 | username: ${{ github.actor }} 85 | password: ${{ secrets.GITHUB_TOKEN }} 86 | 87 | - name: Set up Docker Buildx 88 | uses: docker/setup-buildx-action@v3 89 | 90 | - name: Extract metadata (tags, labels) for Docker (server) 91 | id: meta-server 92 | uses: docker/metadata-action@v3 93 | with: 94 | images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-server 95 | 96 | - name: Extract metadata (tags, labels) for Docker (client) 97 | id: meta-client 98 | uses: docker/metadata-action@v3 99 | with: 100 | images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-client 101 | 102 | - name: Build and push Docker image (server) 103 | id: push-server 104 | uses: docker/build-push-action@v6 105 | with: 106 | context: . 107 | push: true 108 | tags: ${{ steps.meta-server.outputs.tags }} 109 | labels: ${{ steps.meta-server.outputs.labels }} 110 | target: server 111 | 112 | - name: Build and push Docker image (client) 113 | id: push-client 114 | uses: docker/build-push-action@v6 115 | with: 116 | context: . 117 | push: true 118 | tags: ${{ steps.meta-client.outputs.tags }} 119 | labels: ${{ steps.meta-client.outputs.labels }} 120 | target: client 121 | 122 | - name: Generate artifact attestation (server) 123 | uses: actions/attest-build-provenance@v2 124 | with: 125 | subject-name: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-server 126 | subject-digest: ${{ steps.push-server.outputs.digest }} 127 | push-to-registry: true 128 | 129 | - name: Generate artifact attestation (client) 130 | uses: actions/attest-build-provenance@v2 131 | with: 132 | subject-name: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-client 133 | subject-digest: ${{ steps.push-client.outputs.digest }} 134 | push-to-registry: true 135 | -------------------------------------------------------------------------------- /docs/Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | activesupport (8.0.2) 5 | base64 6 | benchmark (>= 0.3) 7 | bigdecimal 8 | concurrent-ruby (~> 1.0, >= 1.3.1) 9 | connection_pool (>= 2.2.5) 10 | drb 11 | i18n (>= 1.6, < 2) 12 | logger (>= 1.4.2) 13 | minitest (>= 5.1) 14 | securerandom (>= 0.3) 15 | tzinfo (~> 2.0, >= 2.0.5) 16 | uri (>= 0.13.1) 17 | addressable (2.8.7) 18 | public_suffix (>= 2.0.2, < 7.0) 19 | base64 (0.2.0) 20 | benchmark (0.4.0) 21 | bigdecimal (3.1.9) 22 | coffee-script (2.4.1) 23 | coffee-script-source 24 | execjs 25 | coffee-script-source (1.12.2) 26 | colorator (1.1.0) 27 | commonmarker (0.23.11) 28 | concurrent-ruby (1.3.5) 29 | connection_pool (2.5.3) 30 | csv (3.3.4) 31 | dnsruby (1.72.4) 32 | base64 (~> 0.2.0) 33 | logger (~> 1.6.5) 34 | simpleidn (~> 0.2.1) 35 | drb (2.2.1) 36 | em-websocket (0.5.3) 37 | eventmachine (>= 0.12.9) 38 | http_parser.rb (~> 0) 39 | ethon (0.16.0) 40 | ffi (>= 1.15.0) 41 | eventmachine (1.2.7) 42 | execjs (2.10.0) 43 | faraday (2.13.1) 44 | faraday-net_http (>= 2.0, < 3.5) 45 | json 46 | logger 47 | faraday-net_http (3.4.0) 48 | net-http (>= 0.5.0) 49 | ffi (1.17.2-aarch64-linux-gnu) 50 | ffi (1.17.2-aarch64-linux-musl) 51 | ffi (1.17.2-arm-linux-gnu) 52 | ffi (1.17.2-arm-linux-musl) 53 | ffi (1.17.2-arm64-darwin) 54 | ffi (1.17.2-x86_64-darwin) 55 | ffi (1.17.2-x86_64-linux-gnu) 56 | ffi (1.17.2-x86_64-linux-musl) 57 | forwardable-extended (2.6.0) 58 | gemoji (4.1.0) 59 | github-pages (232) 60 | github-pages-health-check (= 1.18.2) 61 | jekyll (= 3.10.0) 62 | jekyll-avatar (= 0.8.0) 63 | jekyll-coffeescript (= 1.2.2) 64 | jekyll-commonmark-ghpages (= 0.5.1) 65 | jekyll-default-layout (= 0.1.5) 66 | jekyll-feed (= 0.17.0) 67 | jekyll-gist (= 1.5.0) 68 | jekyll-github-metadata (= 2.16.1) 69 | jekyll-include-cache (= 0.2.1) 70 | jekyll-mentions (= 1.6.0) 71 | jekyll-optional-front-matter (= 0.3.2) 72 | jekyll-paginate (= 1.1.0) 73 | jekyll-readme-index (= 0.3.0) 74 | jekyll-redirect-from (= 0.16.0) 75 | jekyll-relative-links (= 0.6.1) 76 | jekyll-remote-theme (= 0.4.3) 77 | jekyll-sass-converter (= 1.5.2) 78 | jekyll-seo-tag (= 2.8.0) 79 | jekyll-sitemap (= 1.4.0) 80 | jekyll-swiss (= 1.0.0) 81 | jekyll-theme-architect (= 0.2.0) 82 | jekyll-theme-cayman (= 0.2.0) 83 | jekyll-theme-dinky (= 0.2.0) 84 | jekyll-theme-hacker (= 0.2.0) 85 | jekyll-theme-leap-day (= 0.2.0) 86 | jekyll-theme-merlot (= 0.2.0) 87 | jekyll-theme-midnight (= 0.2.0) 88 | jekyll-theme-minimal (= 0.2.0) 89 | jekyll-theme-modernist (= 0.2.0) 90 | jekyll-theme-primer (= 0.6.0) 91 | jekyll-theme-slate (= 0.2.0) 92 | jekyll-theme-tactile (= 0.2.0) 93 | jekyll-theme-time-machine (= 0.2.0) 94 | jekyll-titles-from-headings (= 0.5.3) 95 | jemoji (= 0.13.0) 96 | kramdown (= 2.4.0) 97 | kramdown-parser-gfm (= 1.1.0) 98 | liquid (= 4.0.4) 99 | mercenary (~> 0.3) 100 | minima (= 2.5.1) 101 | nokogiri (>= 1.16.2, < 2.0) 102 | rouge (= 3.30.0) 103 | terminal-table (~> 1.4) 104 | webrick (~> 1.8) 105 | github-pages-health-check (1.18.2) 106 | addressable (~> 2.3) 107 | dnsruby (~> 1.60) 108 | octokit (>= 4, < 8) 109 | public_suffix (>= 3.0, < 6.0) 110 | typhoeus (~> 1.3) 111 | html-pipeline (2.14.3) 112 | activesupport (>= 2) 113 | nokogiri (>= 1.4) 114 | http_parser.rb (0.8.0) 115 | i18n (1.14.7) 116 | concurrent-ruby (~> 1.0) 117 | jekyll (3.10.0) 118 | addressable (~> 2.4) 119 | colorator (~> 1.0) 120 | csv (~> 3.0) 121 | em-websocket (~> 0.5) 122 | i18n (>= 0.7, < 2) 123 | jekyll-sass-converter (~> 1.0) 124 | jekyll-watch (~> 2.0) 125 | kramdown (>= 1.17, < 3) 126 | liquid (~> 4.0) 127 | mercenary (~> 0.3.3) 128 | pathutil (~> 0.9) 129 | rouge (>= 1.7, < 4) 130 | safe_yaml (~> 1.0) 131 | webrick (>= 1.0) 132 | jekyll-avatar (0.8.0) 133 | jekyll (>= 3.0, < 5.0) 134 | jekyll-coffeescript (1.2.2) 135 | coffee-script (~> 2.2) 136 | coffee-script-source (~> 1.12) 137 | jekyll-commonmark (1.4.0) 138 | commonmarker (~> 0.22) 139 | jekyll-commonmark-ghpages (0.5.1) 140 | commonmarker (>= 0.23.7, < 1.1.0) 141 | jekyll (>= 3.9, < 4.0) 142 | jekyll-commonmark (~> 1.4.0) 143 | rouge (>= 2.0, < 5.0) 144 | jekyll-default-layout (0.1.5) 145 | jekyll (>= 3.0, < 5.0) 146 | jekyll-feed (0.17.0) 147 | jekyll (>= 3.7, < 5.0) 148 | jekyll-gist (1.5.0) 149 | octokit (~> 4.2) 150 | jekyll-github-metadata (2.16.1) 151 | jekyll (>= 3.4, < 5.0) 152 | octokit (>= 4, < 7, != 4.4.0) 153 | jekyll-include-cache (0.2.1) 154 | jekyll (>= 3.7, < 5.0) 155 | jekyll-mentions (1.6.0) 156 | html-pipeline (~> 2.3) 157 | jekyll (>= 3.7, < 5.0) 158 | jekyll-optional-front-matter (0.3.2) 159 | jekyll (>= 3.0, < 5.0) 160 | jekyll-paginate (1.1.0) 161 | jekyll-readme-index (0.3.0) 162 | jekyll (>= 3.0, < 5.0) 163 | jekyll-redirect-from (0.16.0) 164 | jekyll (>= 3.3, < 5.0) 165 | jekyll-relative-links (0.6.1) 166 | jekyll (>= 3.3, < 5.0) 167 | jekyll-remote-theme (0.4.3) 168 | addressable (~> 2.0) 169 | jekyll (>= 3.5, < 5.0) 170 | jekyll-sass-converter (>= 1.0, <= 3.0.0, != 2.0.0) 171 | rubyzip (>= 1.3.0, < 3.0) 172 | jekyll-sass-converter (1.5.2) 173 | sass (~> 3.4) 174 | jekyll-seo-tag (2.8.0) 175 | jekyll (>= 3.8, < 5.0) 176 | jekyll-sitemap (1.4.0) 177 | jekyll (>= 3.7, < 5.0) 178 | jekyll-swiss (1.0.0) 179 | jekyll-theme-architect (0.2.0) 180 | jekyll (> 3.5, < 5.0) 181 | jekyll-seo-tag (~> 2.0) 182 | jekyll-theme-cayman (0.2.0) 183 | jekyll (> 3.5, < 5.0) 184 | jekyll-seo-tag (~> 2.0) 185 | jekyll-theme-dinky (0.2.0) 186 | jekyll (> 3.5, < 5.0) 187 | jekyll-seo-tag (~> 2.0) 188 | jekyll-theme-hacker (0.2.0) 189 | jekyll (> 3.5, < 5.0) 190 | jekyll-seo-tag (~> 2.0) 191 | jekyll-theme-leap-day (0.2.0) 192 | jekyll (> 3.5, < 5.0) 193 | jekyll-seo-tag (~> 2.0) 194 | jekyll-theme-merlot (0.2.0) 195 | jekyll (> 3.5, < 5.0) 196 | jekyll-seo-tag (~> 2.0) 197 | jekyll-theme-midnight (0.2.0) 198 | jekyll (> 3.5, < 5.0) 199 | jekyll-seo-tag (~> 2.0) 200 | jekyll-theme-minimal (0.2.0) 201 | jekyll (> 3.5, < 5.0) 202 | jekyll-seo-tag (~> 2.0) 203 | jekyll-theme-modernist (0.2.0) 204 | jekyll (> 3.5, < 5.0) 205 | jekyll-seo-tag (~> 2.0) 206 | jekyll-theme-primer (0.6.0) 207 | jekyll (> 3.5, < 5.0) 208 | jekyll-github-metadata (~> 2.9) 209 | jekyll-seo-tag (~> 2.0) 210 | jekyll-theme-slate (0.2.0) 211 | jekyll (> 3.5, < 5.0) 212 | jekyll-seo-tag (~> 2.0) 213 | jekyll-theme-tactile (0.2.0) 214 | jekyll (> 3.5, < 5.0) 215 | jekyll-seo-tag (~> 2.0) 216 | jekyll-theme-time-machine (0.2.0) 217 | jekyll (> 3.5, < 5.0) 218 | jekyll-seo-tag (~> 2.0) 219 | jekyll-titles-from-headings (0.5.3) 220 | jekyll (>= 3.3, < 5.0) 221 | jekyll-watch (2.2.1) 222 | listen (~> 3.0) 223 | jemoji (0.13.0) 224 | gemoji (>= 3, < 5) 225 | html-pipeline (~> 2.2) 226 | jekyll (>= 3.0, < 5.0) 227 | json (2.12.0) 228 | kramdown (2.4.0) 229 | rexml 230 | kramdown-parser-gfm (1.1.0) 231 | kramdown (~> 2.0) 232 | liquid (4.0.4) 233 | listen (3.9.0) 234 | rb-fsevent (~> 0.10, >= 0.10.3) 235 | rb-inotify (~> 0.9, >= 0.9.10) 236 | logger (1.6.6) 237 | mercenary (0.3.6) 238 | minima (2.5.1) 239 | jekyll (>= 3.5, < 5.0) 240 | jekyll-feed (~> 0.9) 241 | jekyll-seo-tag (~> 2.1) 242 | minitest (5.25.5) 243 | net-http (0.6.0) 244 | uri 245 | nokogiri (1.18.8-aarch64-linux-gnu) 246 | racc (~> 1.4) 247 | nokogiri (1.18.8-aarch64-linux-musl) 248 | racc (~> 1.4) 249 | nokogiri (1.18.8-arm-linux-gnu) 250 | racc (~> 1.4) 251 | nokogiri (1.18.8-arm-linux-musl) 252 | racc (~> 1.4) 253 | nokogiri (1.18.8-arm64-darwin) 254 | racc (~> 1.4) 255 | nokogiri (1.18.8-x86_64-darwin) 256 | racc (~> 1.4) 257 | nokogiri (1.18.8-x86_64-linux-gnu) 258 | racc (~> 1.4) 259 | nokogiri (1.18.8-x86_64-linux-musl) 260 | racc (~> 1.4) 261 | octokit (4.25.1) 262 | faraday (>= 1, < 3) 263 | sawyer (~> 0.9) 264 | pathutil (0.16.2) 265 | forwardable-extended (~> 2.6) 266 | public_suffix (5.1.1) 267 | racc (1.8.1) 268 | rb-fsevent (0.11.2) 269 | rb-inotify (0.11.1) 270 | ffi (~> 1.0) 271 | rexml (3.4.1) 272 | rouge (3.30.0) 273 | rubyzip (2.4.1) 274 | safe_yaml (1.0.5) 275 | sass (3.7.4) 276 | sass-listen (~> 4.0.0) 277 | sass-listen (4.0.0) 278 | rb-fsevent (~> 0.9, >= 0.9.4) 279 | rb-inotify (~> 0.9, >= 0.9.7) 280 | sawyer (0.9.2) 281 | addressable (>= 2.3.5) 282 | faraday (>= 0.17.3, < 3) 283 | securerandom (0.4.1) 284 | simpleidn (0.2.3) 285 | terminal-table (1.8.0) 286 | unicode-display_width (~> 1.1, >= 1.1.1) 287 | typhoeus (1.4.1) 288 | ethon (>= 0.9.0) 289 | tzinfo (2.0.6) 290 | concurrent-ruby (~> 1.0) 291 | unicode-display_width (1.8.0) 292 | uri (1.0.3) 293 | webrick (1.9.1) 294 | 295 | PLATFORMS 296 | aarch64-linux-gnu 297 | aarch64-linux-musl 298 | arm-linux-gnu 299 | arm-linux-musl 300 | arm64-darwin 301 | x86_64-darwin 302 | x86_64-linux-gnu 303 | x86_64-linux-musl 304 | 305 | DEPENDENCIES 306 | github-pages 307 | http_parser.rb (~> 0.6.0) 308 | jekyll-remote-theme (~> 0.4.3) 309 | tzinfo (>= 1, < 3) 310 | tzinfo-data 311 | wdm (~> 0.1) 312 | 313 | BUNDLED WITH 314 | 2.6.9 315 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /src/slipstream_sockloop.c: -------------------------------------------------------------------------------- 1 | #include "slipstream_sockloop.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #ifndef __USE_XOPEN2K 11 | #define __USE_XOPEN2K 12 | #endif 13 | #ifndef __USE_POSIX 14 | #define __USE_POSIX 15 | #endif 16 | #include 17 | 18 | #ifndef __APPLE__ 19 | #ifdef __LINUX__ 20 | #include /* Definition of PR_* constants */ 21 | #else 22 | #endif 23 | #endif 24 | 25 | #include 26 | 27 | #ifndef SOCKET_TYPE 28 | #define SOCKET_TYPE int 29 | #endif 30 | #ifndef INVALID_SOCKET 31 | #define INVALID_SOCKET -1 32 | #endif 33 | #ifndef SOCKET_CLOSE 34 | #define SOCKET_CLOSE(x) close(x) 35 | #endif 36 | #ifndef WSA_LAST_ERROR 37 | #define WSA_LAST_ERROR(x) ((long)(x)) 38 | #endif 39 | 40 | #include "picosocks.h" 41 | #include "picoquic.h" 42 | #include "picoquic_internal.h" 43 | #include "picoquic_packet_loop.h" 44 | #include "slipstream_slot.h" 45 | 46 | # if defined(UDP_SEGMENT) 47 | static int udp_gso_available = 1; 48 | #else 49 | static int udp_gso_available = 0; 50 | #endif 51 | 52 | 53 | int slipstream_packet_loop_(picoquic_network_thread_ctx_t* thread_ctx, picoquic_socket_ctx_t* s_ctx) { 54 | picoquic_quic_t* quic = thread_ctx->quic; 55 | picoquic_packet_loop_param_t* param = thread_ctx->param; 56 | const picoquic_packet_loop_cb_fn loop_callback = thread_ctx->loop_callback; 57 | void* loop_callback_ctx = thread_ctx->loop_callback_ctx; 58 | slot_t slots[PICOQUIC_PACKET_LOOP_RECV_MAX] = {0}; 59 | 60 | size_t send_buffer_size = param->socket_buffer_size; 61 | size_t send_msg_size = 0; 62 | size_t* send_msg_ptr = NULL; 63 | if (udp_gso_available && !param->do_not_use_gso) { 64 | send_buffer_size = 0xFFFF; 65 | send_msg_ptr = &send_msg_size; 66 | } 67 | if (send_buffer_size == 0) { 68 | send_buffer_size = 0xffff; 69 | } 70 | 71 | size_t buffer_size; 72 | if (param->is_client) { 73 | buffer_size = PICOQUIC_MAX_PACKET_SIZE; 74 | } else { 75 | buffer_size = MAX_DNS_QUERY_SIZE; 76 | } 77 | 78 | while (!thread_ctx->thread_should_close) { 79 | if (loop_callback) { 80 | int ret = loop_callback(quic, picoquic_packet_loop_before_select, loop_callback_ctx, s_ctx); 81 | if (ret < 0) { 82 | break; 83 | } 84 | } 85 | 86 | size_t nb_slots_written = 0; 87 | size_t nb_packet_received = 0; 88 | while (nb_slots_written < PICOQUIC_PACKET_LOOP_RECV_MAX) { 89 | int64_t delta_t = 0; 90 | 91 | if (!param->is_client && nb_slots_written == 0) { 92 | // Server mode: wait for a packet to arrive 93 | delta_t = 10000000; 94 | } 95 | 96 | if (param->is_client && nb_slots_written == 0) { 97 | const uint64_t current_time = picoquic_current_time(); 98 | const int64_t delay_max = param->delay_max == 0 ? 10000000 : param->delay_max; 99 | delta_t = picoquic_get_next_wake_delay(quic, current_time, delay_max); 100 | } 101 | 102 | struct sockaddr_storage peer_addr; 103 | struct sockaddr_storage local_addr; 104 | int if_index_to = 0; 105 | uint8_t received_ecn; 106 | uint8_t buffer[buffer_size]; 107 | int is_wake_up_event; 108 | int socket_rank = -1; 109 | int bytes_recv = picoquic_packet_loop_select(s_ctx, 1, &peer_addr, &local_addr, &if_index_to, &received_ecn, 110 | buffer, buffer_size, delta_t, &is_wake_up_event, thread_ctx, &socket_rank); 111 | if (bytes_recv < 0) { 112 | /* The interrupt error is expected if the loop is closing. */ 113 | return thread_ctx->thread_should_close ? PICOQUIC_NO_ERROR_TERMINATE_PACKET_LOOP : -1; 114 | } 115 | 116 | if (bytes_recv == 0 && is_wake_up_event) { 117 | const int ret = loop_callback(quic, picoquic_packet_loop_wake_up, loop_callback_ctx, NULL); 118 | if (ret < 0) { 119 | return ret; 120 | } 121 | } 122 | if (bytes_recv == 0) { 123 | break; 124 | } 125 | 126 | slot_t* slot = &slots[nb_slots_written]; 127 | assert(slot != NULL); 128 | memset(slot, 0, sizeof(slot_t)); 129 | slot->path_id = -1; 130 | nb_slots_written++; 131 | 132 | unsigned char* decoded = NULL; 133 | bytes_recv = param->decode(slot, thread_ctx->loop_callback_ctx, &decoded, 134 | (const unsigned char*)buffer, bytes_recv, &peer_addr, &local_addr); 135 | if (bytes_recv < 0) { 136 | DBG_PRINTF("decode() failed with error %d\n", bytes_recv); 137 | return bytes_recv; 138 | } 139 | 140 | if (bytes_recv == 0) { 141 | continue; 142 | } 143 | 144 | memcpy(buffer, decoded, bytes_recv); 145 | free(decoded); 146 | 147 | /* Submit the packet to the server */ 148 | uint8_t* received_buffer = buffer; 149 | uint64_t current_time = picoquic_current_time(); 150 | picoquic_cnx_t* last_cnx = NULL; 151 | int last_path_id = -1; 152 | int ret = picoquic_incoming_packet_ex(quic, received_buffer, 153 | (size_t)bytes_recv, (struct sockaddr*)&peer_addr, 154 | (struct sockaddr*)&local_addr, if_index_to, received_ecn, 155 | &last_cnx, &last_path_id, current_time); 156 | if (ret < 0) { 157 | return ret; 158 | } 159 | if (last_cnx == NULL) { 160 | DBG_PRINTF("last_cnx null in recv", NULL); 161 | continue; 162 | } 163 | slot->cnx = last_cnx; 164 | slot->path_id = last_path_id; 165 | nb_packet_received++; 166 | 167 | if (!param->is_client) { 168 | last_cnx->no_ack_delay = 1; 169 | } 170 | } 171 | 172 | const uint64_t loop_time = picoquic_current_time(); 173 | size_t nb_packets_sent = 0; 174 | size_t nb_slots_read = 0; 175 | const size_t max_slots = param->is_client ? PICOQUIC_PACKET_LOOP_SEND_MAX : nb_slots_written; 176 | while (nb_slots_read < max_slots) { 177 | uint8_t send_buffer[send_buffer_size]; 178 | slot_t* slot = &slots[nb_slots_read]; 179 | assert(slot != NULL); 180 | nb_slots_read++; 181 | 182 | size_t send_length = 0; 183 | struct sockaddr_storage peer_addr = {0}; 184 | struct sockaddr_storage local_addr = {0}; 185 | int if_index = param->dest_if; 186 | if (slot->error == RCODE_OKAY) { 187 | picoquic_connection_id_t log_cid; 188 | int ret; 189 | if (!param->is_client && slot->cnx) { 190 | ret = picoquic_prepare_packet_ex(slot->cnx, slot->path_id, loop_time, 191 | send_buffer, send_buffer_size, &send_length, 192 | &peer_addr, &local_addr, &if_index, send_msg_ptr); 193 | } 194 | else if (param->is_client) { 195 | ret = picoquic_prepare_next_packet_ex(quic, loop_time, 196 | send_buffer, send_buffer_size, &send_length, 197 | &peer_addr, &local_addr, &if_index, &log_cid, &slot->cnx, 198 | send_msg_ptr); 199 | } 200 | if (ret < 0) { 201 | return -1; 202 | } 203 | if (param->is_client && send_length == 0) { 204 | break; 205 | } 206 | } 207 | 208 | if (param->is_client && peer_addr.ss_family == 0 && slot->peer_addr.ss_family == 0) { 209 | continue; 210 | } 211 | 212 | int sock_err = 0; 213 | int bytes_sent; 214 | unsigned char* encoded = NULL; 215 | size_t segment_len = send_msg_size == 0 ? send_length : send_msg_size; 216 | ssize_t encoded_len = param->encode(slot, loop_callback_ctx, &encoded, 217 | (const unsigned char*)send_buffer, send_length, &segment_len, &peer_addr, &local_addr); 218 | if (encoded_len <= 0) { 219 | DBG_PRINTF("Encoding fails, ret=%d\n", encoded_len); 220 | continue; 221 | } 222 | 223 | if (encoded_len < segment_len) { 224 | DBG_PRINTF("Encoded len shorter than original: %d < %d", encoded_len, segment_len); 225 | return -1; 226 | } 227 | 228 | if (send_msg_size > 0) { 229 | send_msg_size = segment_len; // new size after encoding 230 | } 231 | 232 | const SOCKET_TYPE send_socket = s_ctx->fd; 233 | bytes_sent = picoquic_sendmsg(send_socket, 234 | (struct sockaddr*)&peer_addr, (struct sockaddr*)&local_addr, param->dest_if, 235 | (const char*)encoded, (int)encoded_len, (int)send_msg_size, &sock_err); 236 | free(encoded); 237 | if (bytes_sent == 0) { 238 | DBG_PRINTF("BYTES_SENT == 0 %d\n", bytes_sent); 239 | return -1; 240 | } 241 | if (bytes_sent < 0) { 242 | return bytes_sent; 243 | } 244 | 245 | nb_packets_sent++; 246 | } 247 | 248 | if (!param->is_client || nb_packet_received == 0) { 249 | continue; 250 | } 251 | 252 | size_t nb_polls_sent = 0; 253 | nb_slots_read = 0; 254 | while (nb_slots_read < nb_slots_written) { 255 | uint8_t send_buffer[send_buffer_size]; 256 | slot_t* slot = &slots[nb_slots_read]; 257 | assert(slot != NULL); 258 | nb_slots_read++; 259 | if (slot->cnx == NULL) { 260 | continue; // in case the slot written was a bogus message 261 | } 262 | 263 | slot->cnx->is_poll_requested = 1; 264 | 265 | size_t send_length = 0; 266 | struct sockaddr_storage peer_addr = {0}; 267 | struct sockaddr_storage local_addr = {0}; 268 | int if_index = param->dest_if; 269 | int ret = picoquic_prepare_packet_ex(slot->cnx, -1, loop_time, 270 | send_buffer, send_buffer_size, &send_length, 271 | &peer_addr, &local_addr, &if_index, send_msg_ptr); 272 | if (ret < 0) { 273 | return -1; 274 | } 275 | if (param->is_client && send_length == 0) { 276 | break; 277 | } 278 | if (slot->cnx->is_poll_requested == 1) { 279 | // TODO: should we try again or skip this slot 280 | continue; 281 | } 282 | 283 | int sock_err = 0; 284 | int bytes_sent; 285 | unsigned char* encoded = NULL; 286 | size_t segment_len = send_msg_size == 0 ? send_length : send_msg_size; 287 | ssize_t encoded_len = param->encode(slot, loop_callback_ctx, &encoded, 288 | (const unsigned char*)send_buffer, send_length, &segment_len, &peer_addr, &local_addr); 289 | if (encoded_len <= 0) { 290 | DBG_PRINTF("Encoding fails, ret=%d\n", encoded_len); 291 | continue; 292 | } 293 | 294 | if (encoded_len < segment_len) { 295 | DBG_PRINTF("Encoded len shorter than original: %d < %d", encoded_len, segment_len); 296 | return -1; 297 | } 298 | 299 | if (send_msg_size > 0) { 300 | send_msg_size = segment_len; // new size after encoding 301 | } 302 | 303 | const SOCKET_TYPE send_socket = s_ctx->fd; 304 | bytes_sent = picoquic_sendmsg(send_socket, 305 | (struct sockaddr*)&peer_addr, (struct sockaddr*)&local_addr, if_index, 306 | (const char*)encoded, (int)encoded_len, (int)send_msg_size, &sock_err); 307 | free(encoded); 308 | if (bytes_sent == 0) { 309 | DBG_PRINTF("BYTES_SENT == 0 %d\n", bytes_sent); 310 | return -1; 311 | } 312 | if (bytes_sent < 0) { 313 | return bytes_sent; 314 | } 315 | 316 | nb_polls_sent++; 317 | } 318 | 319 | // if (param->is_client) { 320 | // DBG_PRINTF("[polls_sent:%d][sent:%d][received:%d]", nb_polls_sent, nb_packets_sent, nb_packet_received); 321 | // } 322 | } 323 | 324 | return thread_ctx->return_code; 325 | } 326 | 327 | void* slipstream_packet_loop(picoquic_network_thread_ctx_t* thread_ctx) { 328 | const picoquic_packet_loop_param_t* param = thread_ctx->param; 329 | if (!param->do_not_use_gso && param->encode != NULL && !param->is_client) { 330 | DBG_FATAL_PRINTF("%s", "GSO disabled because encoding is enabled and server mode"); 331 | } 332 | 333 | picoquic_socket_ctx_t s_ctx = {0}; 334 | if (picoquic_packet_loop_open_sockets(param->local_port, 335 | param->local_af, param->socket_buffer_size, 336 | 0, param->do_not_use_gso, &s_ctx) <= 0) { 337 | thread_ctx->return_code = PICOQUIC_ERROR_UNEXPECTED_ERROR; 338 | return NULL; 339 | } 340 | 341 | thread_ctx->thread_is_ready = 1; 342 | thread_ctx->return_code = slipstream_packet_loop_(thread_ctx, &s_ctx); 343 | thread_ctx->thread_is_ready = 0; 344 | 345 | /* Close the sockets */ 346 | picoquic_packet_loop_close_socket(&s_ctx); 347 | 348 | if (thread_ctx->is_threaded) { 349 | pthread_exit((void*)&thread_ctx->return_code); 350 | } 351 | return (NULL); 352 | } 353 | -------------------------------------------------------------------------------- /docs/protocol.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Protocol Design 3 | nav_order: 40 4 | --- 5 | 6 | ## Protocol Design 7 | 8 | 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. 9 | A major aspect of slipstream is the use of QUIC[^QUIC] multipath[^QUICMP] as the transport protocol. 10 | 11 | [^QUIC]: [QUIC: A UDP-Based Multiplexed and Secure Transport (RFC 9000)](https://datatracker.ietf.org/doc/html/rfc9000) 12 | [^QUICMP]: [Multipath Extension for QUIC (draft-ietf-quic-multipath-14)](https://datatracker.ietf.org/doc/draft-ietf-quic-multipath/14/) 13 | 14 | ### Protocol Stack 15 | 16 | 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. 17 | 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. 18 | slipstream itself does not implement any reliability based on DNS message status codes, keeping reliability management completely within QUIC. 19 | A DNS message with a failure response code does not necessarily mean that the message was not delivered to the DNS tunneling server. 20 | 21 | [^TurboTunnel]: [David Fifield. Turbo Tunnel, a good way to design censorship circumvention protocols. FOCI 2020.](https://www.usenix.org/conference/foci20/presentation/fifield) 22 | 23 |

24 | 25 | 26 | Shows a two stacks of protocol names. Dnstt shows KCP, SMUX, and Noise, and TurboTunnel. Slipstream shows QUIC and slipstream. 27 | 28 |

29 | 30 |

31 | Protocol stack comparison between slipstream and dnstt. 32 |

33 | 34 | QUIC implementations are abundant, allowing us to pick and choose an implementation that fits the pull interface proposed in TurboTunnel. 35 | The main selection criteria for a QUIC implementation is support for a pull interface and the multipath QUIC extension. 36 | The latter deemed especially challenging as this RFC is going through many changes in each version of the proposal. 37 | 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). 38 | 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. 39 | 40 | ### Reduced Header Overhead 41 | 42 | 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). 43 | QUIC uses variable-length integers to encode certain fields, allowing for more efficient encoding of small numbers. 44 | 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. 45 | 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. 46 | slipstream does not add any additional headers or fields on top of QUIC. 47 | 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). 48 | For dnstt's random padding cache-breaking solution, we also rely on QUIC to use unique packet IDs (see QUIC[^QUIC] section 12.3.). 49 | 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. 50 | 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. 51 | 52 |

53 | 54 | 55 | 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. 56 | 57 |

58 | 59 |

60 | Comparison of slipstream and dnstt packet formats. 61 |

62 | 63 | Another consideration is that QUIC expects the `Initial` packets to have a minimum size of 1200 bytes to prevent amplification attacks. 64 | 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. 65 | Of course, we could still integrate response rate-limiting on unauthenticated client IDs. 66 | 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. 67 | 68 | ### Congestion Control 69 | 70 | Achieving high performance in DNS tunneling requires careful management of query rates to maximize throughput while avoiding rate limits imposed by DNS resolvers. 71 | In addition, with larger RTTs, DNS tunnels suffer from low performance[^nussbaumRobustCovertChannels2009], showing the importance of a good congestion control algorithm. 72 | 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. 73 | While QUIC supports congestion control algorithms, we need to make several changes: 74 | 75 | * 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). 76 | 77 | * 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. 78 | 79 | * 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. 80 | 81 | * 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]. 82 | 83 | [^nussbaumRobustCovertChannels2009]: Lucas Nussbaum, Pierre Neyron and Olivier Richard. On Robust Covert Channels Inside DNS. 24th IFIP International Security Conference. 2009. 84 | [^RACK]: [The RACK-TLP Loss Detection Algorithm for TCP (RFC 8985)](https://datatracker.ietf.org/doc/html/rfc8985) 85 | 86 | ### Connection IDs and Multipath 87 | 88 | QUIC relies on the connection ID to track individual connections, rather than a source address. 89 | 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. 90 | In the case of DNS tunneling, each path corresponds to a different DNS resolver, each with its own network conditions and rate limit. 91 | 92 | Although QUIC primarily uses a connection ID for tracking individual connections, it relies on source addresses for path management (i.e. migration). 93 | 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. 94 | As a result, several QUIC features should be manually disabled. 95 | 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. 96 | 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). 97 | 98 | ### Polling 99 | 100 | The polling mechanism in slipstream builds upon dnstt's strategy of replying to every server reply with a poll message. 101 | 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. 102 | The poll frame is a frame with size 0, such that the only space used is the frame type. 103 | 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. 104 | If possible, we replace an outgoing poll message with a real frame that contains data. 105 | This ensures that when the server is sending data, the poll messages won't take precedence over the client's outgoing data frames. 106 | 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. 107 | 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. 108 | 109 | ### Encryption 110 | 111 | QUIC provides built-in encryption, mutual authentication, and integrity checks. 112 | This means that we do not need to implement a custom solution, reducing the complexity of the tunneling protocol. 113 | 114 | ### Timings 115 | 116 | DNS resolvers may keep track of round-trip-times of authoritative nameservers and drop or retransmit packets for responses that arrive too late. 117 | For example, Unbound DNS resolver keeps track of ping, variance, and RTT for its timeout management[^UnboundTimeoutManagement]. 118 | 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. 119 | 120 | [^UnboundTimeoutManagement]: [Unbound Timeout Information](https://www.nlnetlabs.nl/documentation/unbound/info-timeout/) 121 | 122 | ### Performant Encoding 123 | 124 | In high performance tunneling where the bottleneck becomes the tunnel performance, the speed of encoding and decoding is crucial. 125 | There is a trade-off between the encoding and decoding speed and the features of the DNS encoding library. 126 | 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. 127 | 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. 128 | We settled on the [SPCDNS](https://github.com/spc476/SPCDNS) library, which keeps the processing time of encoding and decoding to a minimum. 129 | Although the library is very bare-bones, it is quite sufficient for our use-case. 130 | 131 | Similarly, the performance of base32 encoding is crucial. 132 | 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. 133 | 134 | ### Socket loop 135 | 136 | For tunneling purposes, we need to poll on 2 sockets: one for the TCP stream and one for the DNS messages. 137 | When DNS messages arrive, we decode the message and pass it to the QUIC library using `picoquic_incoming_packet_ex`. 138 | To improve the performance, we allow multiple packets to be received before attempting to respond. 139 | For sending retransmissions, acknowledgments, or new data on the TCP stream, we poll on the QUIC library using `picoquic_prepare_next_packet_ex`. 140 | 141 | In the server side, we buffer the multiple incoming DNS messages in a FIFO queue, such that we can respond to them in order. 142 | 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. 143 | 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. 144 | 145 | 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. 146 | 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. 147 | 148 |

149 | 150 | 151 | 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. 152 | 153 |

154 | 155 |

156 | The socket loop's data flow. 157 |

158 | -------------------------------------------------------------------------------- /src/slipstream_server.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #ifdef BUILD_LOGLIB 7 | #include 8 | #endif 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | #include "lua-resty-base-encoding-base32.h" 20 | #include "picoquic_config.h" 21 | #include "picoquic_logger.h" 22 | #include "slipstream.h" 23 | #include "slipstream_inline_dots.h" 24 | #include "../include/slipstream_server_cc.h" 25 | #include "slipstream_slot.h" 26 | #include "slipstream_utils.h" 27 | #include "SPCDNS/src/dns.h" 28 | #include "SPCDNS/src/mappings.h" 29 | 30 | volatile sig_atomic_t should_shutdown = 0; 31 | 32 | void server_sighandler(int signum) { 33 | DBG_PRINTF("Signal %d received", signum); 34 | should_shutdown = 1; 35 | } 36 | 37 | char* server_domain_name = NULL; 38 | size_t server_domain_name_len = 0; 39 | 40 | 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) { 41 | *dest_buf = NULL; 42 | 43 | // we don't support segmentation in the server 44 | assert(segment_len == NULL || *segment_len == 0 || *segment_len == src_buf_len); 45 | 46 | slot_t* slot = (slot_t*) slot_p; 47 | 48 | #ifdef NOENCODE 49 | *dest_buf = malloc(src_buf_len); 50 | memcpy((void*)*dest_buf, src_buf, src_buf_len); 51 | 52 | memcpy(peer_addr, &slot->peer_addr, sizeof(struct sockaddr_storage)); 53 | memcpy(local_addr, &slot->local_addr, sizeof(struct sockaddr_storage)); 54 | 55 | return src_buf_len; 56 | #endif 57 | 58 | dns_query_t *query = (dns_query_t *) slot->dns_decoded; 59 | dns_txt_t answer_txt; // TODO: fix 60 | dns_answer_t edns = {0}; 61 | edns.opt.name = "."; 62 | edns.opt.type = RR_OPT; 63 | edns.opt.class = CLASS_UNKNOWN; 64 | edns.opt.ttl = 0; 65 | edns.opt.udp_payload = 1232; 66 | 67 | dns_query_t response = {0}; 68 | response.id = query->id; 69 | response.query = false; 70 | response.opcode = OP_QUERY; 71 | response.aa = true; 72 | response.rd = query->rd; 73 | response.cd = query->cd; 74 | response.rcode = slot->error; 75 | response.qdcount = 1; 76 | response.questions = query->questions; 77 | 78 | if (src_buf_len > 0) { 79 | const dns_question_t *question = &query->questions[0]; // assuming server_decode ensures there is exactly one question 80 | answer_txt.name = question->name; 81 | answer_txt.type = question->type; 82 | answer_txt.class = question->class; 83 | answer_txt.ttl = 60; 84 | answer_txt.text = (char *)src_buf; 85 | answer_txt.len = src_buf_len; 86 | 87 | response.ancount = 1; 88 | response.answers = (dns_answer_t *)&answer_txt; 89 | } else { 90 | if (slot->error == RCODE_OKAY) { 91 | response.rcode = RCODE_NAME_ERROR; 92 | } 93 | } 94 | 95 | response.arcount = 1; 96 | response.additional = &edns; 97 | 98 | dns_packet_t* packet = malloc(MAX_UDP_PACKET_SIZE); 99 | size_t packet_len = MAX_UDP_PACKET_SIZE; 100 | dns_rcode_t rc = dns_encode(packet, &packet_len, &response); 101 | if (rc != RCODE_OKAY) { 102 | free(packet); 103 | DBG_PRINTF("dns_encode() = (%d) %s", rc, dns_rcode_text(rc)); 104 | return EXIT_FAILURE; 105 | } 106 | *dest_buf = (unsigned char*)packet; 107 | 108 | memcpy(peer_addr, &slot->peer_addr, sizeof(struct sockaddr_storage)); 109 | memcpy(local_addr, &slot->local_addr, sizeof(struct sockaddr_storage)); 110 | 111 | return packet_len; 112 | } 113 | 114 | ssize_t server_decode(void* slot_p, void* callback_ctx, unsigned char** dest_buf, const unsigned char* src_buf, size_t src_buf_len, struct sockaddr_storage *peer_addr, struct sockaddr_storage *local_addr) { 115 | *dest_buf = NULL; 116 | 117 | slot_t* slot = slot_p; 118 | 119 | // DNS packets arrive from random source ports, so: 120 | // * save the original address in the dns query slot 121 | // * set the source address to a dummy address (to prevent QUIC from using it) 122 | memcpy(&slot->peer_addr, peer_addr, sizeof(struct sockaddr_storage)); 123 | sockaddr_dummy(peer_addr); 124 | // Save local address for right response local addr 125 | memcpy(&slot->local_addr, local_addr, sizeof(struct sockaddr_storage)); 126 | 127 | #ifdef NODECODE 128 | *dest_buf = malloc(src_buf_len); 129 | memcpy((void*)*dest_buf, src_buf, src_buf_len); 130 | 131 | return src_buf_len; 132 | #endif 133 | 134 | size_t packet_len = DNS_DECODEBUF_4K * sizeof(dns_decoded_t); 135 | dns_decoded_t* packet = slot->dns_decoded; 136 | const dns_rcode_t rc = dns_decode(packet, &packet_len, (const dns_packet_t*) src_buf, src_buf_len); 137 | if (rc != RCODE_OKAY) { 138 | DBG_PRINTF("dns_decode() = (%d) %s", rc, dns_rcode_text(rc)); 139 | // TODO: how to get rid of this packet 140 | return -1; // TODO: server failure 141 | } 142 | 143 | const dns_query_t *query = (dns_query_t*) packet; 144 | if (!query->query) { 145 | DBG_PRINTF("dns record is not a query", NULL); 146 | slot->error = RCODE_FORMAT_ERROR; 147 | return 0; 148 | } 149 | 150 | if (query->qdcount != 1) { 151 | DBG_PRINTF("dns record should contain exactly one query", NULL); 152 | slot->error = RCODE_FORMAT_ERROR; 153 | return 0; 154 | } 155 | 156 | const dns_question_t *question = &query->questions[0]; 157 | if (question->type != RR_TXT) { 158 | // resolvers send anything for pinging, so we only respond to TXT queries 159 | // DBG_PRINTF("query type is not TXT", NULL); 160 | slot->error = RCODE_NAME_ERROR; 161 | return 0; 162 | } 163 | 164 | const ssize_t data_len = strlen(question->name) - server_domain_name_len - 1 - 1; 165 | if (data_len <= 0) { 166 | DBG_PRINTF("subdomain is empty", NULL); 167 | slot->error = RCODE_NAME_ERROR; 168 | return 0; 169 | } 170 | 171 | // copy the subdomain from name to a new buffer 172 | char data_buf[data_len]; 173 | memcpy(data_buf, question->name, data_len); 174 | data_buf[data_len] = '\0'; 175 | const size_t encoded_len = slipstream_inline_undotify(data_buf, data_len); 176 | 177 | char* decoded_buf = malloc(encoded_len); 178 | const size_t decoded_len = b32_decode(decoded_buf, data_buf, encoded_len, false); 179 | if (decoded_len == (size_t) -1) { 180 | free(decoded_buf); 181 | DBG_PRINTF("error decoding base32: %lu", decoded_len); 182 | slot->error = RCODE_SERVER_FAILURE; 183 | return 0; 184 | } 185 | 186 | *dest_buf = decoded_buf; 187 | 188 | return decoded_len; 189 | } 190 | 191 | typedef struct st_slipstream_server_stream_ctx_t { 192 | struct st_slipstream_server_stream_ctx_t* next_stream; 193 | struct st_slipstream_server_stream_ctx_t* previous_stream; 194 | int fd; 195 | int pipefd[2]; 196 | uint64_t stream_id; 197 | volatile sig_atomic_t set_active; 198 | } slipstream_server_stream_ctx_t; 199 | 200 | typedef struct st_slipstream_server_ctx_t { 201 | picoquic_cnx_t* cnx; 202 | slipstream_server_stream_ctx_t* first_stream; 203 | picoquic_network_thread_ctx_t* thread_ctx; 204 | struct sockaddr_storage upstream_addr; 205 | struct st_slipstream_server_ctx_t* prev_ctx; 206 | struct st_slipstream_server_ctx_t* next_ctx; 207 | } slipstream_server_ctx_t; 208 | 209 | slipstream_server_stream_ctx_t* slipstream_server_create_stream_ctx(slipstream_server_ctx_t* server_ctx, 210 | uint64_t stream_id) { 211 | slipstream_server_stream_ctx_t* stream_ctx = malloc(sizeof(slipstream_server_stream_ctx_t)); 212 | 213 | if (stream_ctx == NULL) { 214 | DBG_PRINTF("Memory Error, cannot create stream", NULL); 215 | return NULL; 216 | } 217 | 218 | memset(stream_ctx, 0, sizeof(slipstream_server_stream_ctx_t)); 219 | stream_ctx->stream_id = stream_id; 220 | 221 | if (pipe(stream_ctx->pipefd) < 0) { 222 | perror("pipe() failed"); 223 | free(stream_ctx); 224 | return NULL; 225 | } 226 | 227 | int sock_fd = socket(AF_INET, SOCK_STREAM, 0); 228 | if (sock_fd < 0) { 229 | perror("socket() failed"); 230 | close(stream_ctx->pipefd[0]); 231 | close(stream_ctx->pipefd[1]); 232 | free(stream_ctx); 233 | return NULL; 234 | } 235 | stream_ctx->fd = sock_fd; 236 | 237 | if (server_ctx->first_stream == NULL) { 238 | server_ctx->first_stream = stream_ctx; 239 | } else { 240 | stream_ctx->next_stream = server_ctx->first_stream; 241 | stream_ctx->next_stream->previous_stream = stream_ctx; 242 | server_ctx->first_stream = stream_ctx; 243 | } 244 | 245 | return stream_ctx; 246 | } 247 | 248 | static void slipstream_server_free_stream_context(slipstream_server_ctx_t* server_ctx, 249 | slipstream_server_stream_ctx_t* stream_ctx) { 250 | if (stream_ctx->previous_stream != NULL) { 251 | stream_ctx->previous_stream->next_stream = stream_ctx->next_stream; 252 | } 253 | if (stream_ctx->next_stream != NULL) { 254 | stream_ctx->next_stream->previous_stream = stream_ctx->previous_stream; 255 | } 256 | if (server_ctx->first_stream == stream_ctx) { 257 | server_ctx->first_stream = stream_ctx->next_stream; 258 | } 259 | 260 | stream_ctx->fd = close(stream_ctx->fd); 261 | 262 | free(stream_ctx); 263 | } 264 | 265 | static void slipstream_server_free_context(slipstream_server_ctx_t* server_ctx) { 266 | slipstream_server_stream_ctx_t* stream_ctx; 267 | 268 | /* Delete any remaining stream context */ 269 | while ((stream_ctx = server_ctx->first_stream) != NULL) { 270 | slipstream_server_free_stream_context(server_ctx, stream_ctx); 271 | } 272 | 273 | if (server_ctx->prev_ctx) { 274 | server_ctx->prev_ctx->next_ctx = server_ctx->next_ctx; 275 | } 276 | 277 | if (server_ctx->next_ctx) { 278 | server_ctx->next_ctx->prev_ctx = server_ctx->prev_ctx; 279 | } 280 | 281 | /* release the memory */ 282 | free(server_ctx); 283 | } 284 | 285 | void slipstream_server_mark_active_pass(slipstream_server_ctx_t* server_ctx) { 286 | slipstream_server_stream_ctx_t* stream_ctx = server_ctx->first_stream; 287 | 288 | while (stream_ctx != NULL) { 289 | if (stream_ctx->set_active) { 290 | stream_ctx->set_active = 0; 291 | DBG_PRINTF("[stream_id=%d][fd=%d] activate: stream", stream_ctx->stream_id, stream_ctx->fd); 292 | picoquic_mark_active_stream(server_ctx->cnx, stream_ctx->stream_id, 1, stream_ctx); 293 | } 294 | stream_ctx = stream_ctx->next_stream; 295 | } 296 | } 297 | 298 | int slipstream_server_sockloop_callback(picoquic_quic_t* quic, picoquic_packet_loop_cb_enum cb_mode, 299 | void* callback_ctx, void* callback_arg) { 300 | slipstream_server_ctx_t* default_ctx = callback_ctx; 301 | 302 | switch (cb_mode) { 303 | case picoquic_packet_loop_wake_up: 304 | if (callback_ctx == NULL) { 305 | return 0; 306 | } 307 | 308 | /* skip default ctx */ 309 | slipstream_server_ctx_t* server_ctx = default_ctx->next_ctx; 310 | while (server_ctx != NULL) { 311 | slipstream_server_mark_active_pass(server_ctx); 312 | server_ctx = server_ctx->next_ctx; 313 | } 314 | 315 | break; 316 | case picoquic_packet_loop_before_select: 317 | if (should_shutdown) { 318 | // Iterate and close all connections 319 | picoquic_cnx_t* cnx = picoquic_get_first_cnx(quic); 320 | bool has_unclosed = false; 321 | while (cnx != NULL) { 322 | if (cnx->cnx_state != picoquic_state_disconnected) { 323 | has_unclosed = true; 324 | } 325 | 326 | picoquic_close(cnx, 0); // 0 = no error, or use appropriate error code 327 | 328 | if (cnx->cnx_state == picoquic_state_draining) { 329 | picoquic_connection_disconnect(cnx); 330 | } 331 | 332 | cnx = picoquic_get_next_cnx(cnx); 333 | } 334 | 335 | if (!has_unclosed) { 336 | DBG_PRINTF("All connections closed, shutting down.", NULL); 337 | return -1; 338 | } 339 | } 340 | default: 341 | break; 342 | } 343 | 344 | return 0; 345 | } 346 | 347 | typedef struct st_slipstream_server_poller_args { 348 | int fd; 349 | picoquic_cnx_t* cnx; 350 | slipstream_server_ctx_t* server_ctx; 351 | slipstream_server_stream_ctx_t* stream_ctx; 352 | } slipstream_server_poller_args; 353 | 354 | void* slipstream_server_poller(void* arg) { 355 | slipstream_server_poller_args* args = arg; 356 | 357 | while (1) { 358 | struct pollfd fds; 359 | fds.fd = args->fd; 360 | fds.events = POLLIN; 361 | fds.revents = 0; 362 | 363 | /* add timeout handlilng */ 364 | int ret = poll(&fds, 1, 1000); 365 | if (ret < 0) { 366 | perror("poll() failed"); 367 | break; 368 | } 369 | if (ret == 0) { 370 | continue; 371 | } 372 | 373 | args->stream_ctx->set_active = 1; 374 | 375 | ret = picoquic_wake_up_network_thread(args->server_ctx->thread_ctx); 376 | if (ret != 0) { 377 | DBG_PRINTF("poll: could not wake up network thread, ret = %d", ret); 378 | } 379 | DBG_PRINTF("[stream_id=%d][fd=%d] wakeup", args->stream_ctx->stream_id, args->fd); 380 | 381 | break; 382 | } 383 | 384 | 385 | free(args); 386 | pthread_exit(NULL); 387 | } 388 | 389 | typedef struct st_slipstream_io_copy_args { 390 | int pipe; 391 | int socket; 392 | picoquic_cnx_t* cnx; 393 | slipstream_server_ctx_t* server_ctx; 394 | slipstream_server_stream_ctx_t* stream_ctx; 395 | } slipstream_io_copy_args; 396 | 397 | void* slipstream_io_copy(void* arg) { 398 | char buffer[1024]; 399 | slipstream_io_copy_args* args = arg; 400 | int pipe = args->pipe; 401 | int socket = args->socket; 402 | slipstream_server_ctx_t* server_ctx = args->server_ctx; 403 | slipstream_server_stream_ctx_t* stream_ctx = args->stream_ctx; 404 | 405 | if (connect(socket, (struct sockaddr*)&server_ctx->upstream_addr, sizeof(server_ctx->upstream_addr)) < 0) { 406 | perror("connect() failed"); 407 | return NULL; 408 | } 409 | 410 | DBG_PRINTF("[%lu:%d] setup pipe done", stream_ctx->stream_id, stream_ctx->fd); 411 | 412 | stream_ctx->set_active = 1; 413 | 414 | args->stream_ctx->set_active = 1; 415 | 416 | int ret = picoquic_wake_up_network_thread(args->server_ctx->thread_ctx); 417 | if (ret != 0) { 418 | DBG_PRINTF("poll: could not wake up network thread, ret = %d", ret); 419 | } 420 | DBG_PRINTF("[stream_id=%d][fd=%d] wakeup", args->stream_ctx->stream_id, args->socket); 421 | 422 | while (1) { 423 | ssize_t bytes_read = read(pipe, buffer, sizeof(buffer)); 424 | 425 | DBG_PRINTF("[%lu:%d] read %d bytes", stream_ctx->stream_id, stream_ctx->fd, bytes_read); 426 | if (bytes_read < 0) { 427 | perror("recv failed"); 428 | return NULL; 429 | } else if (bytes_read == 0) { 430 | // End of stream - source socket closed connection 431 | break; 432 | } 433 | 434 | char *p = buffer; 435 | ssize_t remaining = bytes_read; 436 | 437 | while (remaining > 0) { 438 | ssize_t bytes_written = send(socket, p, remaining, 0); 439 | if (bytes_written < 0) { 440 | perror("send failed"); 441 | return NULL; 442 | } 443 | remaining -= bytes_written; 444 | p += bytes_written; 445 | } 446 | } 447 | 448 | return NULL; 449 | } 450 | 451 | 452 | int slipstream_server_callback(picoquic_cnx_t* cnx, 453 | uint64_t stream_id, uint8_t* bytes, size_t length, 454 | picoquic_call_back_event_t fin_or_event, void* callback_ctx, void* v_stream_ctx) { 455 | int ret = 0; 456 | slipstream_server_ctx_t* server_ctx = (slipstream_server_ctx_t*)callback_ctx; 457 | slipstream_server_stream_ctx_t* stream_ctx = (slipstream_server_stream_ctx_t*)v_stream_ctx; 458 | 459 | /* If this is the first reference to the connection, the application context is set 460 | * to the default value defined for the server. This default value contains the pointer 461 | * to the file directory in which all files are defined. 462 | */ 463 | if (callback_ctx == NULL || callback_ctx == picoquic_get_default_callback_context(picoquic_get_quic_ctx(cnx))) { 464 | server_ctx = (slipstream_server_ctx_t*)malloc(sizeof(slipstream_server_ctx_t)); 465 | if (server_ctx == NULL) { 466 | /* cannot handle the connection */ 467 | picoquic_close(cnx, PICOQUIC_ERROR_MEMORY); 468 | return -1; 469 | } 470 | slipstream_server_ctx_t* d_ctx = picoquic_get_default_callback_context(picoquic_get_quic_ctx(cnx)); 471 | if (d_ctx != NULL) { 472 | memcpy(server_ctx, d_ctx, sizeof(slipstream_server_ctx_t)); 473 | } 474 | else { 475 | /* This really is an error case: the default connection context should never be NULL */ 476 | memset(server_ctx, 0, sizeof(slipstream_server_ctx_t)); 477 | } 478 | server_ctx->cnx = cnx; 479 | picoquic_set_callback(cnx, slipstream_server_callback, server_ctx); 480 | 481 | if (d_ctx->next_ctx != NULL) { 482 | d_ctx->next_ctx->prev_ctx = server_ctx; 483 | } 484 | server_ctx->next_ctx = d_ctx->next_ctx; 485 | server_ctx->prev_ctx = d_ctx; 486 | d_ctx->next_ctx = server_ctx; 487 | 488 | DBG_PRINTF("Created ctx", NULL); 489 | } 490 | 491 | switch (fin_or_event) { 492 | case picoquic_callback_stream_data: 493 | case picoquic_callback_stream_fin: 494 | /* Data arrival on stream #x, maybe with fin mark */ 495 | if (stream_ctx == NULL) { 496 | /* Create and initialize stream context */ 497 | stream_ctx = slipstream_server_create_stream_ctx(server_ctx, stream_id); 498 | if (stream_ctx == NULL || picoquic_set_app_stream_ctx(cnx, stream_id, stream_ctx) != 0) { 499 | /* Internal error */ 500 | (void)picoquic_reset_stream(cnx, stream_id, SLIPSTREAM_INTERNAL_ERROR); 501 | return 0; 502 | } 503 | DBG_PRINTF("[%lu:%d] setup pipe", stream_id, stream_ctx->pipefd[1]); 504 | 505 | slipstream_io_copy_args* args = malloc(sizeof(slipstream_io_copy_args)); 506 | args->pipe = stream_ctx->pipefd[0]; 507 | args->socket = stream_ctx->fd; 508 | args->cnx = cnx; 509 | args->server_ctx = server_ctx; 510 | args->stream_ctx = stream_ctx; 511 | 512 | pthread_t thread; 513 | if (pthread_create(&thread, NULL, slipstream_io_copy, args) != 0) { 514 | perror("pthread_create() failed for thread1"); 515 | free(args); 516 | } 517 | #ifdef __APPLE__ 518 | pthread_setname_np("slipstream_io_copy"); 519 | #else 520 | pthread_setname_np(thread, "slipstream_io_copy"); 521 | #endif 522 | pthread_detach(thread); 523 | 524 | } 525 | 526 | // DBG_PRINTF("[stream_id=%d] quic_recv->send %lu bytes", stream_id, length); 527 | 528 | if (length > 0) { 529 | DBG_PRINTF("[stream_id=%d][leftover_length=%d]", stream_ctx->stream_id, length); 530 | 531 | ssize_t bytes_sent = write(stream_ctx->pipefd[1], bytes, length); 532 | DBG_PRINTF("[stream_id=%d][bytes_sent=%d]", stream_ctx->stream_id, bytes_sent); 533 | if (bytes_sent < 0) { 534 | if (errno == EPIPE) { 535 | /* Connection closed */ 536 | DBG_PRINTF("[stream_id=%d] send: closed stream", stream_ctx->stream_id); 537 | 538 | (void)picoquic_reset_stream(cnx, stream_id, SLIPSTREAM_FILE_CANCEL_ERROR); 539 | return 0; 540 | } 541 | if (errno == EAGAIN) { 542 | /* TODO: this is bad because we don't have a way to backpressure */ 543 | } 544 | 545 | DBG_PRINTF("[stream_id=%d] send: error: %s (%d)", stream_id, strerror(errno), errno); 546 | (void)picoquic_reset_stream(cnx, stream_id, SLIPSTREAM_INTERNAL_ERROR); 547 | return 0; 548 | } 549 | } 550 | if (fin_or_event == picoquic_callback_stream_fin) { 551 | DBG_PRINTF("[stream_id=%d] fin", stream_ctx->stream_id); 552 | /* Close the local_sock fd */ 553 | close(stream_ctx->fd); 554 | stream_ctx->fd = -1; 555 | picoquic_unlink_app_stream_ctx(cnx, stream_id); 556 | } 557 | break; 558 | case picoquic_callback_stop_sending: /* Should not happen, treated as reset */ 559 | /* Mark stream as abandoned, close the file, etc. */ 560 | picoquic_reset_stream(cnx, stream_id, 0); 561 | /* Fall through */ 562 | case picoquic_callback_stream_reset: /* Server reset stream #x */ 563 | if (stream_ctx == NULL) { 564 | /* This is unexpected, as all contexts were declared when initializing the 565 | * connection. */ 566 | } 567 | else { 568 | DBG_PRINTF("[stream_id=%d] stream reset", stream_ctx->stream_id); 569 | 570 | slipstream_server_free_stream_context(server_ctx, stream_ctx); 571 | picoquic_reset_stream(cnx, stream_id, SLIPSTREAM_FILE_CANCEL_ERROR); 572 | } 573 | break; 574 | case picoquic_callback_stateless_reset: 575 | case picoquic_callback_close: /* Received connection close */ 576 | case picoquic_callback_application_close: /* Received application close */ 577 | DBG_PRINTF("Connection closed.", NULL); 578 | if (server_ctx != NULL) { 579 | slipstream_server_free_context(server_ctx); 580 | } 581 | /* Remove the application callback */ 582 | picoquic_set_callback(cnx, NULL, NULL); 583 | picoquic_close(cnx, 0); 584 | picoquic_wake_up_network_thread(server_ctx->thread_ctx); 585 | break; 586 | case picoquic_callback_prepare_to_send: 587 | /* Active sending API */ 588 | if (stream_ctx == NULL) { 589 | /* This should never happen */ 590 | } 591 | else { 592 | int length_available; 593 | ret = ioctl(stream_ctx->fd, FIONREAD, &length_available); 594 | // DBG_PRINTF("[stream_id=%d] recv->quic_send (available %d)", stream_id, length_available); 595 | if (ret < 0) { 596 | DBG_PRINTF("[stream_id=%d] ioctl error: %s (%d)", stream_ctx->stream_id, strerror(errno), errno); 597 | /* TODO: why would it return an error? */ 598 | (void)picoquic_reset_stream(cnx, stream_id, SLIPSTREAM_INTERNAL_ERROR); 599 | break; 600 | } 601 | ret = 0; 602 | 603 | int length_to_read = MIN(length, length_available); 604 | if (length_to_read == 0) { 605 | char a; 606 | ssize_t bytes_read = recv(stream_ctx->fd, &a, 1, MSG_PEEK | MSG_DONTWAIT); 607 | // DBG_PRINTF("[%lu:%d] recv->quic_send empty read %d bytes\n", stream_id, stream_ctx->fd, bytes_read); 608 | if (errno == EAGAIN || errno == EWOULDBLOCK) { 609 | // DBG_PRINTF("[%lu:%d] recv->quic_send empty errno set: %s\n", stream_id, stream_ctx->fd, strerror(errno)); 610 | /* No bytes available, wait for next event */ 611 | (void)picoquic_provide_stream_data_buffer(bytes, 0, 0, 0); 612 | DBG_PRINTF("[stream_id=%d] recv->quic_send: empty, disactivate", stream_ctx->stream_id); 613 | 614 | slipstream_server_poller_args* args = malloc(sizeof(slipstream_server_poller_args)); 615 | args->fd = stream_ctx->fd; 616 | args->cnx = cnx; 617 | args->server_ctx = server_ctx; 618 | args->stream_ctx = stream_ctx; 619 | 620 | pthread_t thread; 621 | if (pthread_create(&thread, NULL, slipstream_server_poller, args) != 0) { 622 | perror("pthread_create() failed for thread1"); 623 | free(args); 624 | } 625 | #ifdef __APPLE__ 626 | pthread_setname_np("slipstream_server_poller"); 627 | #else 628 | pthread_setname_np(thread, "slipstream_server_poller"); 629 | #endif 630 | pthread_detach(thread); 631 | } 632 | if (bytes_read == 0) { 633 | DBG_PRINTF("[stream_id=%d] recv: closed stream", stream_ctx->stream_id); 634 | (void)picoquic_reset_stream(cnx, stream_id, SLIPSTREAM_FILE_CANCEL_ERROR); 635 | return 0; 636 | } 637 | if (bytes_read > 0) { 638 | /* send it in next loop iteration */ 639 | (void)picoquic_provide_stream_data_buffer(bytes, 0, 0, 1); 640 | break; 641 | } 642 | return 0; 643 | } 644 | 645 | uint8_t* buffer = picoquic_provide_stream_data_buffer(bytes, length_to_read, 0, 1); 646 | if (buffer == NULL) { 647 | /* Should never happen according to callback spec. */ 648 | break; 649 | } 650 | // DBG_PRINTF("[%lu:%d] recv->quic_send recv %d bytes into quic\n", stream_id, stream_ctx->fd, length_to_read); 651 | ssize_t bytes_read = recv(stream_ctx->fd, buffer, length_to_read, MSG_DONTWAIT); 652 | // DBG_PRINTF("[%lu:%d] recv->quic_send recv done %d bytes into quic\n", stream_id, stream_ctx->fd, bytes_read); 653 | if (bytes_read == 0) { 654 | DBG_PRINTF("Closed connection on sock %d on recv", stream_ctx->fd); 655 | (void)picoquic_reset_stream(cnx, stream_id, SLIPSTREAM_FILE_CANCEL_ERROR); 656 | return 0; 657 | } 658 | if (bytes_read < 0) { 659 | DBG_PRINTF("recv: %s (%d)", strerror(errno), errno); 660 | /* There should be bytes available, so a return value of 0 is an error */ 661 | (void)picoquic_reset_stream(cnx, stream_id, SLIPSTREAM_INTERNAL_ERROR); 662 | return 0; 663 | } 664 | } 665 | break; 666 | case picoquic_callback_almost_ready: 667 | DBG_PRINTF("Connection completed, almost ready", NULL); 668 | break; 669 | case picoquic_callback_ready: 670 | DBG_PRINTF("Connection confirmed", NULL); 671 | break; 672 | default: 673 | /* unexpected -- just ignore. */ 674 | break; 675 | } 676 | 677 | return ret; 678 | } 679 | 680 | int picoquic_slipstream_server(int server_port, const char* server_cert, const char* server_key, 681 | struct sockaddr_storage* target_address, const char* domain_name) { 682 | /* Start: start the QUIC process with cert and key files */ 683 | int ret = 0; 684 | uint64_t current_time = 0; 685 | slipstream_server_ctx_t default_context = {0}; 686 | 687 | // Store the target address directly - no need to resolve it here anymore 688 | memcpy(&default_context.upstream_addr, target_address, sizeof(struct sockaddr_storage)); 689 | 690 | server_domain_name = strdup(domain_name); 691 | server_domain_name_len = strlen(domain_name); 692 | 693 | // int mtu = 250; 694 | int mtu = 900; 695 | 696 | /* Create config */ 697 | picoquic_quic_config_t config; 698 | picoquic_config_init(&config); 699 | config.nb_connections = 8; 700 | config.server_cert_file = server_cert; 701 | config.server_key_file = server_key; 702 | // config.log_file = "-"; 703 | #ifdef BUILD_LOGLIB 704 | config.qlog_dir = SLIPSTREAM_QLOG_DIR; 705 | #endif 706 | config.server_port = server_port; 707 | config.mtu_max = mtu; 708 | config.initial_send_mtu_ipv4 = mtu; 709 | config.initial_send_mtu_ipv6 = mtu; 710 | config.multipath_option = 1; 711 | config.use_long_log = 1; 712 | config.do_preemptive_repeat = 1; 713 | config.disable_port_blocking = 1; 714 | config.enable_sslkeylog = 1; 715 | config.alpn = SLIPSTREAM_ALPN; 716 | 717 | 718 | /* Create the QUIC context for the server */ 719 | current_time = picoquic_current_time(); 720 | /* Create QUIC context */ 721 | picoquic_quic_t* quic = picoquic_create_and_configure(&config, slipstream_server_callback, &default_context, current_time, NULL); 722 | if (quic == NULL) { 723 | DBG_PRINTF("Could not create server context", NULL); 724 | return -1; 725 | } 726 | 727 | picoquic_set_cookie_mode(quic, 0); 728 | picoquic_set_default_priority(quic, 2); 729 | #ifdef BUILD_LOGLIB 730 | picoquic_set_qlog(quic, config.qlog_dir); 731 | debug_printf_push_stream(stderr); 732 | #endif 733 | picoquic_set_key_log_file_from_env(quic); 734 | // picoquic_set_textlog(quic, "-"); 735 | // picoquic_set_log_level(quic, 1); 736 | 737 | picoquic_set_default_congestion_algorithm(quic, slipstream_server_cc_algorithm); 738 | 739 | picoquic_packet_loop_param_t param = {0}; 740 | param.local_af = AF_INET; 741 | param.local_port = server_port; 742 | param.do_not_use_gso = 1; // can't use GSO since we're limited to responding to one DNS query at a time 743 | param.is_client = 0; 744 | param.decode = server_decode; 745 | param.encode = server_encode; 746 | // param.delay_max = 5000; 747 | 748 | picoquic_network_thread_ctx_t thread_ctx = {0}; 749 | thread_ctx.quic = quic; 750 | thread_ctx.param = ¶m; 751 | thread_ctx.loop_callback = slipstream_server_sockloop_callback; 752 | thread_ctx.loop_callback_ctx = &default_context; 753 | 754 | /* Open the wake up pipe or event */ 755 | picoquic_open_network_wake_up(&thread_ctx, &ret); 756 | 757 | default_context.thread_ctx = &thread_ctx; 758 | 759 | signal(SIGTERM, server_sighandler); 760 | // picoquic_packet_loop_v3(&thread_ctx); 761 | slipstream_packet_loop(&thread_ctx); 762 | ret = thread_ctx.return_code; 763 | 764 | /* And finish. */ 765 | DBG_PRINTF("Server exit, ret = %d", ret); 766 | 767 | picoquic_free(quic); 768 | 769 | return ret; 770 | } 771 | 772 | -------------------------------------------------------------------------------- /src/slipstream_client.c: -------------------------------------------------------------------------------- 1 | // ReSharper disable CppDFAUnreachableCode 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #ifdef BUILD_LOGLIB 8 | #include 9 | #endif 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | #include "lua-resty-base-encoding-base32.h" 21 | #include "picoquic_config.h" 22 | #include "slipstream.h" 23 | #include "slipstream_inline_dots.h" 24 | #include "slipstream_utils.h" 25 | #include "SPCDNS/src/dns.h" 26 | #include "SPCDNS/src/mappings.h" 27 | 28 | volatile sig_atomic_t should_shutdown = 0; 29 | 30 | void client_sighandler(int signum) { 31 | DBG_PRINTF("Signal %d received", signum); 32 | should_shutdown = 1; 33 | } 34 | 35 | 36 | typedef struct st_slipstream_client_stream_ctx_t { 37 | struct st_slipstream_client_stream_ctx_t* next_stream; 38 | struct st_slipstream_client_stream_ctx_t* previous_stream; 39 | int fd; 40 | uint64_t stream_id; 41 | volatile sig_atomic_t set_active; 42 | } slipstream_client_stream_ctx_t; 43 | 44 | typedef struct st_slipstream_client_ctx_t { 45 | picoquic_cnx_t* cnx; 46 | slipstream_client_stream_ctx_t* first_stream; 47 | picoquic_network_thread_ctx_t* thread_ctx; 48 | struct st_address_t* server_addresses; 49 | size_t server_address_count; 50 | bool ready; 51 | bool closed; 52 | int listen_sock; 53 | } slipstream_client_ctx_t; 54 | 55 | char* client_domain_name = NULL; 56 | size_t client_domain_name_len = 0; 57 | 58 | ssize_t client_encode_segment(dns_packet_t* packet, size_t* packet_len, const unsigned char* src_buf, size_t src_buf_len) { 59 | char name[255]; 60 | const size_t len = b32_encode(&name[0], (const char*) src_buf, src_buf_len, true, false); 61 | const size_t encoded_len = slipstream_inline_dotify(name, 255, len); 62 | name[encoded_len] = '.'; 63 | 64 | memcpy(&name[encoded_len + 1], client_domain_name, client_domain_name_len); 65 | name[encoded_len + 1 + client_domain_name_len] = '.'; 66 | name[encoded_len + 1 + client_domain_name_len + 1] = '\0'; 67 | 68 | dns_question_t question; 69 | question.name = name; 70 | question.type = RR_TXT; 71 | question.class = CLASS_IN; 72 | 73 | dns_answer_t edns = {0}; 74 | edns.opt.name = "."; 75 | edns.opt.type = RR_OPT; 76 | edns.opt.class = CLASS_UNKNOWN; 77 | edns.opt.ttl = 0; 78 | edns.opt.udp_payload = 1232; 79 | 80 | dns_query_t query = {0}; 81 | query.id = rand() % UINT16_MAX; 82 | query.query = true; 83 | query.opcode = OP_QUERY; 84 | query.rd = true; 85 | query.rcode = RCODE_OKAY; 86 | query.qdcount = 1; 87 | query.questions = &question; 88 | query.arcount = 1; 89 | query.additional = &edns; 90 | 91 | const dns_rcode_t rc = dns_encode(packet, packet_len, &query); 92 | if (rc != RCODE_OKAY) { 93 | DBG_PRINTF( "dns_encode() = (%d) %s: %s\n", rc, dns_rcode_text(rc), name); 94 | return -1; 95 | } 96 | 97 | return 0; 98 | } 99 | 100 | 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) { 101 | *dest_buf = NULL; 102 | 103 | // optimize path for single segment 104 | if (src_buf_len <= *segment_len) { 105 | #ifdef NOENCODE 106 | *dest_buf = malloc(src_buf_len); 107 | memcpy((void*)*dest_buf, src_buf, src_buf_len); 108 | 109 | return src_buf_len; 110 | #endif 111 | size_t packet_len = MAX_DNS_QUERY_SIZE; 112 | unsigned char* packet = malloc(packet_len); 113 | const ssize_t ret = client_encode_segment((dns_packet_t*) packet, &packet_len, src_buf, src_buf_len); 114 | if (ret < 0) { 115 | free(packet); 116 | return -1; 117 | } 118 | 119 | *dest_buf = packet; 120 | *segment_len = packet_len; 121 | 122 | return packet_len; 123 | } 124 | 125 | #ifdef NOENCODE 126 | assert(false); 127 | #endif 128 | 129 | size_t num_segments = src_buf_len / *segment_len; 130 | unsigned char* packets = malloc(MAX_DNS_QUERY_SIZE * num_segments); 131 | unsigned char* current_packet = packets; 132 | 133 | const unsigned char* segment = src_buf; 134 | size_t first_packet_len = 0; 135 | for (size_t i = 0; i < num_segments; i++) { 136 | size_t packet_len = MAX_DNS_QUERY_SIZE; 137 | const ssize_t ret = client_encode_segment((dns_packet_t*) current_packet, &packet_len, segment, *segment_len); 138 | if (ret < 0) { 139 | free(packets); 140 | return -1; 141 | } 142 | 143 | if (first_packet_len == 0) { 144 | first_packet_len = packet_len; 145 | } else { 146 | if (packet_len > first_packet_len) { 147 | DBG_PRINTF("current encoded segment length %d > %d than first segment\n", packet_len, first_packet_len); 148 | free(packets); 149 | return -1; 150 | } 151 | } 152 | 153 | current_packet += packet_len; 154 | segment += *segment_len; 155 | } 156 | 157 | *dest_buf = packets; 158 | *segment_len = first_packet_len; 159 | 160 | return current_packet - packets; 161 | } 162 | 163 | ssize_t client_decode(void* slot_p, void* callback_ctx, unsigned char** dest_buf, const unsigned char* src_buf, size_t src_buf_len, struct sockaddr_storage* peer_addr, struct sockaddr_storage* local_addr) { 164 | *dest_buf = NULL; 165 | 166 | #ifdef NODECODE 167 | *dest_buf = malloc(src_buf_len); 168 | memcpy((void*)*dest_buf, src_buf, src_buf_len); 169 | 170 | return src_buf_len; 171 | #endif 172 | 173 | size_t bufsize = DNS_DECODEBUF_4K * sizeof(dns_decoded_t); 174 | dns_decoded_t decoded[DNS_DECODEBUF_4K] = {0}; 175 | const dns_rcode_t rc = dns_decode(decoded, &bufsize, (const dns_packet_t*) src_buf, src_buf_len); 176 | if (rc != RCODE_OKAY) { 177 | DBG_PRINTF("dns_decode() = (%d) %s", rc, dns_rcode_text(rc)); 178 | return -1; 179 | } 180 | 181 | const dns_query_t *query = (dns_query_t *)decoded; 182 | 183 | if (query->query == 1) { 184 | DBG_PRINTF("[%d] dns record is not a response", query->id, NULL); 185 | return 0; 186 | } 187 | 188 | if (query->rcode == RCODE_NAME_ERROR) { 189 | // returned when the server has nothing to send 190 | return 0; 191 | } 192 | 193 | if (query->rcode != RCODE_OKAY) { 194 | DBG_PRINTF("[%d] dns record rcode not okay: %d", query->id, query->rcode); 195 | return 0; 196 | } 197 | 198 | if (query->ancount != 1) { 199 | // DBG_PRINTF("[%d] dns record should contain exactly one answer", query->id); 200 | return 0; 201 | } 202 | 203 | dns_txt_t *answer_txt = (dns_txt_t*) &query->answers[0]; 204 | if (answer_txt->type != RR_TXT) { 205 | DBG_PRINTF("[%d] answer type is not TXT", query->id, NULL); 206 | return 0; 207 | } 208 | 209 | *dest_buf = malloc(answer_txt->len); 210 | memcpy((void*)*dest_buf, answer_txt->text, answer_txt->len); 211 | 212 | return answer_txt->len; 213 | } 214 | 215 | slipstream_client_stream_ctx_t* slipstream_client_create_stream_ctx(picoquic_cnx_t* cnx, 216 | slipstream_client_ctx_t* client_ctx, int sock_fd) { 217 | slipstream_client_stream_ctx_t* stream_ctx = malloc(sizeof(slipstream_client_stream_ctx_t)); 218 | 219 | if (stream_ctx == NULL) { 220 | fprintf(stdout, "Memory Error, cannot create stream for sock %d\n", sock_fd); 221 | return NULL; 222 | } 223 | 224 | memset(stream_ctx, 0, sizeof(slipstream_client_stream_ctx_t)); 225 | if (client_ctx->first_stream == NULL) { 226 | client_ctx->first_stream = stream_ctx; 227 | } else { 228 | stream_ctx->next_stream = client_ctx->first_stream; 229 | stream_ctx->next_stream->previous_stream = stream_ctx; 230 | client_ctx->first_stream = stream_ctx; 231 | } 232 | stream_ctx->fd = sock_fd; 233 | stream_ctx->stream_id = -1; 234 | 235 | return stream_ctx; 236 | } 237 | 238 | static void slipstream_client_free_stream_ctx(slipstream_client_ctx_t* client_ctx, slipstream_client_stream_ctx_t* stream_ctx) { 239 | if (stream_ctx->previous_stream != NULL) { 240 | stream_ctx->previous_stream->next_stream = stream_ctx->next_stream; 241 | } 242 | if (stream_ctx->next_stream != NULL) { 243 | stream_ctx->next_stream->previous_stream = stream_ctx->previous_stream; 244 | } 245 | if (client_ctx->first_stream == stream_ctx) { 246 | client_ctx->first_stream = stream_ctx->next_stream; 247 | } 248 | 249 | stream_ctx->fd = close(stream_ctx->fd); 250 | 251 | free(stream_ctx); 252 | } 253 | 254 | static void slipstream_client_free_context(slipstream_client_ctx_t* client_ctx) { 255 | slipstream_client_stream_ctx_t* stream_ctx; 256 | 257 | /* Delete any remaining stream context */ 258 | while ((stream_ctx = client_ctx->first_stream) != NULL) { 259 | slipstream_client_free_stream_ctx(client_ctx, stream_ctx); 260 | } 261 | 262 | free(client_ctx->server_addresses); 263 | 264 | client_ctx->closed = true; 265 | } 266 | 267 | void slipstream_client_mark_active_pass(slipstream_client_ctx_t* client_ctx) { 268 | slipstream_client_stream_ctx_t* stream_ctx = client_ctx->first_stream; 269 | 270 | while (stream_ctx != NULL) { 271 | if (stream_ctx->set_active) { 272 | if (stream_ctx->stream_id == -1) { 273 | stream_ctx->stream_id = picoquic_get_next_local_stream_id(client_ctx->cnx, 0); 274 | printf("[%lu:%d] assigned stream id\n", stream_ctx->stream_id, stream_ctx->fd); 275 | } 276 | stream_ctx->set_active = 0; 277 | printf("[%lu:%d] activate: stream\n", stream_ctx->stream_id, stream_ctx->fd); 278 | picoquic_mark_active_stream(client_ctx->cnx, stream_ctx->stream_id, 1, stream_ctx); 279 | } 280 | stream_ctx = stream_ctx->next_stream; 281 | } 282 | } 283 | 284 | void slipstream_add_paths(slipstream_client_ctx_t* client_ctx) { 285 | picoquic_cnx_t* cnx = client_ctx->cnx; 286 | // add rest of the resolvers 287 | for (size_t i = 1; i < client_ctx->server_address_count; i++) { 288 | address_t* slipstream_path = &client_ctx->server_addresses[i]; 289 | if (slipstream_path->added) { 290 | continue; 291 | } 292 | 293 | uint64_t current_time = picoquic_current_time(); 294 | // if (current_time - cnx->start_time < 10000000) { 295 | // DBG_PRINTF("Can't add path yet", NULL); 296 | // continue; 297 | // } 298 | 299 | print_sockaddr_ip_and_port(&slipstream_path->server_address); 300 | int path_id = -2; 301 | picoquic_probe_new_path_ex(cnx, (struct sockaddr*)&slipstream_path->server_address, (struct sockaddr*)&cnx->path[0]->local_addr, 0, current_time, 0, &path_id); 302 | if (path_id < 0) { 303 | DBG_PRINTF("Failed adding path", NULL); 304 | continue; 305 | } 306 | DBG_PRINTF("Added path", NULL); 307 | 308 | picoquic_reinsert_by_wake_time(cnx->quic, cnx, current_time); 309 | slipstream_path->added = true; 310 | } 311 | } 312 | 313 | int slipstream_client_sockloop_callback(picoquic_quic_t* quic, picoquic_packet_loop_cb_enum cb_mode, 314 | void* callback_ctx, void* callback_arg) { 315 | slipstream_client_ctx_t* client_ctx = callback_ctx; 316 | 317 | if (client_ctx->closed) { 318 | return 0; 319 | } 320 | 321 | switch (cb_mode) { 322 | case picoquic_packet_loop_before_select: 323 | if (client_ctx->ready) { 324 | slipstream_add_paths(client_ctx); 325 | } 326 | if (should_shutdown) { 327 | // Iterate and close all connections 328 | picoquic_cnx_t* cnx = picoquic_get_first_cnx(quic); 329 | bool has_unclosed = false; 330 | while (cnx != NULL) { 331 | DBG_PRINTF("CNX state: %d", cnx->cnx_state); 332 | if (cnx->cnx_state != picoquic_state_disconnected) { 333 | has_unclosed = true; 334 | } 335 | 336 | picoquic_close(cnx, 0); // 0 = no error, or use appropriate error code 337 | 338 | if (cnx->cnx_state == picoquic_state_draining) { 339 | picoquic_connection_disconnect(cnx); 340 | } 341 | 342 | cnx = picoquic_get_next_cnx(cnx); 343 | } 344 | 345 | if (!has_unclosed) { 346 | DBG_PRINTF("All connections closed, shutting down.", NULL); 347 | return -1; 348 | } 349 | } 350 | case picoquic_packet_loop_wake_up: 351 | if (callback_ctx == NULL) { 352 | return 0; 353 | } 354 | 355 | slipstream_client_mark_active_pass(client_ctx); 356 | 357 | break; 358 | case picoquic_packet_loop_after_send: 359 | if (callback_ctx == NULL) { 360 | return 0; 361 | } 362 | 363 | if (client_ctx->cnx->cnx_state == picoquic_state_disconnected) { 364 | printf("Terminate packet loop\n"); 365 | return PICOQUIC_NO_ERROR_TERMINATE_PACKET_LOOP; 366 | } 367 | default: 368 | break; 369 | } 370 | 371 | return 0; 372 | } 373 | 374 | typedef struct st_slipstream_client_poller_args { 375 | int fd; 376 | picoquic_cnx_t* cnx; 377 | slipstream_client_ctx_t* client_ctx; 378 | slipstream_client_stream_ctx_t* stream_ctx; 379 | } slipstream_client_poller_args; 380 | 381 | void* slipstream_client_poller(void* arg) { 382 | slipstream_client_poller_args* args = arg; 383 | 384 | while (1) { 385 | struct pollfd fds; 386 | fds.fd = args->fd; 387 | fds.events = POLLIN; 388 | fds.revents = 0; 389 | 390 | /* add timeout handlilng */ 391 | int ret = poll(&fds, 1, 1000); 392 | if (ret < 0) { 393 | perror("poll() failed"); 394 | break; 395 | } 396 | if (ret == 0) { 397 | continue; 398 | } 399 | 400 | args->stream_ctx->set_active = 1; 401 | 402 | ret = picoquic_wake_up_network_thread(args->client_ctx->thread_ctx); 403 | if (ret != 0) { 404 | fprintf(stderr, "poll: could not wake up network thread, ret = %d\n", ret); 405 | } 406 | printf("[%lu:%d] wakeup\n", args->stream_ctx->stream_id, args->fd); 407 | 408 | break; 409 | } 410 | 411 | free(args); 412 | pthread_exit(NULL); 413 | } 414 | 415 | typedef struct st_slipstream_client_accepter_args { 416 | int fd; 417 | picoquic_cnx_t* cnx; 418 | slipstream_client_ctx_t* client_ctx; 419 | slipstream_client_stream_ctx_t* stream_ctx; 420 | picoquic_network_thread_ctx_t* thread_ctx; 421 | } slipstream_client_accepter_args; 422 | 423 | void* slipstream_client_accepter(void* arg) { 424 | slipstream_client_accepter_args* args = arg; 425 | 426 | while (1) { 427 | // Accept incoming client connection 428 | struct sockaddr_in client_addr; 429 | socklen_t client_len = sizeof(client_addr); 430 | int client_sock = accept(args->fd, (struct sockaddr*)&client_addr, &client_len); 431 | if (client_sock < 0) { 432 | if (errno == EINTR) { 433 | fprintf(stderr, "my ass?"); 434 | continue; 435 | } 436 | perror("accept() failed"); 437 | break; 438 | } 439 | 440 | char client_ip_str[INET_ADDRSTRLEN]; // Buffer for the IP address string 441 | 442 | // Convert binary IP address to string representation 443 | if (inet_ntop(AF_INET, &client_addr.sin_addr, client_ip_str, sizeof(client_ip_str)) == NULL) { 444 | perror("inet_ntop failed"); 445 | close(client_sock); // Close socket if IP conversion fails 446 | continue; // Or break, depending on desired error handling 447 | } 448 | 449 | // Convert port number from network byte order to host byte order 450 | uint16_t client_port = ntohs(client_addr.sin_port); 451 | 452 | // Print the connection details 453 | fprintf(stderr, "Accepted connection from %s:%u on socket %d\n", client_ip_str, client_port, client_sock); 454 | // --- End printing section --- 455 | 456 | slipstream_client_stream_ctx_t* stream_ctx = slipstream_client_create_stream_ctx(args->cnx, args->client_ctx, client_sock); 457 | if (stream_ctx == NULL) { 458 | fprintf(stderr, "Could not initiate stream for %d", client_sock); 459 | break; 460 | } 461 | 462 | stream_ctx->set_active = 1; 463 | 464 | int ret = picoquic_wake_up_network_thread(args->thread_ctx); 465 | if (ret != 0) { 466 | fprintf(stderr, "accept: could not wake up network thread, ret = %d\n", ret); 467 | pthread_exit(NULL); 468 | } 469 | 470 | printf("[%lu:%d] accept: connection\n[%lu:%d] wakeup\n", stream_ctx->stream_id, client_sock, stream_ctx->stream_id, client_sock); 471 | } 472 | 473 | free(args); 474 | pthread_exit(NULL); 475 | } 476 | 477 | int slipstream_client_callback(picoquic_cnx_t* cnx, 478 | uint64_t stream_id, uint8_t* bytes, size_t length, 479 | picoquic_call_back_event_t fin_or_event, void* callback_ctx, void* v_stream_ctx) { 480 | int ret = 0; 481 | slipstream_client_ctx_t* client_ctx = (slipstream_client_ctx_t*)callback_ctx; 482 | slipstream_client_stream_ctx_t* stream_ctx = (slipstream_client_stream_ctx_t*)v_stream_ctx; 483 | 484 | if (client_ctx == NULL) { 485 | /* This should never happen, because the callback context for the client is initialized 486 | * when creating the client connection. */ 487 | return -1; 488 | } 489 | 490 | switch (fin_or_event) { 491 | case picoquic_callback_stream_data: 492 | case picoquic_callback_stream_fin: 493 | /* Data arrival on stream #x, maybe with fin mark */ 494 | if (stream_ctx == NULL) { 495 | /* This is unexpected, as all contexts were declared when initializing the 496 | * connection. */ 497 | return 0; 498 | } 499 | 500 | // printf("[%lu:%d] quic_recv->send %lu bytes\n", stream_id, stream_ctx->fd, length); 501 | if (length > 0) { 502 | ssize_t bytes_sent = send(stream_ctx->fd, bytes, length, MSG_NOSIGNAL); 503 | if (bytes_sent < 0) { 504 | if (errno == EPIPE) { 505 | /* Connection closed */ 506 | printf("[%lu:%d] send: closed stream\n", stream_id, stream_ctx->fd); 507 | 508 | (void)picoquic_reset_stream(cnx, stream_id, SLIPSTREAM_FILE_CANCEL_ERROR); 509 | return 0; 510 | } 511 | if (errno == EAGAIN) { 512 | /* TODO: this is bad because we don't have a way to backpressure */ 513 | } 514 | 515 | printf("[%lu:%d] send: error: %s (%d)\n", stream_id, stream_ctx->fd, strerror(errno), errno); 516 | (void)picoquic_reset_stream(cnx, stream_id, SLIPSTREAM_INTERNAL_ERROR); 517 | return 0; 518 | } 519 | } 520 | if (fin_or_event == picoquic_callback_stream_fin) { 521 | printf("[%lu:%d] fin\n", stream_id, stream_ctx->fd); 522 | /* Close the local_sock fd */ 523 | close(stream_ctx->fd); 524 | stream_ctx->fd = -1; 525 | picoquic_unlink_app_stream_ctx(cnx, stream_id); 526 | } 527 | break; 528 | case picoquic_callback_stop_sending: /* Should not happen, treated as reset */ 529 | /* Mark stream as abandoned, close the file, etc. */ 530 | picoquic_reset_stream(cnx, stream_id, 0); 531 | /* Fall through */ 532 | case picoquic_callback_stream_reset: /* Server reset stream #x */ 533 | if (stream_ctx == NULL) { 534 | /* This is unexpected, as all contexts were declared when initializing the 535 | * connection. */ 536 | } 537 | else { 538 | printf("[%lu:%d] stream reset\n", stream_id, stream_ctx->fd); 539 | 540 | slipstream_client_free_stream_ctx(client_ctx, stream_ctx); 541 | picoquic_reset_stream(cnx, stream_id, SLIPSTREAM_FILE_CANCEL_ERROR); 542 | } 543 | break; 544 | case picoquic_callback_stateless_reset: 545 | case picoquic_callback_close: /* Received connection close */ 546 | case picoquic_callback_application_close: /* Received application close */ 547 | printf("Connection closed.\n"); 548 | should_shutdown = true; 549 | break; 550 | case picoquic_callback_prepare_to_send: 551 | /* Active sending API */ 552 | if (stream_ctx == NULL) { 553 | /* This should never happen */ 554 | } 555 | else { 556 | int length_available; 557 | ret = ioctl(stream_ctx->fd, FIONREAD, &length_available); 558 | // printf("[%lu:%d] recv->quic_send (available %d)\n", stream_id, stream_ctx->fd, length_available); 559 | if (ret < 0) { 560 | printf("[%lu:%d] ioctl error: %s (%d)\n", stream_id, stream_ctx->fd, strerror(errno), errno); 561 | /* TODO: why would it return an error? */ 562 | (void)picoquic_reset_stream(cnx, stream_id, SLIPSTREAM_INTERNAL_ERROR); 563 | break; 564 | } 565 | ret = 0; 566 | 567 | int length_to_read = MIN(length, length_available); 568 | if (length_to_read == 0) { 569 | char a; 570 | ssize_t bytes_read = recv(stream_ctx->fd, &a, 1, MSG_PEEK | MSG_DONTWAIT); 571 | // printf("[%lu:%d] recv->quic_send empty read %d bytes\n", stream_id, stream_ctx->fd, bytes_read); 572 | if (errno == EAGAIN || errno == EWOULDBLOCK) { 573 | // printf("[%lu:%d] recv->quic_send empty errno set: %s\n", stream_id, stream_ctx->fd, strerror(errno)); 574 | /* No bytes available, wait for next event */ 575 | (void)picoquic_provide_stream_data_buffer(bytes, 0, 0, 0); 576 | printf("[%lu:%d] recv->quic_send: empty, disactivate\n\n", stream_id, stream_ctx->fd); 577 | 578 | slipstream_client_poller_args* args = malloc(sizeof(slipstream_client_poller_args)); 579 | args->fd = stream_ctx->fd; 580 | args->cnx = cnx; 581 | args->client_ctx = client_ctx; 582 | args->stream_ctx = stream_ctx; 583 | 584 | pthread_t thread; 585 | if (pthread_create(&thread, NULL, slipstream_client_poller, args) != 0) { 586 | perror("pthread_create() failed for thread1"); 587 | free(args); 588 | } 589 | #ifdef __APPLE__ 590 | pthread_setname_np("slipstream_server_poller"); 591 | #else 592 | pthread_setname_np(thread, "slipstream_server_poller"); 593 | #endif 594 | pthread_detach(thread); 595 | } 596 | if (bytes_read == 0) { 597 | printf("[%lu:%d] recv: closed stream\n", stream_id, stream_ctx->fd); 598 | (void)picoquic_reset_stream(cnx, stream_id, SLIPSTREAM_FILE_CANCEL_ERROR); 599 | return 0; 600 | } 601 | if (bytes_read > 0) { 602 | /* send it in next loop iteration */ 603 | (void)picoquic_provide_stream_data_buffer(bytes, 0, 0, 1); 604 | break; 605 | } 606 | return 0; 607 | } 608 | 609 | uint8_t* buffer = picoquic_provide_stream_data_buffer(bytes, length_to_read, 0, 1); 610 | if (buffer == NULL) { 611 | /* Should never happen according to callback spec. */ 612 | break; 613 | } 614 | // printf("[%lu:%d] recv->quic_send recv %d bytes into quic\n", stream_id, stream_ctx->fd, length_to_read); 615 | ssize_t bytes_read = recv(stream_ctx->fd, buffer, length_to_read, MSG_DONTWAIT); 616 | // printf("[%lu:%d] recv->quic_send recv done %d bytes into quic\n", stream_id, stream_ctx->fd, bytes_read); 617 | if (bytes_read == 0) { 618 | printf("Closed connection on sock %d on recv", stream_ctx->fd); 619 | (void)picoquic_reset_stream(cnx, stream_id, SLIPSTREAM_FILE_CANCEL_ERROR); 620 | return 0; 621 | } 622 | if (bytes_read < 0) { 623 | fprintf(stderr, "recv: %s (%d)\n", strerror(errno), errno); 624 | /* There should be bytes available, so a return value of 0 is an error */ 625 | (void)picoquic_reset_stream(cnx, stream_id, SLIPSTREAM_INTERNAL_ERROR); 626 | return 0; 627 | } 628 | } 629 | break; 630 | case picoquic_callback_almost_ready: 631 | fprintf(stdout, "Connection completed, almost ready.\n"); 632 | break; 633 | case picoquic_callback_ready: 634 | fprintf(stdout, "Connection confirmed.\n"); 635 | client_ctx->ready = true; 636 | slipstream_add_paths(client_ctx); 637 | default: 638 | /* unexpected -- just ignore. */ 639 | break; 640 | } 641 | 642 | return ret; 643 | } 644 | 645 | static int slipstream_connect(struct sockaddr_storage* server_address, 646 | picoquic_quic_t* quic, picoquic_cnx_t** cnx, 647 | slipstream_client_ctx_t* client_ctx) { 648 | int ret = 0; 649 | char const* sni = SLIPSTREAM_SNI; 650 | uint64_t current_time = picoquic_current_time(); 651 | 652 | *cnx = NULL; 653 | 654 | char host[NI_MAXHOST]; 655 | socklen_t addrlen = sizeof(*server_address); 656 | ret = getnameinfo((struct sockaddr*)server_address, addrlen, 657 | host, sizeof(host), 658 | NULL, 0, 659 | NI_NUMERICHOST | NI_NUMERICSERV); 660 | if (ret != 0) { 661 | fprintf(stderr, "Could not get name info for server address\n"); 662 | return -1; 663 | } 664 | 665 | /* Initialize the callback context and create the connection context. 666 | * We use minimal options on the client side, keeping the transport 667 | * parameter values set by default for picoquic. This could be fixed later. 668 | */ 669 | printf("Starting connection to %s\n", host); 670 | 671 | /* Create a client connection */ 672 | *cnx = picoquic_create_cnx(quic, picoquic_null_connection_id, picoquic_null_connection_id, 673 | (struct sockaddr*)server_address, current_time, 0, sni, SLIPSTREAM_ALPN, 1); 674 | if (*cnx == NULL) { 675 | fprintf(stderr, "Could not create connection context\n"); 676 | return -1; 677 | } 678 | 679 | /* Document connection in client's context */ 680 | client_ctx->cnx = *cnx; 681 | /* Set the client callback context */ 682 | picoquic_set_callback(*cnx, slipstream_client_callback, client_ctx); 683 | /* Client connection parameters could be set here, before starting the connection. */ 684 | ret = picoquic_start_client_cnx(*cnx); 685 | if (ret < 0) { 686 | fprintf(stderr, "Could not activate connection\n"); 687 | return -1; 688 | } 689 | 690 | /* Printing out the initial CID, which is used to identify log files */ 691 | picoquic_connection_id_t icid = picoquic_get_initial_cnxid(*cnx); 692 | printf("Initial connection ID: "); 693 | for (uint8_t i = 0; i < icid.id_len; i++) { 694 | printf("%02x", icid.id[i]); 695 | } 696 | printf("\n"); 697 | 698 | return ret; 699 | } 700 | 701 | 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) { 702 | /* Start: start the QUIC process */ 703 | int ret = 0; 704 | uint64_t current_time = 0; 705 | 706 | client_domain_name = strdup(domain_name); 707 | client_domain_name_len = strlen(domain_name); 708 | 709 | double mtu_d = 240 - (double) client_domain_name_len; 710 | mtu_d = mtu_d / 1.6; 711 | int mtu = (int) mtu_d; 712 | 713 | /* Create config */ 714 | picoquic_quic_config_t config; 715 | picoquic_config_init(&config); 716 | config.nb_connections = 8; 717 | // config.log_file = "-"; 718 | #ifdef BUILD_LOGLIB 719 | config.qlog_dir = SLIPSTREAM_QLOG_DIR; 720 | #endif 721 | config.mtu_max = mtu; 722 | config.initial_send_mtu_ipv4 = mtu; 723 | config.initial_send_mtu_ipv6 = mtu; 724 | config.cc_algo_id = cc_algo_id; 725 | config.multipath_option = 1; 726 | config.use_long_log = 1; 727 | config.do_preemptive_repeat = 1; 728 | config.disable_port_blocking = 1; 729 | config.enable_sslkeylog = 1; 730 | config.alpn = SLIPSTREAM_ALPN; 731 | 732 | /* Create the QUIC context for the server */ 733 | current_time = picoquic_current_time(); 734 | // one connection only, freed in slipstream_client_free_context on picoquic close callback 735 | slipstream_client_ctx_t client_ctx = {0}; 736 | /* Create QUIC context */ 737 | picoquic_quic_t* quic = picoquic_create_and_configure(&config, slipstream_client_callback, &client_ctx, current_time, NULL); 738 | if (quic == NULL) { 739 | fprintf(stderr, "Could not create server context\n"); 740 | return -1; 741 | } 742 | 743 | picoquic_set_cookie_mode(quic, 0); 744 | picoquic_set_default_priority(quic, 2); 745 | #ifdef BUILD_LOGLIB 746 | picoquic_set_qlog(quic, config.qlog_dir); 747 | debug_printf_push_stream(stderr); 748 | #endif 749 | picoquic_set_key_log_file_from_env(quic); 750 | // picoquic_set_textlog(quic, "-"); 751 | // picoquic_set_log_level(quic, 1); 752 | // TODO: idle timeout? 753 | 754 | /* Parse the server addresses directly */ 755 | client_ctx.server_addresses = server_addresses; 756 | client_ctx.server_address_count = server_address_count; 757 | 758 | picoquic_cnx_t* cnx = NULL; 759 | ret = slipstream_connect(&client_ctx.server_addresses[0].server_address, quic, &cnx, &client_ctx); 760 | if (ret != 0) { 761 | fprintf(stderr, "Could not connect to server\n"); 762 | return -1; 763 | } 764 | 765 | if (keep_alive_interval != 0) { 766 | picoquic_enable_keep_alive(cnx, keep_alive_interval * 1000); 767 | } else { 768 | picoquic_disable_keep_alive(cnx); 769 | } 770 | 771 | // Create listening socket 772 | client_ctx.listen_sock = socket(AF_INET, SOCK_STREAM, 0); 773 | if (client_ctx.listen_sock < 0) { 774 | perror("socket() failed"); 775 | exit(EXIT_FAILURE); 776 | } 777 | 778 | int optval = 1; 779 | setsockopt(client_ctx.listen_sock, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)); 780 | 781 | struct sockaddr_in listen_addr = {0}; 782 | listen_addr.sin_family = AF_INET; 783 | listen_addr.sin_addr.s_addr = INADDR_ANY; 784 | listen_addr.sin_port = htons(listen_port); 785 | 786 | if (bind(client_ctx.listen_sock, (struct sockaddr*)&listen_addr, sizeof(listen_addr)) < 0) { 787 | perror("bind() failed"); 788 | close(client_ctx.listen_sock); 789 | exit(EXIT_FAILURE); 790 | } 791 | 792 | if (listen(client_ctx.listen_sock, 5) < 0) { 793 | perror("listen() failed"); 794 | close(client_ctx.listen_sock); 795 | exit(EXIT_FAILURE); 796 | } 797 | 798 | printf("Listening on port %d...\n", listen_port); 799 | 800 | picoquic_packet_loop_param_t param = {0}; 801 | param.local_af = AF_INET; 802 | 803 | // For loopback testing, we need to disable hardware GSO since packets on loopback never reach a hardware NIC 804 | // $ ethtool -K lo tx-udp-segmentation off 805 | // And ensure that gso is on 806 | // $ ethtool -k lo | grep generic-segmentation-offload 807 | // generic-segmentation-offload: on 808 | param.do_not_use_gso = !gso; 809 | 810 | param.is_client = 1; 811 | param.decode = client_decode; 812 | param.encode = client_encode; 813 | 814 | picoquic_network_thread_ctx_t thread_ctx = {0}; 815 | thread_ctx.quic = quic; 816 | thread_ctx.param = ¶m; 817 | thread_ctx.loop_callback = slipstream_client_sockloop_callback; 818 | thread_ctx.loop_callback_ctx = &client_ctx; 819 | 820 | /* Open the wake up pipe or event */ 821 | picoquic_open_network_wake_up(&thread_ctx, &ret); 822 | 823 | client_ctx.thread_ctx = &thread_ctx; 824 | 825 | slipstream_client_accepter_args* args = malloc(sizeof(slipstream_client_accepter_args)); 826 | args->fd = client_ctx.listen_sock; 827 | args->cnx = cnx; 828 | args->client_ctx = &client_ctx; 829 | args->thread_ctx = &thread_ctx; 830 | 831 | pthread_t thread; 832 | if (pthread_create(&thread, NULL, slipstream_client_accepter, args) != 0) { 833 | perror("pthread_create() failed for thread"); 834 | free(args); 835 | } 836 | 837 | signal(SIGTERM, client_sighandler); 838 | // picoquic_packet_loop_v3(&thread_ctx); 839 | slipstream_packet_loop(&thread_ctx); 840 | ret = thread_ctx.return_code; 841 | 842 | /* And finish. */ 843 | printf("Client exit, ret = %d\n", ret); 844 | 845 | picoquic_free(quic); 846 | 847 | return ret; 848 | } 849 | --------------------------------------------------------------------------------