From 3a280b0ac01e66cad7c30cb3f3b6e8f412267386 Mon Sep 17 00:00:00 2001 From: Emil Sauer Lynge Date: Sat, 8 Jan 2022 22:42:39 +0100 Subject: [PATCH 1/2] add QUIC transport based on bi streams --- Cargo.lock | 308 ++++++++++++++++++++++++++++ Cargo.toml | 10 +- examples/quic/ca.pfx | Bin 0 -> 2634 bytes examples/quic/client.toml | 12 ++ examples/quic/server.toml | 12 ++ examples/quic/test_ca.pem | 19 ++ examples/quic/test_server.cert.pem | 45 +++++ examples/quic/test_server.key.pem | 32 +++ examples/quic/test_server.pfx | Bin 0 -> 3237 bytes src/client.rs | 11 + src/config.rs | 50 +++-- src/lib.rs | 4 +- src/server.rs | 20 +- src/transport/mod.rs | 8 +- src/transport/noise.rs | 5 +- src/transport/quic.rs | 315 +++++++++++++++++++++++++++++ src/transport/tcp.rs | 5 +- src/transport/tls.rs | 5 +- tests/for_tcp/quic_transport.toml | 29 +++ tests/for_udp/quic_transport.toml | 33 +++ tests/integration_test.rs | 2 + 21 files changed, 902 insertions(+), 23 deletions(-) create mode 100644 examples/quic/ca.pfx create mode 100644 examples/quic/client.toml create mode 100644 examples/quic/server.toml create mode 100644 examples/quic/test_ca.pem create mode 100644 examples/quic/test_server.cert.pem create mode 100644 examples/quic/test_server.key.pem create mode 100644 examples/quic/test_server.pfx create mode 100644 src/transport/quic.rs create mode 100644 tests/for_tcp/quic_transport.toml create mode 100644 tests/for_udp/quic_transport.toml diff --git a/Cargo.lock b/Cargo.lock index 1baebf93..633d08c8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -180,6 +180,28 @@ dependencies = [ "generic-array", ] +[[package]] +name = "block-modes" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cb03d1bed155d89dce0f845b7899b18a9a163e148fd004e1c28421a783e2d8e" +dependencies = [ + "block-padding", + "cipher", +] + +[[package]] +name = "block-padding" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae" + +[[package]] +name = "bumpalo" +version = "3.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a45a46ab1f2412e53d3a0ade76ffad2025804294569aae387231a0cd6e0899" + [[package]] name = "byteorder" version = "1.4.3" @@ -437,6 +459,17 @@ dependencies = [ "zeroize", ] +[[package]] +name = "des" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac41dd49fb554432020d52c875fc290e110113f864c6b1b525cd62c7e7747a5d" +dependencies = [ + "byteorder", + "cipher", + "opaque-debug", +] + [[package]] name = "digest" version = "0.9.0" @@ -455,6 +488,7 @@ dependencies = [ "block-buffer 0.10.0", "crypto-common", "generic-array", + "subtle", ] [[package]] @@ -637,14 +671,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9b5cf40b47a271f77a8b1bec03ca09044d99d2372c0de244e66430761127164" dependencies = [ "futures-core", + "futures-io", "futures-macro", "futures-sink", "futures-task", + "memchr", "pin-project-lite", "pin-utils", "slab", ] +[[package]] +name = "fxhash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" +dependencies = [ + "byteorder", +] + [[package]] name = "generic-array" version = "0.14.5" @@ -780,6 +825,15 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hmac" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddca131f3e7f2ce2df364b57949a9d47915cfbd35e46cfee355ccebbf794d6a2" +dependencies = [ + "digest 0.10.1", +] + [[package]] name = "http" version = "0.2.6" @@ -936,6 +990,15 @@ dependencies = [ "libc", ] +[[package]] +name = "js-sys" +version = "0.3.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cc9ffccd38c451a86bf13657df244e9c3f37493cce8e5e21e940963777acc84" +dependencies = [ + "wasm-bindgen", +] + [[package]] name = "kqueue" version = "1.0.4" @@ -1214,6 +1277,22 @@ dependencies = [ "memchr", ] +[[package]] +name = "p12" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55a97f513ac60e90aec6d9db783b1924a25839a2635036a3ab64f2ca78cd4518" +dependencies = [ + "block-modes", + "des", + "getrandom 0.2.4", + "hmac", + "lazy_static", + "rc2", + "sha-1", + "yasna", +] + [[package]] name = "parking_lot" version = "0.11.2" @@ -1417,6 +1496,60 @@ dependencies = [ "prost", ] +[[package]] +name = "quinn" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61a84d97630b137463c8e6802adc1dfe9de81457b41bb1ac59189e6761ab9255" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "fxhash", + "quinn-proto", + "quinn-udp", + "rustls", + "thiserror", + "tokio", + "tracing", + "webpki", +] + +[[package]] +name = "quinn-proto" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "063dedf7983c8d57db474218f258daa85b627de6f2dbc458b690a93b1de790e8" +dependencies = [ + "bytes", + "fxhash", + "rand", + "ring", + "rustls", + "rustls-native-certs", + "rustls-pemfile", + "slab", + "thiserror", + "tinyvec", + "tracing", + "webpki", +] + +[[package]] +name = "quinn-udp" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f7996776e9ee3fc0e5c14476c1a640a17e993c847ae9c81191c2c102fbef903" +dependencies = [ + "futures-util", + "libc", + "mio", + "quinn-proto", + "socket2", + "tokio", + "tracing", +] + [[package]] name = "quote" version = "1.0.14" @@ -1490,10 +1623,15 @@ dependencies = [ "console-subscriber", "const_format", "fdlimit", + "futures-util", "hex", "lazy_static", "notify", + "p12", + "quinn", "rand", + "rustls", + "rustls-pemfile", "serde", "sha2 0.10.1", "snowstorm", @@ -1506,6 +1644,16 @@ dependencies = [ "vergen", ] +[[package]] +name = "rc2" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48f197c283075d1345c20d5ad172526a7837882cdc998b1fcd2b2f3cfff1cb94" +dependencies = [ + "cipher", + "opaque-debug", +] + [[package]] name = "redox_syscall" version = "0.2.10" @@ -1550,6 +1698,21 @@ dependencies = [ "winapi", ] +[[package]] +name = "ring" +version = "0.16.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" +dependencies = [ + "cc", + "libc", + "once_cell", + "spin", + "untrusted", + "web-sys", + "winapi", +] + [[package]] name = "rustc_version" version = "0.3.3" @@ -1559,6 +1722,38 @@ dependencies = [ "semver", ] +[[package]] +name = "rustls" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d37e5e2290f3e040b594b1a9e04377c2c671f1a1cfd9bfdef82106ac1c113f84" +dependencies = [ + "ring", + "sct", + "webpki", +] + +[[package]] +name = "rustls-native-certs" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca9ebdfa27d3fc180e42879037b5338ab1c040c06affd00d8338598e7800943" +dependencies = [ + "openssl-probe", + "rustls-pemfile", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-pemfile" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5eebeaeb360c87bfb72e84abdb3447159c0eaececf1bef2aecd65a8be949d1c9" +dependencies = [ + "base64", +] + [[package]] name = "rustversion" version = "1.0.6" @@ -1596,6 +1791,16 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +[[package]] +name = "sct" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" +dependencies = [ + "ring", + "untrusted", +] + [[package]] name = "security-framework" version = "2.4.2" @@ -1668,6 +1873,17 @@ dependencies = [ "serde", ] +[[package]] +name = "sha-1" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "028f48d513f9678cda28f6e4064755b3fbb2af6acd672f2c209b62323f7aea0f" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest 0.10.1", +] + [[package]] name = "sha2" version = "0.9.9" @@ -1763,6 +1979,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + [[package]] name = "strsim" version = "0.10.0" @@ -2195,6 +2417,12 @@ dependencies = [ "subtle", ] +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + [[package]] name = "url" version = "2.2.2" @@ -2268,6 +2496,80 @@ version = "0.10.2+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" +[[package]] +name = "wasm-bindgen" +version = "0.2.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "632f73e236b219150ea279196e54e610f5dbafa5d61786303d4da54f84e47fce" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a317bf8f9fba2476b4b2c85ef4c4af8ff39c3c7f0cdfeed4f82c34a880aa837b" +dependencies = [ + "bumpalo", + "lazy_static", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d56146e7c495528bf6587663bea13a8eb588d39b36b679d83972e1a2dbbdacf9" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7803e0eea25835f8abdc585cd3021b3deb11543c6fe226dcd30b228857c5c5ab" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0237232789cf037d5480773fe568aac745bfe2afbc11a863e97901780a6b47cc" + +[[package]] +name = "web-sys" +version = "0.3.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38eb105f1c59d9eaa6b5cdc92b859d85b926e82cb2e0945cd0c9259faa6fe9fb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd" +dependencies = [ + "ring", + "untrusted", +] + [[package]] name = "which" version = "4.2.2" @@ -2321,6 +2623,12 @@ dependencies = [ "zeroize", ] +[[package]] +name = "yasna" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e262a29d0e61ccf2b6190d7050d4b237535fc76ce4c1210d9caa316f71dffa75" + [[package]] name = "zeroize" version = "1.3.0" diff --git a/Cargo.toml b/Cargo.toml index eb873ca1..e019ae13 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,7 @@ build = "build.rs" include = ["src/**/*", "LICENSE", "README.md", "build.rs"] [features] -default = ["server", "client", "tls", "noise", "hot-reload"] +default = ["server", "client", "tls", "noise", "quic", "hot-reload"] # Run as a server server = [] @@ -21,6 +21,9 @@ client = [] tls = ["tokio-native-tls"] # Noise support noise = ["snowstorm", "base64"] +#QUIC support +quic = ["quinn", "rustls", "rustls-pemfile", "p12", "futures-util"] + # Configuration hot-reload support hot-reload = ["notify"] @@ -70,6 +73,11 @@ notify = { version = "5.0.0-pre.13", optional = true } console-subscriber = { version = "0.1", optional = true, features = ["parking_lot"] } const_format = "0.2" atty = "0.2" +quinn = { version = "0.8.0", optional = true} +rustls = { version = "*", default-features = false, features = ["quic"], optional = true } +rustls-pemfile = { version = "*", optional = true } +p12 = { version = "0.4.0", optional = true } +futures-util = { version = "*", optional = true} [build-dependencies] vergen = { version = "6.0", default-features = false, features = ["build", "git", "cargo"] } diff --git a/examples/quic/ca.pfx b/examples/quic/ca.pfx new file mode 100644 index 0000000000000000000000000000000000000000..a6bf739d4927189710ec8776dd0c4eb498af1152 GIT binary patch literal 2634 zcma);X*d)L7sqGDj4>Gdnr$p8`-~YOll77%rtFjbW^7|i?rV){EMw1J7+E5UlI$8} zr;uGpqKQZ%lcmt>KJR_*ecn&+`{A7Poc}rh^YQl_B$?F+1Y|;zVOz{F`FP{_Jva~w zEGEO2z+~9Mi9CxWL!SQE2vPzjLkdsiypzabX8X&<&I$w-lOeh%A{OcKw*vx4;*lEv zwCqR)FdsIgygsgnlkn8P4aJF^#nFiS4Jv5yi_Y1{E%2Z!`)R~nOw?+h)aYT zA7RVqaav*ilInODB{_>!ej>kL{j~Wo<0EhI%*+G85>ylTD;Em=hvi=6GS^K=NT3ES(TS=`wW$ObMCt&1&X7U$3*H zcytMB(iL!RBJjn|9@GAa%_DBcvnvCO6;wgxo~$AZbbi;6=4NOFpe4I-;(6vH3_hEB z73x2MwUokCjOvQJAabZjLULhbQEtdEDK3w-TT|!{^~z*#;j0-B-()Cr2l? zot1X8U__={FxY{bCtnjhwQ*+kEpTaJ*Iiv8XJdm%fBB1|0aIjT>-waV)NiFZvzflq z)T1@o&upfj_fx6nl#^x?v6U;gF7uK>I4Qd0+i7309L=q49ypW%8E2Je;|m>9MCY*?mEo(91dm~#oZn0t$f@ky>%6HXCSY%;;jVXWasq-n=!E5hHzY%0oRnk5j z4@^E=QZlUduz0_Ho9$CI+l3-{@~WEIx&%c9y3;QoP^uvpq%-x$zV@n%V+>tC({6`z zBKVk+JX4VopSOJ*p^@%H-T!P5BE8q=ikaC1@0)f(hc{FYHb*}2{1~w6{q)7SPvG5} z109V;>Hi76zn}`^Lve|70D=Joz>SlP09*sOAtnDA^P~8{Jl5WR1O)0F26awd4TC}> z)lf(>_@5;xb1@mrb0TqofPj+%{o4)&{x47qYH(^;?>yKVV}xT=f{pdlrH+mMA5bff zx;J*J;+R%1&|j$u#YdCKOzAREFIf;Bb;f1_|Ezt?V5;4n=JZJR^r7DO=BV+gUZbj7 z_d6B}f#Z#nNeud{6y3vY{i zRpM}h$sHHDvXV^su;%v?!!J_$0~`l_^Ms$euKi;wtp95$a+wwEm{7`8v^vVHHcb

zrWqFDD>8aGeD1!Z4r9yBqws=>xDZxT`-hfI&6sKXf+L+Tr;{}zV?|Li2W_c(81@Ej zF#k%^cBd~CDkyV*CBJBA;ZzRNDH^t7rDD=79QZ=|)$*az_ucM3{&-G&PU7IW4Zf`3 zaMk+0&1+Z)AyCqdR4aXRkRd1~U~XDqIiMwbJm}tVHsF3r>Le)#ZM@vrAwSWjiih=J z;=6Uer8_Fo78w~qlTJ!<*SaTfPS`m-{&Yz;`T)dzYB5$|DAwaB4siq*G5@d^Lkh2? zCi`ot%L*6RJxr-TEeg)5Zn0HrA9A;_j0il{o`68cVVI9#7wgFvC7M4MgfEJ}LxY98 zr*$u>P(?4NJ+$)I{#L;~bTLG_pYGW_g}nkx)8MpppDWJqR7-4%t<%)bb`ih>3YKnXuv&{d^B{VajRAwKHymQU=N zVn&bIlDyuk=up(QrEJ@iK;vZ=DYJVe*w!1hrM$g}hSq3JU%OsxF>&rW*s`;F&dG1E zk$a0~6-)B(foN;F76N%<$&~iC^5Med=}&sEwIX`o|H~$_YpKCbr*C4k18L={PTymm{fab4qLA>v z|0@s>0stfOglz}$uB?L2>)SfL;^S&N)KT$H@Y{)8`;@JScjkC7AJvDaK$Pt>co+2h Qzye~AM~*0N|0fdu4ZIYiEdT%j literal 0 HcmV?d00001 diff --git a/examples/quic/client.toml b/examples/quic/client.toml new file mode 100644 index 00000000..02e5d0bb --- /dev/null +++ b/examples/quic/client.toml @@ -0,0 +1,12 @@ +[client] +remote_addr = "localhost:2333" +default_token = "123" + +[client.transport] +type = "quic" +[client.transport.quic] +trusted_root = "example/quic/test_ca.pem" +hostname = "testserver" + +[client.services.foo1] +local_addr = "127.0.0.1:80" diff --git a/examples/quic/server.toml b/examples/quic/server.toml new file mode 100644 index 00000000..f78a15e5 --- /dev/null +++ b/examples/quic/server.toml @@ -0,0 +1,12 @@ +[server] +bind_addr = "0.0.0.0:2333" +default_token = "123" + +[server.transport] +type = "quic" +[server.transport.quic] +pkcs12 = "example/quic/test_server.pfx" +pkcs12_password = "1234" + +[server.services.foo1] +bind_addr = "0.0.0.0:5202" diff --git a/examples/quic/test_ca.pem b/examples/quic/test_ca.pem new file mode 100644 index 00000000..8395bca4 --- /dev/null +++ b/examples/quic/test_ca.pem @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDBzCCAe+gAwIBAgIEYdn6bDANBgkqhkiG9w0BAQsFADASMRAwDgYDVQQDDAdU +ZXN0IENBMCAXDTIyMDEwODIwNTYxMloYDzIxMjEwMTA4MjA1NjEyWjASMRAwDgYD +VQQDDAdUZXN0IENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0T5h +iSVIVmbsX062PgWb4cQC2sOeB7YXHoYPh41upB5DozRIBpZ87GFFvtS5PrYbf1E7 +9n7iVhay0Sh/xSH84pZi+Akh8RvfU2JIJAqPce/tdA+pLo6k1l9XuZyM0Bc5DVu/ +9r7wkD1tVgvgar5RV0yYovZAJk7C2IE3owRRog/0VOJPRSLNJoUl/1C2KUznrtlD +ojTBE8UDY3uHC7t9A226EJiH9cwf8iUhGqzDmi1FinPptPFg3UA7YkNYPt2zozoW +neFasEDHwhOo9eYjPUbS5hjaH2COxFiISmSiq7lxBxrIUq+dchkI5VzwCycla55d +lJonUPadS/AtIvIVvQIDAQABo2MwYTAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB +/wQEAwIBBjAfBgNVHSMEGDAWgBTURLbYs6qjLPcJ7CaOa/0xhIcm5jAdBgNVHQ4E +FgQU1ES22LOqoyz3Cewmjmv9MYSHJuYwDQYJKoZIhvcNAQELBQADggEBAEvzt38H +Z3e6qJyKh4XdADxQCdpeykDAmAGEGWpDmNK5cqAI7XSyZ2X7NDrkLCWyf2S+GpCH +QU8y1dQm5zZ4FdvsoCbcH9RCBXn6xPsQS3VIdCWAMKcJQoRkZDXUSCGyC6tY/VK0 +j+1KIcBpAM2ZjlmG9r5Te4o1M0WAyXiYGuXv3uZ1QOfATa71UKxEO4HmIYg199jy +W0/JNKBUStCK6m2em41Y/X6G2e6ZJVyB4ma0qMaNJp3IAjgiG+luvmK6AsNzoy3s +J5drYWrErtQSWOx9sK84v9yBzZ9psgF6Wm7NbGVcRjBmanYrXLNmwwgKRGFWMyg8 +rwvAd2jZnsmpVLQ= +-----END CERTIFICATE----- diff --git a/examples/quic/test_server.cert.pem b/examples/quic/test_server.cert.pem new file mode 100644 index 00000000..48da65b2 --- /dev/null +++ b/examples/quic/test_server.cert.pem @@ -0,0 +1,45 @@ +Bag Attributes + friendlyName: test server + localKeyID: 54 69 6D 65 20 31 36 34 31 36 37 35 35 33 37 38 34 32 +subject=CN = Test Server + +issuer=CN = Test CA + +-----BEGIN CERTIFICATE----- +MIIDJDCCAgygAwIBAgIEYdn6xzANBgkqhkiG9w0BAQsFADASMRAwDgYDVQQDDAdU +ZXN0IENBMB4XDTIyMDEwODIwNTc0M1oXDTIzMDEwODIwNTc0M1owFjEUMBIGA1UE +AwwLVGVzdCBTZXJ2ZXIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCf +tkfv9IKlVLbYiOF94n4bFM1xvoreB4ReNnvVH7qpsYMpsYopVUScwYmEqZLDsmR7 +DLIxc3PDqtNGWdrAzyOcLRC9ejw8hDddhN8XyCFRGJ3e3hmOSbuFXm6G/gHpB9xA +AehMHWmYYoAgHkIf0Hf3qsnLTy23E7P809oZ6hQGCQlpHWqLol6hMO0esAnYDXZQ +Y92PZ8qH4C5r8a40l0uex1nQB2wygEq7mEx2gscNkwg38H7cuHDBlDWWZ2j4/Tiz +kpNhKMaNvrAtflMnXbRGS3Nc7iX2pdiSl9w+AYVLueM3Ctuhu1CXkyhHPbpYzrW4 +hOhDGs5v91PT7EPgeWYNAgMBAAGjfjB8MA4GA1UdDwEB/wQEAwIFoDAVBgNVHREE +DjAMggp0ZXN0c2VydmVyMB8GA1UdIwQYMBaAFNREttizqqMs9wnsJo5r/TGEhybm +MB0GA1UdDgQWBBQTaoQi9csuSm8KXvog5xSRgGl7yzATBgNVHSUEDDAKBggrBgEF +BQcDATANBgkqhkiG9w0BAQsFAAOCAQEARUb1HYkv3Ned+a8jk0ULcQsnurxCVsvn +DQQASs72vmrV1wAd+B/VgjzTAc95EMvLdbhtDhx0OsYh3955eyRvFIBJ2ZdoL69m +gpR9flXL4V/zd93RyNak7D2/zIP5zFiVGWlpht+WfMKuPEyRd3vwNxHAAG4B6TRp +hTpxTmhPtsQHGn6/SlN8QpCXI579cX7/yCUMK5pXR+A/crkI4UWE0YX/U8KqWXiD +y2pKQ139uMBArRSfo3Z8RD6zxqsCvGQcBKGwb7bwBXJwgTGJAHXte2QnFkoJ02H9 +peeYI5MqAw4+iddzcyyxna2jLqJsFUhgnam4iwA60fxtV95rcKCsMQ== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIDBzCCAe+gAwIBAgIEYdn6bDANBgkqhkiG9w0BAQsFADASMRAwDgYDVQQDDAdU +ZXN0IENBMCAXDTIyMDEwODIwNTYxMloYDzIxMjEwMTA4MjA1NjEyWjASMRAwDgYD +VQQDDAdUZXN0IENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0T5h +iSVIVmbsX062PgWb4cQC2sOeB7YXHoYPh41upB5DozRIBpZ87GFFvtS5PrYbf1E7 +9n7iVhay0Sh/xSH84pZi+Akh8RvfU2JIJAqPce/tdA+pLo6k1l9XuZyM0Bc5DVu/ +9r7wkD1tVgvgar5RV0yYovZAJk7C2IE3owRRog/0VOJPRSLNJoUl/1C2KUznrtlD +ojTBE8UDY3uHC7t9A226EJiH9cwf8iUhGqzDmi1FinPptPFg3UA7YkNYPt2zozoW +neFasEDHwhOo9eYjPUbS5hjaH2COxFiISmSiq7lxBxrIUq+dchkI5VzwCycla55d +lJonUPadS/AtIvIVvQIDAQABo2MwYTAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB +/wQEAwIBBjAfBgNVHSMEGDAWgBTURLbYs6qjLPcJ7CaOa/0xhIcm5jAdBgNVHQ4E +FgQU1ES22LOqoyz3Cewmjmv9MYSHJuYwDQYJKoZIhvcNAQELBQADggEBAEvzt38H +Z3e6qJyKh4XdADxQCdpeykDAmAGEGWpDmNK5cqAI7XSyZ2X7NDrkLCWyf2S+GpCH +QU8y1dQm5zZ4FdvsoCbcH9RCBXn6xPsQS3VIdCWAMKcJQoRkZDXUSCGyC6tY/VK0 +j+1KIcBpAM2ZjlmG9r5Te4o1M0WAyXiYGuXv3uZ1QOfATa71UKxEO4HmIYg199jy +W0/JNKBUStCK6m2em41Y/X6G2e6ZJVyB4ma0qMaNJp3IAjgiG+luvmK6AsNzoy3s +J5drYWrErtQSWOx9sK84v9yBzZ9psgF6Wm7NbGVcRjBmanYrXLNmwwgKRGFWMyg8 +rwvAd2jZnsmpVLQ= +-----END CERTIFICATE----- diff --git a/examples/quic/test_server.key.pem b/examples/quic/test_server.key.pem new file mode 100644 index 00000000..8a031da6 --- /dev/null +++ b/examples/quic/test_server.key.pem @@ -0,0 +1,32 @@ +Bag Attributes + friendlyName: test server + localKeyID: 54 69 6D 65 20 31 36 34 31 36 37 35 35 33 37 38 34 32 +Key Attributes: +-----BEGIN PRIVATE KEY----- +MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCftkfv9IKlVLbY +iOF94n4bFM1xvoreB4ReNnvVH7qpsYMpsYopVUScwYmEqZLDsmR7DLIxc3PDqtNG +WdrAzyOcLRC9ejw8hDddhN8XyCFRGJ3e3hmOSbuFXm6G/gHpB9xAAehMHWmYYoAg +HkIf0Hf3qsnLTy23E7P809oZ6hQGCQlpHWqLol6hMO0esAnYDXZQY92PZ8qH4C5r +8a40l0uex1nQB2wygEq7mEx2gscNkwg38H7cuHDBlDWWZ2j4/TizkpNhKMaNvrAt +flMnXbRGS3Nc7iX2pdiSl9w+AYVLueM3Ctuhu1CXkyhHPbpYzrW4hOhDGs5v91PT +7EPgeWYNAgMBAAECggEADnR1e1LCdks+B0gQPJAAwNu3omlP8Tt17/73YzktcElY +KTBf5FDK1nMvypl8ZojhTj++avpbimSOHappQZUd0HdFshh7ljCTQDwT4veiiE/1 +jePFJVsoBTCgSUh5DMnA1ew2RZlN4tRba0zByFZaXUiQXf3LEexPGH1mGn1UlZ0c +J66VzboW3ZiSMx5Mz0GdajjA1FNNv/zTmruFQEGsjAd2EjlC+43ID2IGikmRpRY0 +mXK5esjBru2qJUZqLjfdkHtV5YfgSzpu9pZgQq9UOVboTm0oFVrUL2wK6zbDlvNP +scbwNXPmK7vFgPOXNAOSDpL0wksW3gOJgUvCi6zBwwKBgQDPFpMGkJs1jCUsNLX2 +CKH7rpahF/hzbZHH717jqR7FWmkf2SLsu4bgNRo58H8QwdkZw2J6xUgUXqB8C8B0 +oye5NfokuF6I3wFGXqZcMGEmc9G71t7u91gUb4AAvGgETkTZzFZ8dCw4ofggonvR +PqLkqwCSdNx2z5j15tAR6kx2cwKBgQDFbyUYltaJlCGqcPq/3N4ERkDbqBdmU1EH +cnJ3K6N6AEMeNHd35fKBdR37NDN+Eu9a0mmXskNY1ugGf1d/+MfC2PJWqyFjKXm1 +o14RShYLJlmfH8t5yTUOQKXFvip8GpKNM0K2TMe2oRST/QOtxm4bPNGvHP6RPWL7 +8KNE9F8RfwKBgH+bqX2iHgIhGcbjtDynlSlBrBAYdUCrg+lv10jyLcPuslittJer +9rCyCDcruyDYUq9NdqGwb3od1Uaa9zzoTNIUMM/vzFELGf4C1QB5z2OietsEzNr0 +D5KIIphRgMcmc8bB44lNDPLY281AUovdzQKbXP7ig/eydM8SK6Tee7+BAoGACAjU +3qJMyr5/fDsqySII2u2s+ANoKF7dnkr3A4iAF5fpI1KJRhTSgJguhymBqvDEUtLb +PzQe73+XY6RNAEU0g+ZmPkaqjimC7XRfgJ6eNQfzf7lAg40/nnvdAyYQ/onqStq6 +LUcEnZcCil8yhiDcHDmmYtTwOyLfY1dQnZ7AO6sCgYAFa8PTiw9ygDhQcGlrzVeb +s+dv26zrlgZ4YDTd3/rvv4jRm7eMkjk0gcOfC6WuKpm0aKLajOYaQUbDE24DVztQ +X2Y8e7yBl250sSSiQf9T6mtvdWmdiTICnU20yIIl/C1fc5vANJ2BsIclKkoe6lhF +dPpxqoqYGy2kxF8fW8Kn9A== +-----END PRIVATE KEY----- diff --git a/examples/quic/test_server.pfx b/examples/quic/test_server.pfx new file mode 100644 index 0000000000000000000000000000000000000000..c7184ae827c5cff46aa56caac85c914009ffdecd GIT binary patch literal 3237 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 { + #[cfg(feature = "quic")] + { + let mut client = Client::::from(config).await?; + client.run(shutdown_rx, service_rx).await + } + #[cfg(not(feature = "quic"))] + crate::helper::feature_not_compile("quic") + } } } diff --git a/src/config.rs b/src/config.rs index 7304e421..449712d7 100644 --- a/src/config.rs +++ b/src/config.rs @@ -14,6 +14,8 @@ pub enum TransportType { Tls, #[serde(rename = "noise")] Noise, + #[serde(rename = "quic")] + Quic, } impl Default for TransportType { @@ -88,6 +90,8 @@ pub struct TlsConfig { pub trusted_root: Option, pub pkcs12: Option, pub pkcs12_password: Option, + pub pem_server_key: Option, + pub pem_server_cert: Option, } fn default_noise_pattern() -> String { @@ -129,6 +133,7 @@ pub struct TransportConfig { pub keepalive_interval: u64, pub tls: Option, pub noise: Option, + pub quic: Option, // reuse TLSconfig since QUIC uses TLS1.3 } impl Default for TransportConfig { @@ -140,6 +145,7 @@ impl Default for TransportConfig { keepalive_interval: default_keepalive_interval(), tls: None, noise: None, + quic: None } } } @@ -228,6 +234,29 @@ impl Config { Ok(()) } + fn validate_tls_config(tls_config: &TlsConfig, is_server: bool, is_quic: bool) -> Result<()>{ + if is_server { + if tls_config.pem_server_key.is_some() { + if !is_quic { + bail!("`pem_server_key` and `pem_server_cert` are not yet supported for TLS") + } + tls_config.pem_server_cert.as_ref().ok_or( + anyhow!("`pem_server_key` provided but `pem_server_cert` is missing"))?; + } else { + tls_config + .pkcs12 + .as_ref() + .and(tls_config.pkcs12_password.as_ref()) + .ok_or(anyhow!("Missing `pkcs12` or `pkcs12_password`"))?; + } + } else { + tls_config + .trusted_root + .as_ref() + .ok_or(anyhow!("Missing `trusted_root`"))?; + } + Ok(()) + } fn validate_transport_config(config: &TransportConfig, is_server: bool) -> Result<()> { match config.transport_type { TransportType::Tcp => Ok(()), @@ -236,19 +265,14 @@ impl Config { .tls .as_ref() .ok_or(anyhow!("Missing TLS configuration"))?; - if is_server { - tls_config - .pkcs12 - .as_ref() - .and(tls_config.pkcs12_password.as_ref()) - .ok_or(anyhow!("Missing `pkcs12` or `pkcs12_password`"))?; - } else { - tls_config - .trusted_root - .as_ref() - .ok_or(anyhow!("Missing `trusted_root`"))?; - } - Ok(()) + Config::validate_tls_config(tls_config, is_server, false) + } + TransportType::Quic => { + let tls_config = config + .quic + .as_ref() + .ok_or(anyhow!("Missing QUIC configuration"))?; + Config::validate_tls_config(tls_config, is_server, true) } TransportType::Noise => { // The check is done in transport diff --git a/src/lib.rs b/src/lib.rs index 481200a2..19391dc2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -111,7 +111,9 @@ pub async fn run(args: Cli, shutdown_rx: broadcast::Receiver) -> Result<() } let _ = shutdown_tx.send(true); - + if let Some((h,_)) = last_instance{ + h.await?; + } Ok(()) } diff --git a/src/server.rs b/src/server.rs index 051a856d..2bbb523e 100644 --- a/src/server.rs +++ b/src/server.rs @@ -26,6 +26,8 @@ use tracing::{debug, error, info, info_span, instrument, warn, Instrument, Span} use crate::transport::NoiseTransport; #[cfg(feature = "tls")] use crate::transport::TlsTransport; +#[cfg(feature = "quic")] +use crate::transport::QuicTransport; type ServiceDigest = protocol::Digest; // SHA256 of a service name type Nonce = protocol::Digest; // Also called `session_key` @@ -71,6 +73,15 @@ pub async fn run_server( #[cfg(not(feature = "noise"))] crate::helper::feature_not_compile("noise") } + TransportType::Quic => { + #[cfg(feature = "quic")] + { + let mut server = Server::::from(config).await?; + server.run(shutdown_rx, service_rx).await?; + } + #[cfg(not(feature = "tls"))] + crate::helper::feature_not_compile("tls") + } } Ok(()) @@ -122,7 +133,7 @@ impl<'a, T: 'static + Transport> Server<'a, T> { mut service_rx: mpsc::Receiver, ) -> Result<()> { // Listen at `server.bind_addr` - let l = self + let mut l = self .transport .bind(&self.config.bind_addr) .await @@ -140,7 +151,7 @@ impl<'a, T: 'static + Transport> Server<'a, T> { loop { tokio::select! { // Wait for incoming control and data channels - ret = self.transport.accept(&l) => { + ret = self.transport.accept(&mut l) => { match ret { Err(err) => { // Detects whether it's an IO error @@ -189,7 +200,7 @@ impl<'a, T: 'static + Transport> Server<'a, T> { }, // Wait for the shutdown signal _ = shutdown_rx.recv() => { - info!("Shuting down gracefully..."); + info!("Shutting down gracefully..."); break; }, e = service_rx.recv() => { @@ -200,8 +211,9 @@ impl<'a, T: 'static + Transport> Server<'a, T> { } } + // Some transports uses async functions for closing gracefully (QUIC) + self.transport.close(l).await; info!("Shutdown"); - Ok(()) } diff --git a/src/transport/mod.rs b/src/transport/mod.rs index 8660c6ca..f60dd979 100644 --- a/src/transport/mod.rs +++ b/src/transport/mod.rs @@ -27,9 +27,10 @@ pub trait Transport: Debug + Send + Sync { /// Provide the transport with socket options, which can be handled at the need of the transport fn hint(conn: &Self::Stream, opts: SocketOpts); async fn bind(&self, addr: T) -> Result; - async fn accept(&self, a: &Self::Acceptor) -> Result<(Self::RawStream, SocketAddr)>; + async fn accept(&self, a: &mut Self::Acceptor) -> Result<(Self::RawStream, SocketAddr)>; async fn handshake(&self, conn: Self::RawStream) -> Result; async fn connect(&self, addr: &str) -> Result; + async fn close(&self, a: Self::Acceptor); } mod tcp; @@ -44,6 +45,11 @@ mod noise; #[cfg(feature = "noise")] pub use noise::NoiseTransport; +#[cfg(feature = "quic")] +mod quic; +#[cfg(feature = "quic")] +pub use quic::QuicTransport; + #[derive(Debug, Clone, Copy)] struct Keepalive { // tcp_keepalive_time if the underlying protocol is TCP diff --git a/src/transport/noise.rs b/src/transport/noise.rs index c9a3e820..2fe5948f 100644 --- a/src/transport/noise.rs +++ b/src/transport/noise.rs @@ -77,7 +77,7 @@ impl Transport for NoiseTransport { Ok(TcpListener::bind(addr).await?) } - async fn accept(&self, a: &Self::Acceptor) -> Result<(Self::RawStream, SocketAddr)> { + async fn accept(&self, a: &mut Self::Acceptor) -> Result<(Self::RawStream, SocketAddr)> { self.tcp .accept(a) .await @@ -103,4 +103,7 @@ impl Transport for NoiseTransport { .with_context(|| "Failed to do noise handshake")?; return Ok(conn); } + + async fn close(&self, _: Self::Acceptor) { + } } diff --git a/src/transport/quic.rs b/src/transport/quic.rs new file mode 100644 index 00000000..7efac614 --- /dev/null +++ b/src/transport/quic.rs @@ -0,0 +1,315 @@ +use std::borrow::{BorrowMut}; +use std::fmt::{Debug, Formatter}; +use std::io::{Error, IoSlice}; +use std::net::SocketAddr; +use std::pin::Pin; +use std::sync::Arc; +use std::task::Poll; +use std::time::Duration; + +use super::Transport; +use crate::config::{TlsConfig, TransportConfig}; +use anyhow::{anyhow, Context, Result}; +use async_trait::async_trait; +use futures_util::{StreamExt}; +use p12::PFX; +use quinn::{Connecting, Connection, Endpoint, EndpointConfig, Incoming}; +use rustls::{ConfigBuilder, ServerConfig}; +use rustls_pemfile::{Item, read_one, read_all}; +use rustls::server::WantsServerCert; +use tokio::fs; +use tokio::io::{AsyncWrite, ReadBuf}; +use tokio::net::{ToSocketAddrs, UdpSocket}; +use tokio_native_tls::native_tls::Certificate; +use crate::transport::SocketOpts; + +pub const ALPN_QUIC_TUNNEL: &[&[u8]] = &[b"qt"]; + +pub struct QuicTransport { + config: TlsConfig, + keepalive_interval: u64, + client_crypto: Option, +} + +impl Debug for QuicTransport { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + let client_crypto = &self.client_crypto.as_ref().map(|_| "ClientConfig{}"); + + f.debug_struct("QuicTransport") + .field("config", &self.config) + .field("client_crypto", client_crypto) + .finish() + } +} + +#[derive(Debug)] +pub struct QuicBiStream { + send: quinn::SendStream, + recv: quinn::RecvStream, + conn: Connection, +} + +impl QuicBiStream { + fn new((send, recv): (quinn::SendStream, quinn::RecvStream), conn: Connection) -> Self { + Self { send, recv, conn} + } +} + +impl tokio::io::AsyncRead for QuicBiStream { + fn poll_read( + self: Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + buf: &mut ReadBuf<'_>, + ) -> Poll> { + Pin::new(self.get_mut().recv.borrow_mut()).poll_read(cx, buf) + } +} + +impl AsyncWrite for QuicBiStream { + fn poll_write( + self: Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + buf: &[u8], + ) -> Poll> { + Pin::new(self.get_mut().send.borrow_mut()).poll_write(cx, buf) + } + + fn poll_flush( + self: Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + ) -> Poll> { + Pin::new(self.get_mut().send.borrow_mut()).poll_flush(cx) + } + + fn poll_shutdown( + self: Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + ) -> Poll> { + Pin::new(self.get_mut().send.borrow_mut()).poll_shutdown(cx) + } + + fn poll_write_vectored( + self: Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + bufs: &[IoSlice<'_>], + ) -> Poll> { + Pin::new(self.get_mut().send.borrow_mut()).poll_write_vectored(cx, bufs) + } + + fn is_write_vectored(&self) -> bool { + self.send.is_write_vectored() + } +} + +pub struct QuicAcceptor(Endpoint, Incoming); + +impl From for Endpoint { + fn from(a: QuicAcceptor) -> Self { + a.0 + } +} + +impl Drop for QuicBiStream { + fn drop(&mut self) { + self.conn.close(0u8.into(), &[]); + } +} + +async fn read_server_pkcs12(config: &TlsConfig, server_crypto: ConfigBuilder) -> Result { + let buf = fs::read(config.pkcs12.as_ref() + .with_context(|| "Config `quic.pkcs12` was not provided")?) + .await + .with_context(|| "Failed to read the `quic.pkcs12`")?; + + let pfx = PFX::parse(buf.as_slice()).with_context(|| "Failed to parse `quic.pkcs12`")?; + + let keys = pfx.key_bags(config.pkcs12_password.as_ref() + .with_context(|| "Expected `quic.pkcs12_password` value to be set")?) + .with_context(|| "Could not decrypt `quic.pkcs12 with `quic.pkcs12_password`")?; + + let certs = pfx.cert_bags(config.pkcs12_password.as_ref() + .with_context(|| "Config `quic.pkcs12_password` was not provided")?) + .with_context(|| "Could not decrypt `quic.pkcs12` using `quic.pkcs12_password`")?; + + let chain: Vec = certs + .into_iter() + .map(rustls::Certificate) + .collect(); + let key = rustls::PrivateKey(keys.into_iter().next() + .with_context(|| "No keys found in `quic.pkcs12`")?); + server_crypto.with_single_cert(chain, key) + .with_context(|| "Server keys invalid") +} + +async fn read_server_pem(config: &TlsConfig, server_crypto: ConfigBuilder) -> Result { + // unwrap, since caller should have checked that pem_server_key exists + let buf = fs::read(config.pem_server_key.as_ref().unwrap()) + .await + .with_context(|| "Failed to read the `quic.pem_server_key`")?; + let mut bufr = std::io::BufReader::new(buf.as_slice()); + let key: Vec = match read_one(&mut bufr).with_context(|| "Could not parse `quic.pem_server_key`")? { + None => {Err(anyhow!("`quic.pem_server_key` contained no keys"))} + Some(item) => { match item { + Item::X509Certificate(_) => { Err(anyhow!("`quic.pem_server_key` should contain keys, not certificates"))} + Item::RSAKey(der_key) => {Ok(der_key)} + Item::PKCS8Key(der_key) => {Ok(der_key)} + }}}?; + + let buf = fs::read(config.pem_server_cert.as_ref() + .with_context(|| "`quic.pem_server_key` was provided, yet `quic.pem_server_cert` is missing")?) + .await + .with_context(|| "Failed to read the `quic.pem_server_cert`")?; + + let mut bufr = std::io::BufReader::new(buf.as_slice()); + let certs: Result>> = read_all(&mut bufr).with_context(|| "Could not parse `quic.pem_server_cert`")? + .into_iter().map(|item| match item { + Item::X509Certificate(der_cert) => { Ok(der_cert)} + Item::RSAKey(_) | Item::PKCS8Key(_) => { + Err(anyhow!("`quic.pem_server_cert` should contain certificates, not keys"))} + }) + .collect(); + + let certs = certs?; + if certs.is_empty() { + return Err(anyhow!("`quic.pem_server_cert` contained no certs")); + } + + let chain: Vec = certs + .into_iter() + .map(rustls::Certificate) + .collect(); + let key = rustls::PrivateKey(key); + + server_crypto.with_single_cert(chain, key) + .with_context(|| "Server keys invalid") +} + +#[async_trait] +impl Transport for QuicTransport { + type Acceptor = QuicAcceptor; + type RawStream = Connecting; + type Stream = QuicBiStream; + + fn new(config: &TransportConfig) -> Result { + let tls_config = match &config.quic { + Some(v) => v, + None => { + return Err(anyhow!("Missing quic config: {:?}", config)); + } + }; + + let client_crypto = match tls_config.trusted_root.as_ref() { + Some(path) => { + let s = std::fs::read_to_string(path) + .with_context(|| "Failed to read the `quic.trusted_root`")?; + let cert = Certificate::from_pem(s.as_bytes()) + .with_context(|| "Failed to read certificate from `quic.trusted_root`")?; + + let mut roots = rustls::RootCertStore::empty(); + + roots.add(&rustls::Certificate( + cert.to_der() + .with_context(|| "could not encode trust root as DER")?, + )).with_context(|| "adding trusted root cert to trust store")?; + + let mut client_crypto = rustls::ClientConfig::builder() + .with_safe_defaults() + .with_root_certificates(roots) + .with_no_client_auth(); + client_crypto.alpn_protocols = ALPN_QUIC_TUNNEL.iter().map(|&x| x.into()).collect(); + Some(client_crypto) + } + None => None, + }; + + Ok(QuicTransport { + keepalive_interval: config.keepalive_interval, + config: tls_config.clone(), + client_crypto, + }) + } + + fn hint(_: &Self::Stream, _: SocketOpts) { + // keepalive must be set when connection is initiated. nothing to do... + } + + async fn bind(&self, addr: A) -> Result { + let server_crypto = rustls::ServerConfig::builder() + .with_safe_defaults() + .with_no_client_auth(); + let mut server_crypto = match self.config.pem_server_key { + None => {read_server_pkcs12(&self.config, server_crypto).await?} + Some(_) => {read_server_pem(&self.config, server_crypto).await?} + }; + + server_crypto.alpn_protocols = ALPN_QUIC_TUNNEL.iter().map(|&x| x.into()).collect(); + + let mut server_config = quinn::ServerConfig::with_crypto(Arc::new(server_crypto)); + Arc::get_mut(&mut server_config.transport) + .unwrap() + .datagram_receive_buffer_size(Some(65536)) + .datagram_send_buffer_size(65536) + .max_idle_timeout(Some(Duration::from_secs(self.keepalive_interval * 3).try_into()?)); + + server_config.use_retry(true); + let socket = UdpSocket::bind(addr).await?.into_std()?; + quinn::Endpoint::new(EndpointConfig::default(), Some(server_config), socket) + .with_context(|| "Failed to start server") + .map(|(e, i)| QuicAcceptor(e, i)) + } + + + async fn accept(&self, a: &mut Self::Acceptor) -> Result<(Self::RawStream, SocketAddr)> { + if let Some(connecting) = a.1.next().await { + let addr = connecting.remote_address(); + return Ok((connecting, addr)); + } + Err(anyhow!("endpoint closed")) + } + + async fn handshake(&self, conn: Self::RawStream) -> Result { + let mut new_connection = conn.await?; // handshake happens here + // Connection is established now. Wait for client to open a bidirectional stream + if let Some(bi_stream_result) = new_connection.bi_streams.next().await { + Ok(Self::Stream::new(bi_stream_result?, new_connection.connection)) + } else { + anyhow::bail!("connection closed before bi stream could be established") + } + } + + async fn connect(&self, addr: &str) -> Result { + let mut endpoint = quinn::Endpoint::client("[::]:0".parse().unwrap()) + .with_context(|| "could not open client socket")?; + let mut config = + quinn::ClientConfig::new(Arc::new(self.client_crypto.as_ref().unwrap().clone())); + // server times out after 10 sec, so send keepalive every 5 sec + Arc::get_mut(&mut config.transport) + .unwrap() + .keep_alive_interval(Some(Duration::from_secs(self.keepalive_interval))) + .datagram_receive_buffer_size(Some(65536)) + .datagram_send_buffer_size(65536); + endpoint.set_default_client_config(config); + let connecting = endpoint.connect( + addr.parse().with_context(|| "server address not valid")?, + self.config + .hostname + .as_ref() + .unwrap_or(&String::from(addr.split(':').next().unwrap())), + )?; + let new_conn = connecting.await?; + Ok(QuicBiStream::new(new_conn.connection.open_bi().await?, new_conn.connection)) + } + + async fn close(&self, a: Self::Acceptor) { + let e: Endpoint = a.into(); // drops Incoming + e.close(0u8.into(), &[]); + // wait for all connections to signal close as per spec + // See https://github.com/quinn-rs/quinn/issues/1102 + // this may take a couple of seconds unfortunately, but without it we are not + // guaranteed that the socket is released when we exit this method. + e.wait_idle().await; + drop(e); + } +} + + diff --git a/src/transport/tcp.rs b/src/transport/tcp.rs index 6895e077..8d41efa4 100644 --- a/src/transport/tcp.rs +++ b/src/transport/tcp.rs @@ -31,7 +31,7 @@ impl Transport for TcpTransport { Ok(TcpListener::bind(addr).await?) } - async fn accept(&self, a: &Self::Acceptor) -> Result<(Self::RawStream, SocketAddr)> { + async fn accept(&self, a: &mut Self::Acceptor) -> Result<(Self::RawStream, SocketAddr)> { let (s, addr) = a.accept().await?; self.socket_opts.apply(&s); Ok((s, addr)) @@ -46,4 +46,7 @@ impl Transport for TcpTransport { self.socket_opts.apply(&s); Ok(s) } + + async fn close(&self, _: Self::Acceptor) { + } } diff --git a/src/transport/tls.rs b/src/transport/tls.rs index 8d4eb6fd..89e29f97 100644 --- a/src/transport/tls.rs +++ b/src/transport/tls.rs @@ -79,7 +79,7 @@ impl Transport for TlsTransport { Ok(l) } - async fn accept(&self, a: &Self::Acceptor) -> Result<(Self::RawStream, SocketAddr)> { + async fn accept(&self, a: &mut Self::Acceptor) -> Result<(Self::RawStream, SocketAddr)> { self.tcp .accept(a) .await @@ -105,4 +105,7 @@ impl Transport for TlsTransport { ) .await?) } + + async fn close(&self, _: Self::Acceptor) { + } } diff --git a/tests/for_tcp/quic_transport.toml b/tests/for_tcp/quic_transport.toml new file mode 100644 index 00000000..b82d21ba --- /dev/null +++ b/tests/for_tcp/quic_transport.toml @@ -0,0 +1,29 @@ +[client] +remote_addr = "127.0.0.1:2333" +default_token = "default_token_if_not_specify" + +[client.transport] +type = "quic" +[client.transport.quic] +trusted_root = "examples/quic/test_ca.pem" +hostname = "testserver" + +[client.services.echo] +local_addr = "127.0.0.1:8080" +[client.services.pingpong] +local_addr = "127.0.0.1:8081" + +[server] +bind_addr = "0.0.0.0:2333" +default_token = "default_token_if_not_specify" + +[server.transport] +type = "quic" +[server.transport.quic] +pkcs12 = "examples/quic/test_server.pfx" +pkcs12_password = "1234" + +[server.services.echo] +bind_addr = "0.0.0.0:2334" +[server.services.pingpong] +bind_addr = "0.0.0.0:2335" diff --git a/tests/for_udp/quic_transport.toml b/tests/for_udp/quic_transport.toml new file mode 100644 index 00000000..0fc77a7c --- /dev/null +++ b/tests/for_udp/quic_transport.toml @@ -0,0 +1,33 @@ +[client] +remote_addr = "127.0.0.1:2332" +default_token = "default_token_if_not_specify" + +[client.transport] +type = "quic" +[client.transport.quic] +trusted_root = "examples/quic/test_ca.pem" +hostname = "testserver" + +[client.services.echo] +type = "udp" +local_addr = "127.0.0.1:8080" +[client.services.pingpong] +type = "udp" +local_addr = "127.0.0.1:8081" + +[server] +bind_addr = "0.0.0.0:2332" +default_token = "default_token_if_not_specify" + +[server.transport] +type = "quic" +[server.transport.quic] +pem_server_key = "examples/quic/test_server.key.pem" +pem_server_cert = "examples/quic/test_server.cert.pem" + +[server.services.echo] +type = "udp" +bind_addr = "0.0.0.0:2334" +[server.services.pingpong] +type = "udp" +bind_addr = "0.0.0.0:2335" diff --git a/tests/integration_test.rs b/tests/integration_test.rs index 18115753..9be27843 100644 --- a/tests/integration_test.rs +++ b/tests/integration_test.rs @@ -59,6 +59,7 @@ async fn tcp() -> Result<()> { #[cfg(not(target_os = "macos"))] test("tests/for_tcp/tls_transport.toml", Type::Tcp).await?; test("tests/for_tcp/noise_transport.toml", Type::Tcp).await?; + test("tests/for_tcp/quic_transport.toml", Type::Tcp).await?; Ok(()) } @@ -86,6 +87,7 @@ async fn udp() -> Result<()> { #[cfg(not(target_os = "macos"))] test("tests/for_udp/tls_transport.toml", Type::Udp).await?; test("tests/for_udp/noise_transport.toml", Type::Udp).await?; + test("tests/for_udp/quic_transport.toml", Type::Udp).await?; Ok(()) } From 7f083c21b7f0f637b28337c48c74f43830488aa4 Mon Sep 17 00:00:00 2001 From: Emil Sauer Lynge Date: Mon, 17 Jan 2022 23:37:16 +0100 Subject: [PATCH 2/2] QUIC transport remove dependency on native-tls which may not be present --- src/transport/quic.rs | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/src/transport/quic.rs b/src/transport/quic.rs index 7efac614..2b9c51af 100644 --- a/src/transport/quic.rs +++ b/src/transport/quic.rs @@ -1,6 +1,7 @@ use std::borrow::{BorrowMut}; use std::fmt::{Debug, Formatter}; -use std::io::{Error, IoSlice}; +use std::fs::File; +use std::io::{BufReader, Error, IoSlice}; use std::net::SocketAddr; use std::pin::Pin; use std::sync::Arc; @@ -20,7 +21,6 @@ use rustls::server::WantsServerCert; use tokio::fs; use tokio::io::{AsyncWrite, ReadBuf}; use tokio::net::{ToSocketAddrs, UdpSocket}; -use tokio_native_tls::native_tls::Certificate; use crate::transport::SocketOpts; pub const ALPN_QUIC_TUNNEL: &[&[u8]] = &[b"qt"]; @@ -200,17 +200,21 @@ impl Transport for QuicTransport { let client_crypto = match tls_config.trusted_root.as_ref() { Some(path) => { - let s = std::fs::read_to_string(path) - .with_context(|| "Failed to read the `quic.trusted_root`")?; - let cert = Certificate::from_pem(s.as_bytes()) - .with_context(|| "Failed to read certificate from `quic.trusted_root`")?; + let certs: Result>> = read_all( + &mut BufReader::new( &mut File::open(path) + .with_context(|| "Failed to open the `quic.trusted_root`")?)) + .with_context(|| "Could not parse `quic.trusted_root`")? + .into_iter().map(|item| match item { + Item::X509Certificate(der_cert) => { Ok(der_cert)} + Item::RSAKey(_) | Item::PKCS8Key(_) => { + Err(anyhow!("`quic.trusted_root` should contain certificates, not keys"))} + }) + .collect(); let mut roots = rustls::RootCertStore::empty(); - - roots.add(&rustls::Certificate( - cert.to_der() - .with_context(|| "could not encode trust root as DER")?, - )).with_context(|| "adding trusted root cert to trust store")?; + for cert in certs?.into_iter() { + roots.add(&rustls::Certificate(cert)).with_context(|| "adding trusted root cert to trust store")?; + } let mut client_crypto = rustls::ClientConfig::builder() .with_safe_defaults()