diff --git a/Cargo.lock b/Cargo.lock index 9287c76..6644a46 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1348,6 +1348,7 @@ dependencies = [ "futures-util", "hyper", "quinn", + "rand", "rcgen", "reqwest", "rmp-serde", diff --git a/docs/explanation/ptth_direc.md b/docs/explanation/ptth_direc.md new file mode 100644 index 0000000..8269928 --- /dev/null +++ b/docs/explanation/ptth_direc.md @@ -0,0 +1,73 @@ +# PTTH_DIREC - Direct P2P connections + +_It could work, even!_ + +To keep each ridiculous new feature simple, we'll rely on bootstrapping: + +1. PTTH is just HTTPS connections +2. PTTH_QUIC uses a PTTH relay to download the QUIC cert and bootstrap +3. PTTH_DIREC will use a PTTH_QUIC relay to bootstrap + +# Overview + +Given that: + +- P2 is connected to P3 +- P4 is connected to P3 + +Steps: + +- S1. P2 starts a bi stream to P3 +- S2.0. P2 says, "I want to initiate a PTTH_DIREC connection..." +- "... And I'll send you cookie X to do hole-punching..." +- "... And I want to connect to end server Y..." +- S3.0. P3 creates an ID for this connection +- S3.1. P3 replies "go ahead" to P2 +- S4. P3 starts a bi stream to P4 (end server Y) +- S5. P3 says, "I want you to accept a PTTH_DIREC connection..." +- "... And you should send me cookie Z to do hole-punching..." +- "... And the client will be client W..." +- S6. P3 waits for P4 to accept the offer +- S7. P3 waits for both cookies to arrive +- S8. When the cookies arrive, P3 learns the WAN addresses of P2 and P4 +- S9. P3 sends the WAN addresses of P2 and P4 to each other (on the existing bi streams) +- S10. P4 tries to connect directly to P2 +- S11. P2 does the same to P4 +- S12. When P4 sees round-tripped data, it attempts to upgrade to QUIC +- S13. When P2 sees round-tripped data, it attempts to upgrade to QUIC +- Cool stuff happens over QUIC +- ReactorScram implements the rest of the protocol + +P2's PoV: + +- S1. Start a bi stream to P3 +- S2.0. Send cookie and server ID +- S2.1. Wait for go-ahead signal (this may contain the hole-punch address and a new cookie for P4) +- S2.2. Send cookie to hole-punch address repeatedly +- S2.3. While you're sending the cookie, wait to hear P4's WAN address +- S9. Learn P4's WAN address +- S10. Send the new cookie to P4's address +- S12. When you see round-tripped data, upgrade to QUIC + +P4's PoV: + +- S4. Accept a bi stream from P3 +- S5. Receive cookie and client ID +- S6. Reply "OK" +- S7.0. Send cookie to hole-punch address repeatedly +- S7.1. While sending the cookie, wait to hear P2's WAN address +- S9. Learn P2's WAN address +- S10. Try to connect directly to P2 +- S12. When you see round-tripped data, upgrade to QUIC + +Commands needed: + +- ??? + +# Decisions + +I'll add a delay between giving P2's address to P4, and giving P4's address to P2. +This miiiight change things slightly if P4's firewall is suspicious of packets +coming in too early, but I doubt it. + +The delay is easy to remove relay-side if it doesn't help. diff --git a/prototypes/quic_demo/Cargo.toml b/prototypes/quic_demo/Cargo.toml index 9e591f3..98e2072 100644 --- a/prototypes/quic_demo/Cargo.toml +++ b/prototypes/quic_demo/Cargo.toml @@ -15,6 +15,7 @@ ctrlc = "3.2.1" futures-util = "0.3.9" hyper = { version = "0.14.4", features = ["http1", "server", "stream", "tcp"] } quinn = "0.7.2" +rand = "0.8.4" rcgen = "0.8.11" reqwest = "0.11.4" rmp-serde = "0.15.5" diff --git a/prototypes/quic_demo/src/bin/quic_demo_client.rs b/prototypes/quic_demo/src/bin/quic_demo_client.rs index 6208d31..a015744 100644 --- a/prototypes/quic_demo/src/bin/quic_demo_client.rs +++ b/prototypes/quic_demo/src/bin/quic_demo_client.rs @@ -1,5 +1,6 @@ use structopt::StructOpt; use tokio::{ + net::UdpSocket, sync::watch, }; @@ -98,7 +99,34 @@ impl P2Client { }) }; + if false { + let task_direc_connect = { + let connection = connection.clone (); + + tokio::spawn (async move { + let cookie = protocol::p2_direc_to_p4 ( + &connection, + "bogus_server", + ).await?; + + let sock = UdpSocket::bind ("0.0.0.0:0").await?; + + let mut interval = tokio::time::interval (Duration::from_millis (1000)); + interval.set_missed_tick_behavior (tokio::time::MissedTickBehavior::Delay); + + loop { + interval.tick ().await; + sock.send_to(&cookie [..], "127.0.0.1:30379").await?; + debug! ("P2 sent cookie to P3 over plain UDP"); + } + + Ok::<_, anyhow::Error> (()) + }) + }; + } + task_tcp_server.await??; + //task_direc_connect.await??; Ok (()) } diff --git a/prototypes/quic_demo/src/bin/quic_demo_relay_server.rs b/prototypes/quic_demo/src/bin/quic_demo_relay_server.rs index e7be1a3..11c6ad8 100644 --- a/prototypes/quic_demo/src/bin/quic_demo_relay_server.rs +++ b/prototypes/quic_demo/src/bin/quic_demo_relay_server.rs @@ -10,7 +10,10 @@ use hyper::{ StatusCode, }; use structopt::StructOpt; -use tokio::sync::watch; +use tokio::{ + net::UdpSocket, + sync::watch, +}; use quic_demo::prelude::*; use protocol::PeerId; @@ -84,6 +87,35 @@ async fn main () -> anyhow::Result <()> { }) }; + let task_direc_server = { + let relay_state = Arc::clone (&relay_state); + + tokio::spawn (async move { + let sock = UdpSocket::bind("0.0.0.0:30379").await?; + let mut buf = [0; 2048]; + loop { + let (len, addr) = sock.recv_from (&mut buf).await?; + debug! ("{:?} bytes received from {:?}", len, addr); + + let packet = Vec::from_iter ((&buf [0..len]).into_iter ().map (|x| *x)); + + { + let mut direc_cookies = relay_state.direc_cookies.lock ().await; + + if let Some (direc_state) = direc_cookies.remove (&packet) { + debug! ("Got PTTH_DIREC cookie for {}", direc_state.p2_id); + direc_state.p2_addr.send (addr).ok (); + } + else { + debug! ("UDP packet didn't match any PTTH_DIREC cookie"); + } + } + } + + Ok::<_, anyhow::Error> (()) + }) + }; + let task_http_server = tokio::spawn (async move { http_server.serve (make_svc).await?; Ok::<_, anyhow::Error> (()) @@ -128,6 +160,7 @@ async fn main () -> anyhow::Result <()> { task_quic_server.await??; task_http_server.await??; task_tcp_server.await??; + task_direc_server.await??; Ok (()) } @@ -153,9 +186,16 @@ async fn handle_http (_req: Request
, relay_state: Arc