From ee40ecd5c6c555e7825e84405af0124985c4a3a9 Mon Sep 17 00:00:00 2001 From: filipton Date: Wed, 4 Sep 2024 18:04:34 +0200 Subject: [PATCH] Allow http2 traffic (using --own-ssl option on client) (#3) * wip: read SNI from ClientHello packet (ssl) (with rustls) * wip: init sni-parser * wip: use rustls to parse true value of SNI * feat: sni parser (own) * wip: pcap reader * feat: pcap to .bin parser (client hello packets) * feat: sni_parser without println and useless stuff * feat: safer sni parser * feat: nginx test docker (http1 + http2 + http3) * feat: sni parser for quic (inner) * wip: test parse quic * wip: parse encrypted quic packet (using s2n proto and core) * wip: remove quic tests (moved into new repo) * chore: move sni parser into other crate * chore: move sni-parser into qls crate * wip: own-ssl setting (for http2) * feat: working http2 own-ssl (only for http2 - ssl) --- Cargo.lock | 228 +++++++++++++++++++++++++++++++++ Cargo.toml | 1 + nginx-test-docker/Dockerfile | 3 + nginx-test-docker/nginx.conf | 52 ++++++++ nginx-test-docker/run.sh | 4 + nginx-test-docker/ssl/cert.pem | 22 ++++ nginx-test-docker/ssl/key.pem | 28 ++++ perf.js | 17 --- src/client/main.rs | 5 +- src/server/structs.rs | 10 +- src/server/tunnel.rs | 90 +++++++------ utils/src/lib.rs | 19 +-- 12 files changed, 410 insertions(+), 69 deletions(-) create mode 100644 nginx-test-docker/Dockerfile create mode 100644 nginx-test-docker/nginx.conf create mode 100755 nginx-test-docker/run.sh create mode 100644 nginx-test-docker/ssl/cert.pem create mode 100644 nginx-test-docker/ssl/key.pem delete mode 100644 perf.js diff --git a/Cargo.lock b/Cargo.lock index 52d4be3..0a612d4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,6 +17,41 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "aead" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" +dependencies = [ + "crypto-common", + "generic-array", +] + +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + +[[package]] +name = "aes-gcm" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" +dependencies = [ + "aead", + "aes", + "cipher", + "ctr", + "ghash", + "subtle", +] + [[package]] name = "anstream" version = "0.6.13" @@ -104,6 +139,15 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + [[package]] name = "bytes" version = "1.6.0" @@ -122,6 +166,16 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + [[package]] name = "clap" version = "4.5.16" @@ -168,6 +222,35 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" +[[package]] +name = "cpufeatures" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "rand_core", + "typenum", +] + +[[package]] +name = "ctr" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +dependencies = [ + "cipher", +] + [[package]] name = "deranged" version = "0.3.11" @@ -177,6 +260,17 @@ dependencies = [ "powerfmt", ] +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", + "subtle", +] + [[package]] name = "dotenvy" version = "0.15.7" @@ -192,6 +286,7 @@ dependencies = [ "dotenvy", "highway", "kanal", + "qls-proto-utils", "rcgen", "serde", "serde_json", @@ -209,6 +304,16 @@ version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + [[package]] name = "getrandom" version = "0.2.12" @@ -220,6 +325,16 @@ dependencies = [ "wasi", ] +[[package]] +name = "ghash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1" +dependencies = [ + "opaque-debug", + "polyval", +] + [[package]] name = "gimli" version = "0.28.1" @@ -238,12 +353,51 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hex-literal" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" + [[package]] name = "highway" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c706f1711006204c2ba8fb1a7bd55f689bbf7feca9ff40325206b5e140cff6df" +[[package]] +name = "hkdf" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +dependencies = [ + "hmac", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "inout" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +dependencies = [ + "generic-array", +] + [[package]] name = "itoa" version = "1.0.11" @@ -346,6 +500,12 @@ version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +[[package]] +name = "opaque-debug" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" + [[package]] name = "overload" version = "0.1.1" @@ -368,6 +528,18 @@ version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" +[[package]] +name = "polyval" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" +dependencies = [ + "cfg-if", + "cpufeatures", + "opaque-debug", + "universal-hash", +] + [[package]] name = "powerfmt" version = "0.2.0" @@ -383,6 +555,20 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "qls-proto-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eef7ed1f8a91729bbfd1b1e128f7ec2cd6fd0c5d1e64f86f66143e6b4c794439" +dependencies = [ + "aes", + "aes-gcm", + "hex", + "hex-literal", + "hkdf", + "sha2", +] + [[package]] name = "quote" version = "1.0.35" @@ -392,6 +578,15 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + [[package]] name = "rcgen" version = "0.13.1" @@ -512,6 +707,17 @@ dependencies = [ "serde", ] +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + [[package]] name = "sharded-slab" version = "0.1.7" @@ -720,12 +926,28 @@ dependencies = [ "tracing-log", ] +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + [[package]] name = "unicode-ident" version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +[[package]] +name = "universal-hash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" +dependencies = [ + "crypto-common", + "subtle", +] + [[package]] name = "untrusted" version = "0.9.0" @@ -756,6 +978,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" diff --git a/Cargo.toml b/Cargo.toml index adfa933..4322001 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,6 +27,7 @@ serde = { version = "1.0.209", features = ["derive"] } serde_json = "1.0.127" utils = { path = "./utils" } +qls-proto-utils = "0.1.0" [profile.release] opt-level = "z" # Optimize for size. diff --git a/nginx-test-docker/Dockerfile b/nginx-test-docker/Dockerfile new file mode 100644 index 0000000..3e3837d --- /dev/null +++ b/nginx-test-docker/Dockerfile @@ -0,0 +1,3 @@ +FROM nginx:1.27.1 +COPY ssl /ssl +COPY nginx.conf /etc/nginx/nginx.conf diff --git a/nginx-test-docker/nginx.conf b/nginx-test-docker/nginx.conf new file mode 100644 index 0000000..15c5ddc --- /dev/null +++ b/nginx-test-docker/nginx.conf @@ -0,0 +1,52 @@ +user nginx; +worker_processes 1; + +error_log /var/log/nginx/error.log warn; +pid /var/run/nginx.pid; + +events { + worker_connections 1024; +} + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for"'; + + access_log /var/log/nginx/access.log main; + + sendfile on; + + keepalive_timeout 65; + + server { + listen 80 default_server; + listen [::]:80 default_server; + return 301 https://$host$request_uri; + + root /usr/share/nginx/html; + } + + server { + http2 on; + listen 443 ssl default_server; + listen [::]:443 ssl default_server; + + listen 443 quic reuseport; + listen [::]:443 quic reuseport; + + add_header Alt-Svc 'h3-23=":443"'; + + ssl_certificate /ssl/cert.pem; + ssl_certificate_key /ssl/key.pem; + ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3; + index index.html index.htm; + ssl_ciphers HIGH:!aNULL:!MD5; + http2_max_concurrent_streams 1024; + + root /usr/share/nginx/html; + } +} diff --git a/nginx-test-docker/run.sh b/nginx-test-docker/run.sh new file mode 100755 index 0000000..524629d --- /dev/null +++ b/nginx-test-docker/run.sh @@ -0,0 +1,4 @@ +#!/bin/bash + +docker build -t nginx-test-docker . +docker run -it --rm -p 80:80/tcp -p 443:443/udp -p 443:443/tcp nginx-test-docker diff --git a/nginx-test-docker/ssl/cert.pem b/nginx-test-docker/ssl/cert.pem new file mode 100644 index 0000000..c357491 --- /dev/null +++ b/nginx-test-docker/ssl/cert.pem @@ -0,0 +1,22 @@ +-----BEGIN CERTIFICATE----- +MIIDfDCCAmSgAwIBAgIITyI/3fjewaYwDQYJKoZIhvcNAQELBQAwQzEaMBgGA1UE +CgwRUGhvZW5peCBGcmFtZXdvcmsxJTAjBgNVBAMMHFNlbGYtc2lnbmVkIHRlc3Qg +Y2VydGlmaWNhdGUwHhcNMjAxMDI4MDAwMDAwWhcNMjExMDI4MDAwMDAwWjBDMRow +GAYDVQQKDBFQaG9lbml4IEZyYW1ld29yazElMCMGA1UEAwwcU2VsZi1zaWduZWQg +dGVzdCBjZXJ0aWZpY2F0ZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB +AKjUsckcT1nm5cKy78mLa1Zn91Tk+dzSQvSus6MuPCPGhHJUBD4uF8eX/WkfoGdt +H3lQRDRxBRUXqzUZZgcK8ZPgjnCwvykl7H3HidtWHPsbkSZz+pWETaefdjJFnzsl +zMsjg+tqgovyPkLz3JT26QOmU1FSGjaSlEG2V2cDFDGj6Say4rfGo7tHKiDsjrfS +XB75Vzah8xmBTZJUMqAG4noqKRvqZrNV73szfFGhHYjp0LHjt1b0z40Fy6f6bE1C +yxcXmE04AxrJ7Zhi4rbyKBpExGSjqXBCbc+TREW5aWkOkHKymKTzREOixgLJ08CV ++OrQqXuKXTYhybCn9Xj17IsCAwEAAaN0MHIwDAYDVR0TAQH/BAIwADAOBgNVHQ8B +Af8EBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMB0GA1UdDgQW +BBQeqCc8RSJCDNMliHg/1SxZXYJxJjAUBgNVHREEDTALgglsb2NhbGhvc3QwDQYJ +KoZIhvcNAQELBQADggEBADDPfjLQGLy1zCKpRDVO5C1BxlNMKhU0LWaA555O/3E5 +GpOyE+hsVY9OPoidcUlG+hrIjp2ZO+FOwsTW+Il5CYbSmKPnnc19s1jwq6F2kCSd +KPfSkEmtPpghH7e7gvtVa/W33UQq4V0vW7diyyUeJ0xFh6c4n/t95/oFs6OsAxXK +ynzXDfKHZWA8SGNCnpwHcmDJ701hZ8RQUOMiX9uHQV1p5AzI6CMitdiJPoPFWGol +OmD6KpRjTIDf9DLgCJkH5eBhXHNNrYH81iLMWRgD90MpfUT+4KBNz7XORQW2tRvd +2Ru2nrKGbYnwbMQsYEpFBREjRJZWXD3RO9PRKU4f/Bk= +-----END CERTIFICATE----- + diff --git a/nginx-test-docker/ssl/key.pem b/nginx-test-docker/ssl/key.pem new file mode 100644 index 0000000..62ccaca --- /dev/null +++ b/nginx-test-docker/ssl/key.pem @@ -0,0 +1,28 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEAqNSxyRxPWeblwrLvyYtrVmf3VOT53NJC9K6zoy48I8aEclQE +Pi4Xx5f9aR+gZ20feVBENHEFFRerNRlmBwrxk+COcLC/KSXsfceJ21Yc+xuRJnP6 +lYRNp592MkWfOyXMyyOD62qCi/I+QvPclPbpA6ZTUVIaNpKUQbZXZwMUMaPpJrLi +t8aju0cqIOyOt9JcHvlXNqHzGYFNklQyoAbieiopG+pms1XvezN8UaEdiOnQseO3 +VvTPjQXLp/psTULLFxeYTTgDGsntmGLitvIoGkTEZKOpcEJtz5NERblpaQ6QcrKY +pPNEQ6LGAsnTwJX46tCpe4pdNiHJsKf1ePXsiwIDAQABAoIBACOx1GTQt3YqeylO +66OQ9oCuOnyYbLtjOQTCavh1LgHiVt5VJRCAbkaku88eAKvdRmo/rivoOUrcVjZD +yG50wd6h+3uX4tKwUe/F4D+fVUFB8s5OdwxljIWEEVlPnvrCYyoPuDRtsGwmK0O8 +i0ZzbZB3+eWXljMzimTaSGBfJkXYKbxEzQDhmfWhrjBAryWx4coFx9kekT95I8t5 +gwa6AxL7dFjUG6uLDBtXeW5mUPAvf4QFYJapLIJ+qiezNw2Qqr/7GGS6Uk0QYmjK +DfTdW4Nz8KXMRBAmgAu/NRXF/ROVNJo4afOoVPBCLRsLw6sZHLKrlbYIM+z3+56q +BKlKZAECgYEA3aR/UPaBdJK1x6Ki1oo2JbBilqY2JBACC4bpzj+xWYOn7kl4acjy +VLY7/wrhKyGwJJrAeiQ7HPrlLTpj2/5jOyTZoTBokCjDGh4GvnCOU/Wx58OBjAEN +ximuk77wDEH9sDVwUy1c8W8U8cmnXhNKl7XtbhDMlIUCMPkQL3VOvNECgYEAwwB0 +bsSMOfZk+OySc3pzR6c03ryk90bK39DvJksa0qA3maJmYD9mtwe1es2NtIAMcvjl +E0tgXTgODqSe3SceX7/hSLRoveT4Wc/bbQP2valQRfulxpc2uHTV5MH//9ukh9ym +HHMcX2IFThEQJ83fCquSFF3F4Ip5Tc30iMLuepsCgYEAohw0usTxfo/nwPJlY8GH +RR2znjhHlSzYMhIlZCMV1kEDTLgUCFtfUzJOw4XzlxZ2vO8rJKrnSZUAlooEi92O +Oq4DN3AuqnIF8U5Q5G1yzFoCG1/Vms8SggPumkWnUW/a20zasRuWxUfX69jwP1Cg +2EHKPRywZVi7d5JTkkKcqmECgYEAsfSYTP3RepQBUamTfQ8iZSGSfjJp2cAhJF/n +32pq8AyGsQ4jn3I8qou+cnNj/nVc5w98/j6rkma1bWeOEwTAE2FolpENhT6P5mZq +OPKFGlCJkVQ11SXqKit1h24D0dsAK3yRmyTxn5o0fSnVNH4MLhClHzD0hBXa3cY2 +gxhhykECgYBJV826lZ1fk6J8V/1G+nN6uZaOm2azVoh+hqrCtS10t82UuA4O5lP7 +bfzZKmq+BbslfsZ8q4TGegskzVr5byW6PI56PfMkfrDtDXcdDkqhiHBrLM/mPGXB +8z7OeHPyGxGZCJo2knc1JHrszJ0k/AHQgzQWkkbLhyi/2E6Xiut9hg== +-----END RSA PRIVATE KEY----- + diff --git a/perf.js b/perf.js deleted file mode 100644 index 203d56d..0000000 --- a/perf.js +++ /dev/null @@ -1,17 +0,0 @@ -import http from "k6/http"; -import { check, sleep } from "k6"; - -export const options = { - stages: [ - { duration: "5s", target: 500 }, - { duration: "30s", target: 500 }, - { duration: "5s", target: 0 }, - ], -}; - -// Simulated user behavior -export default function() { - let res = http.get("https://test2.fkm.filipton.space"); - // Validate response status - check(res, { "status was 200": (r) => r.status == 200 }); -} diff --git a/src/client/main.rs b/src/client/main.rs index 79277b1..fc88550 100644 --- a/src/client/main.rs +++ b/src/client/main.rs @@ -33,6 +33,9 @@ struct Args { #[arg(short, long, action, env = "REDIRECT_SSL")] redirect_ssl: bool, + + #[arg(long, action, env = "OWN_SSL")] + own_ssl: bool, } #[tokio::main] @@ -66,7 +69,7 @@ async fn connector(args: &Args) -> Result<()> { let stream = TcpStream::connect(&args.proxy_addr).await?; let mut stream = acceptor.accept(stream).await?; - let mut hello_packet = generate_hello_packet(0, &args.token, &args.hash)?; + let mut hello_packet = generate_hello_packet(0, &args.token, &args.hash, args.own_ssl)?; stream.write_all(&hello_packet).await?; let nonssl_port = stream.read_u16().await?; diff --git a/src/server/structs.rs b/src/server/structs.rs index 282058b..a312e90 100644 --- a/src/server/structs.rs +++ b/src/server/structs.rs @@ -18,7 +18,7 @@ pub enum TunnelRequest { pub type TunnelSender = AsyncSender; pub struct InnerProxyState { - pub tunnels: HashMap, + pub tunnels: HashMap, pub requests: HashMap>>, pub domains: HashMap, // url(hashed) -> domain } @@ -105,12 +105,12 @@ impl SharedProxyState { Ok(hash) } - pub async fn insert_tunnel_connector(&self, token: u128, tunnel: TunnelSender) { + pub async fn insert_tunnel_connector(&self, token: u128, tunnel: TunnelSender, own_ssl: bool) { let mut state = self.inner.write().await; - let old = state.tunnels.insert(token, tunnel); + let old = state.tunnels.insert(token, (own_ssl, tunnel)); if let Some(old) = old { - _ = old.send(TunnelRequest::Close).await; + _ = old.1.send(TunnelRequest::Close).await; } } @@ -129,7 +129,7 @@ impl SharedProxyState { state.domains.get(&url_hash).map(|x| x.0) } - pub async fn get_tunnel_entry(&self, token: u128) -> Option { + pub async fn get_tunnel_entry(&self, token: u128) -> Option<(bool, TunnelSender)> { let state = self.inner.read().await; state.tunnels.get(&token).cloned() } diff --git a/src/server/tunnel.rs b/src/server/tunnel.rs index ec50e84..90b0c15 100644 --- a/src/server/tunnel.rs +++ b/src/server/tunnel.rs @@ -76,10 +76,7 @@ async fn connector_handler( .await .ok_or_else(|| anyhow!("Cant find token!"))?; - let res = ::utils::parse_hello_packet(token, &connection_buff); - if let Err(e) = res { - return Err(e.into()); - } + let own_ssl = ::utils::parse_hello_packet(token, &connection_buff)?; // im the connector! if connection_buff[0] == 0 { @@ -94,7 +91,7 @@ async fn connector_handler( ::utils::send_string_to_stream(&mut stream, &domain).await?; let (tx, rx) = kanal::unbounded_async::(); - state.insert_tunnel_connector(token, tx).await; + state.insert_tunnel_connector(token, tx, own_ssl).await; let res = connector_loop(&mut stream, rx).await; if let Err(e) = res { tracing::error!("Connector loop: {e:?}"); @@ -169,36 +166,66 @@ async fn remote_listener(addr: SocketAddr, state: SharedProxyState, ssl: bool) - } async fn handle_client( - stream: TcpStream, + mut stream: TcpStream, state: SharedProxyState, ssl: bool, acceptor: Arc, ) -> Result<()> { + let mut in_buffer = [0; 4096]; + let (host, n) = get_host(&mut stream, &mut in_buffer, ssl).await?; + let tunn_res = get_host_tunnel(&state, &host).await; + let own_ssl = tunn_res.as_ref().map(|x| x.0).unwrap_or(false); + if ssl { - let stream = acceptor.accept(stream).await?; - handle_client_inner(stream, state, true).await?; + if own_ssl { + handle_client_inner(stream, state, tunn_res, &host, &in_buffer[..n], true).await?; + } else { + let stream = acceptor.accept(stream).await?; + handle_client_inner(stream, state, tunn_res, &host, &in_buffer[..n], true).await?; + } } else { - handle_client_inner(stream, state, false).await?; + handle_client_inner(stream, state, tunn_res, &host, &in_buffer[..n], false).await?; } Ok(()) } -async fn handle_client_inner(mut stream: T, state: SharedProxyState, ssl: bool) -> Result<()> +async fn get_host( + stream: &mut TcpStream, + in_buffer: &mut [u8], + ssl: bool, +) -> Result<(String, usize)> { + let n = stream.read(in_buffer).await?; + let host = if ssl { + qls_proto_utils::tls::sni::parse_sni(&in_buffer[..n]) + .ok_or_else(|| anyhow!("Server name not found in TLS initial handshake"))? + } else { + let host = ::utils::read_http_host(&in_buffer[..n])?; + let host = host.split(":").next().unwrap(); // remove port from host + + host.to_owned() + }; + + Ok((host, n)) +} + +async fn handle_client_inner( + mut stream: T, + state: SharedProxyState, + tunn_res: TunnelGetResult, + host: &str, + in_buffer: &[u8], + ssl: bool, +) -> Result<()> where T: AsyncRead + AsyncWrite + Unpin, { - let mut in_buffer = [0; 8192]; - - let (host, n) = ::utils::read_http_host(&mut stream, &mut in_buffer).await?; - let host = host.split(":").next().unwrap(); // remove port from host - if state.is_host_panel(&host) { - serve_panel(&mut stream, in_buffer, n, &state).await?; + serve_panel(&mut stream, &in_buffer, &state).await?; return Ok(()); } - if let Ok((tunn, _)) = get_tunn_or_error(&state, &host, &mut stream).await { + if let Ok(tunn) = get_tunn_or_error(tunn_res, &mut stream).await { let rng = state.consts.rng.secure_random; let mut generated_tunnel_id = [0u8; 16]; rng.fill(&mut generated_tunnel_id).unwrap(); @@ -235,7 +262,7 @@ where } let mut tunnel = tunnel_res??; - tunnel.write_all(&in_buffer[..n]).await?; // relay the first packet + tunnel.write_all(&in_buffer).await?; // relay the first packet _ = tokio::io::copy_bidirectional(&mut stream, &mut tunnel).await; _ = tunnel.shutdown().await; } @@ -244,15 +271,11 @@ where Ok(()) } -async fn get_tunn_or_error( - state: &SharedProxyState, - host: &str, - stream: &mut T, -) -> Result<(TunnelSender, u128)> +async fn get_tunn_or_error(tunn_res: TunnelGetResult, stream: &mut T) -> Result where T: AsyncRead + AsyncWrite + Unpin, { - let tunn = match get_tunn(&state, &host).await { + let tunn = match tunn_res { Ok(tunn) => tunn, Err(TunnelError::TunnelDoesNotExist) => { _ = ::utils::http::write_raw_http_resp( @@ -279,13 +302,11 @@ where } }; - Ok(tunn) + Ok(tunn.1) } -async fn get_tunn( - state: &SharedProxyState, - host: &str, -) -> Result<(TunnelSender, u128), TunnelError> { +pub type TunnelGetResult = Result<(bool, TunnelSender), TunnelError>; +async fn get_host_tunnel(state: &SharedProxyState, host: &str) -> TunnelGetResult { let token = state .get_client_token(&host) .await @@ -296,19 +317,14 @@ async fn get_tunn( .await .ok_or_else(|| TunnelError::NoConnectorForTunnel)?; - Ok((tunn, token)) + Ok(tunn) } -async fn serve_panel( - stream: &mut T, - in_buffer: [u8; 8192], - n: usize, - state: &SharedProxyState, -) -> Result<()> +async fn serve_panel(stream: &mut T, in_buffer: &[u8], state: &SharedProxyState) -> Result<()> where T: AsyncRead + AsyncWrite + Unpin, { - let mut lines = in_buffer[..n].lines(); + let mut lines = in_buffer.lines(); let http_header = lines .next_line() .await? diff --git a/utils/src/lib.rs b/utils/src/lib.rs index adb780b..d83a676 100644 --- a/utils/src/lib.rs +++ b/utils/src/lib.rs @@ -21,16 +21,21 @@ pub fn generate_hello_packet( connector_type: u8, token: &u128, hash: &u64, + own_ssl: bool, ) -> Result<[u8; 80], HelloPacketError> { let mut conn_buff = [0u8; 80]; conn_buff[0] = connector_type; conn_buff[1..9].copy_from_slice(&hash.to_be_bytes()); conn_buff[10..26].copy_from_slice(&token.to_be_bytes()); + conn_buff[26] = own_ssl as u8; Ok(conn_buff) } -pub fn parse_hello_packet(token: u128, connection_buff: &[u8; 80]) -> Result<(), HelloPacketError> { +pub fn parse_hello_packet( + token: u128, + connection_buff: &[u8; 80], +) -> Result { //let parsed_connector_type = connection_buff[0]; //let parsed_hash = u64::from_be_bytes(connection_buff[1..9].try_into()?); let parsed_token = u128::from_be_bytes(connection_buff[10..26].try_into()?); @@ -39,7 +44,7 @@ pub fn parse_hello_packet(token: u128, connection_buff: &[u8; 80]) -> Result<(), return Err(HelloPacketError::TokenMismatch); } - Ok(()) + Ok(connection_buff[26] != 0x00) } pub fn generate_string_packet(string: &str) -> Result> { @@ -88,16 +93,12 @@ pub enum HelloPacketError { Anyhow(#[from] anyhow::Error), } -pub async fn read_http_host(stream: &mut T, in_buffer: &mut [u8]) -> Result<(String, usize)> -where - T: AsyncRead + AsyncWrite + Unpin, -{ - let n = stream.read(in_buffer).await?; - let mut lines = in_buffer[..n].split(|&x| x == b'\n'); +pub fn read_http_host(in_buffer: &[u8]) -> Result { + let mut lines = in_buffer.split(|&x| x == b'\n'); let host = lines .find(|x| x.to_ascii_lowercase().starts_with(b"host:")) .ok_or_else(|| anyhow::anyhow!("No host"))?; let host = String::from_utf8_lossy(&host[5..]).trim().to_string(); - Ok((host, n)) + Ok(host) }