diff --git a/.github/actions/test-transfer-http-udp/Dockerfile b/.github/actions/test-transfer/Dockerfile similarity index 93% rename from .github/actions/test-transfer-http-udp/Dockerfile rename to .github/actions/test-transfer/Dockerfile index 7e01849..b297bc7 100644 --- a/.github/actions/test-transfer-http-udp/Dockerfile +++ b/.github/actions/test-transfer/Dockerfile @@ -1,5 +1,5 @@ # Container image that runs your code -FROM rust:latest +FROM rust:bullseye # Copies your code file from your action repository to the filesystem path `/` of the container COPY entrypoint.sh /entrypoint.sh diff --git a/.github/actions/test-transfer-http-udp/action.yml b/.github/actions/test-transfer/action.yml similarity index 65% rename from .github/actions/test-transfer-http-udp/action.yml rename to .github/actions/test-transfer/action.yml index 9bf3fa2..2520a97 100644 --- a/.github/actions/test-transfer-http-udp/action.yml +++ b/.github/actions/test-transfer/action.yml @@ -1,5 +1,5 @@ -name: 'test-transfer-http-udp' -description: 'test aquatic http and udp file transfer' +name: 'test-transfer' +description: 'test aquatic file transfer' outputs: http_ipv4: description: 'HTTP IPv4 status' @@ -7,6 +7,8 @@ outputs: description: 'HTTP IPv4 over TLS status' udp_ipv4: description: 'UDP IPv4 status' + wss_ipv4: + description: 'WSS IPv4 status' runs: using: 'docker' image: 'Dockerfile' \ No newline at end of file diff --git a/.github/actions/test-transfer-http-udp/entrypoint.sh b/.github/actions/test-transfer/entrypoint.sh similarity index 59% rename from .github/actions/test-transfer-http-udp/entrypoint.sh rename to .github/actions/test-transfer/entrypoint.sh index 3618bed..77fd0ad 100755 --- a/.github/actions/test-transfer-http-udp/entrypoint.sh +++ b/.github/actions/test-transfer/entrypoint.sh @@ -1,10 +1,15 @@ #!/bin/bash # # Test that file transfers work with aquatic_http (with and without TLS) -# and aquatic_udp. +# aquatic_udp and experimentally aquatic_ws (with TLS). # # IPv6 is unfortunately disabled by default in Docker # (see sysctl net.ipv6.conf.lo.disable_ipv6) +# +# When testing locally, use: +# 1. docker build -t aquatic ./path/to/Dockerfile +# 2. docker run aquatic +# 3. On failure, run `docker rmi aquatic -f` and go back to step 1 set -e @@ -17,9 +22,14 @@ else fi $SUDO apt-get update -$SUDO apt-get install -y cmake libssl-dev screen rtorrent mktorrent ssl-cert ca-certificates +$SUDO apt-get install -y cmake libssl-dev screen rtorrent mktorrent ssl-cert ca-certificates curl golang -rtorrent -h +git clone https://github.com/anacrolix/torrent.git gotorrent +cd gotorrent +git checkout 16176b762e4a840fc5dfe3b1dfd2d6fa853b68d7 +go build -o $HOME/gotorrent ./cmd/torrent +cd .. +file $HOME/gotorrent # Clone repository if necessary, go to repository directory @@ -70,6 +80,16 @@ echo "[network] address = '127.0.0.1:3000'" > udp.toml ./target/debug/aquatic udp -c udp.toml > "$HOME/udp.log" 2>&1 & +echo "log_level = 'trace' + +[network] +address = '127.0.0.1:3002' +use_tls = true +tls_pkcs12_path = './identity.pfx' +tls_pkcs12_password = 'p' +" > ws.toml +./target/debug/aquatic ws -c ws.toml > "$HOME/wss.log" 2>&1 & + # Setup directories cd "$HOME" @@ -83,25 +103,37 @@ mkdir torrents echo "http-test-ipv4" > seed/http-test-ipv4 echo "tls-test-ipv4" > seed/tls-test-ipv4 echo "udp-test-ipv4" > seed/udp-test-ipv4 +echo "wss-test-ipv4" > seed/wss-test-ipv4 mktorrent -p -o "torrents/http-ipv4.torrent" -a "http://127.0.0.1:3000/announce" "seed/http-test-ipv4" mktorrent -p -o "torrents/tls-ipv4.torrent" -a "https://example.com:3001/announce" "seed/tls-test-ipv4" mktorrent -p -o "torrents/udp-ipv4.torrent" -a "udp://127.0.0.1:3000" "seed/udp-test-ipv4" +mktorrent -p -o "torrents/wss-ipv4.torrent" -a "wss://example.com:3002" "seed/wss-test-ipv4" cp -r torrents torrents-seed cp -r torrents torrents-leech -# Start seeding client +# Setup wss seeding client + +echo "Starting seeding wss client" +cd seed +GOPPROF=http GODEBUG=x509ignoreCN=0 $HOME/gotorrent download --dht=false --tcppeers=false --utppeers=false --pex=false --stats --seed ../torrents/wss-ipv4.torrent > "$HOME/wss-seed.log" 2>&1 & +cd .. + +# Start seeding rtorrent client echo "directory.default.set = $HOME/seed schedule2 = watch_directory,5,5,load.start=$HOME/torrents-seed/*.torrent" > ~/.rtorrent.rc -echo "Starting seeding client" +echo "Starting seeding rtorrent client" screen -dmS rtorrent-seed rtorrent -sleep 10 # Give seeding rtorrent time to load its config file +# Give seeding clients time to load config files etc -# Start leeching client +echo "Waiting for a while" +sleep 30 + +# Start leeching clients echo "directory.default.set = $HOME/leech schedule2 = watch_directory,5,5,load.start=$HOME/torrents-leech/*.torrent" > ~/.rtorrent.rc @@ -109,35 +141,58 @@ schedule2 = watch_directory,5,5,load.start=$HOME/torrents-leech/*.torrent" > ~/. echo "Starting leeching client.." screen -dmS rtorrent-leech rtorrent +echo "Starting leeching wss client" +cd leech +GOPPROF=http GODEBUG=x509ignoreCN=0 $HOME/gotorrent download --dht=false --tcppeers=false --utppeers=false --pex=false --stats --addr ":43000" ../torrents/wss-ipv4.torrent > "$HOME/wss-leech.log" 2>&1 & +cd .. + # Check for completion HTTP_IPv4="Failed" TLS_IPv4="Failed" UDP_IPv4="Failed" +WSS_IPv4="Failed" i="0" echo "Watching for finished files.." -while [ $i -lt 300 ] +while [ $i -lt 60 ] do if test -f "leech/http-test-ipv4"; then if grep -q "http-test-ipv4" "leech/http-test-ipv4"; then - HTTP_IPv4="Ok" + if [ "$HTTP_IPv4" != "Ok" ]; then + HTTP_IPv4="Ok" + echo "HTTP_IPv4 is Ok" + fi fi fi if test -f "leech/tls-test-ipv4"; then if grep -q "tls-test-ipv4" "leech/tls-test-ipv4"; then - TLS_IPv4="Ok" + if [ "$TLS_IPv4" != "Ok" ]; then + TLS_IPv4="Ok" + echo "TLS_IPv4 is Ok" + fi fi fi if test -f "leech/udp-test-ipv4"; then if grep -q "udp-test-ipv4" "leech/udp-test-ipv4"; then - UDP_IPv4="Ok" + if [ "$UDP_IPv4" != "Ok" ]; then + UDP_IPv4="Ok" + echo "UDP_IPv4 is Ok" + fi + fi + fi + if test -f "leech/wss-test-ipv4"; then + if grep -q "wss-test-ipv4" "leech/wss-test-ipv4"; then + if [ "$WSS_IPv4" != "Ok" ]; then + WSS_IPv4="Ok" + echo "WSS_IPv4 is Ok" + fi fi fi - if [ "$HTTP_IPv4" = "Ok" ] && [ "$TLS_IPv4" = "Ok" ] && [ "$UDP_IPv4" = "Ok" ]; then + if [ "$HTTP_IPv4" = "Ok" ] && [ "$TLS_IPv4" = "Ok" ] && [ "$UDP_IPv4" = "Ok" ] && [ "$WSS_IPv4" = "Ok" ]; then break fi @@ -151,25 +206,51 @@ echo "Waited for $i seconds" echo "::set-output name=http_ipv4::$HTTP_IPv4" echo "::set-output name=http_tls_ipv4::$TLS_IPv4" echo "::set-output name=udp_ipv4::$UDP_IPv4" +echo "::set-output name=wss_ipv4::$WSS_IPv4" echo "" echo "# --- HTTP log --- #" cat "http.log" +sleep 1 + echo "" echo "# --- HTTP over TLS log --- #" cat "tls.log" +sleep 1 + echo "" echo "# --- UDP log --- #" cat "udp.log" +sleep 1 + +echo "" +echo "# --- WSS tracker log --- #" +cat "wss.log" + +sleep 1 + +echo "" +echo "# --- WSS seed log --- #" +cat "wss-seed.log" + +sleep 1 + +echo "" +echo "# --- WSS leech log --- #" +cat "wss-leech.log" + +sleep 1 + echo "" echo "# --- Test results --- #" echo "HTTP (IPv4): $HTTP_IPv4" echo "HTTP over TLS (IPv4): $TLS_IPv4" echo "UDP (IPv4): $UDP_IPv4" +echo "WSS (IPv4): $WSS_IPv4" -if [ "$HTTP_IPv4" != "Ok" ] || [ "$TLS_IPv4" != "Ok" ] || [ "$UDP_IPv4" != "Ok" ]; then +if [ "$HTTP_IPv4" != "Ok" ] || [ "$TLS_IPv4" != "Ok" ] || [ "$UDP_IPv4" != "Ok" ] || [ "$WSS_IPv4" != "Ok" ]; then exit 1 fi \ No newline at end of file diff --git a/.github/workflows/test-transfer-http-udp.yml b/.github/workflows/test-transfer.yml similarity index 67% rename from .github/workflows/test-transfer-http-udp.yml rename to .github/workflows/test-transfer.yml index 956dd63..97a4f91 100644 --- a/.github/workflows/test-transfer-http-udp.yml +++ b/.github/workflows/test-transfer.yml @@ -1,4 +1,4 @@ -name: "Test HTTP and UDP file transfer" +name: "Test HTTP, UDP and WSS file transfer" on: push: @@ -9,10 +9,10 @@ on: jobs: test-transfer-http: runs-on: ubuntu-latest - name: "Test BitTorrent file transfer over HTTP (with and without TLS) and UDP" + name: "Test BitTorrent file transfer over HTTP (with and without TLS), UDP and WSS" steps: - name: Checkout uses: actions/checkout@v2 - name: Test file transfers - uses: ./.github/actions/test-transfer-http-udp - id: test_transfer_udp_http \ No newline at end of file + uses: ./.github/actions/test-transfer + id: test_transfer \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 6d63746..aadb41a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -337,9 +337,9 @@ dependencies = [ [[package]] name = "bitflags" -version = "1.3.1" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2da1976d75adbe5fbc88130ecd119529cf1cc6a93ae1546d8696ee66f0d21af1" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "block-buffer" @@ -792,15 +792,6 @@ dependencies = [ "regex", ] -[[package]] -name = "input_buffer" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f97967975f448f1a7ddb12b0bc41069d09ed6a1c161a92687e057325db35d413" -dependencies = [ - "bytes", -] - [[package]] name = "instant" version = "0.1.10" @@ -1675,18 +1666,16 @@ dependencies = [ [[package]] name = "tungstenite" -version = "0.13.0" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fe8dada8c1a3aeca77d6b51a4f1314e0f4b8e438b7b1b71e3ddaca8080e4093" +checksum = "983d40747bce878d2fb67d910dcb8bd3eca2b2358540c3cc1b98c027407a3ae3" dependencies = [ "base64", "byteorder", "bytes", "http", "httparse", - "input_buffer", "log", - "native-tls", "rand", "sha-1", "thiserror", diff --git a/README.md b/README.md index 86ef704..a0a99bf 100644 --- a/README.md +++ b/README.md @@ -17,10 +17,12 @@ Distributed under Apache 2.0 license (details in `LICENSE` file.) ## Technical overview of tracker design One or more socket workers open sockets, read and parse requests from peers and -send them through channels to request workers. They in turn go through the -requests, update internal state as appropriate and generate responses, which -are sent back to the socket workers, which serialize them and send them to -peers. This design means little waiting for locks on internal state occurs, +send them through channels to request workers. The request workers go through +the requests, update shared internal tracker state as appropriate and generate +responses that are sent back to the socket workers. The responses are then +serialized and sent back to the peers. + +This design means little waiting for locks on internal state occurs, while network work can be efficiently distributed over multiple threads, making use of SO_REUSEPORT setting. @@ -149,11 +151,7 @@ exceptions: For information about running over TLS, please refer to the TLS subsection of the `aquatic_http` section above. -`aquatic_ws` is experimental software. Connections are established -successfully when using `aquatic_ws_load_test`, but so far, I haven't been able -to implement CI for testing if aquatic_ws works as the tracker for a full -file transfer session between two real-world clients. One reason for this -is the general lack of high-quality WebTorrent clients. +`aquatic_ws` is experimental software. ## Load testing diff --git a/aquatic_ws/Cargo.toml b/aquatic_ws/Cargo.toml index b5597e8..1087208 100644 --- a/aquatic_ws/Cargo.toml +++ b/aquatic_ws/Cargo.toml @@ -34,7 +34,7 @@ privdrop = "0.5" rand = { version = "0.8", features = ["small_rng"] } serde = { version = "1", features = ["derive"] } socket2 = { version = "0.4.1", features = ["all"] } -tungstenite = "0.13" +tungstenite = "0.15" [dev-dependencies] quickcheck = "1.0" diff --git a/aquatic_ws/src/lib/handler.rs b/aquatic_ws/src/lib/handler.rs index 65ce6c2..1f61a17 100644 --- a/aquatic_ws/src/lib/handler.rs +++ b/aquatic_ws/src/lib/handler.rs @@ -118,6 +118,8 @@ pub fn handle_announce_requests( } } + ::log::trace!("received request from {:?}", request_sender_meta); + // Insert/update/remove peer who sent this request { let peer_status = PeerStatus::from_event_and_bytes_left( @@ -187,6 +189,10 @@ pub fn handle_announce_requests( offer_receiver.connection_meta, OutMessage::Offer(middleman_offer), ); + ::log::trace!( + "sent middleman offer to {:?}", + offer_receiver.connection_meta + ); wake_socket_workers[offer_receiver.connection_meta.worker_index] = true; } } @@ -208,6 +214,10 @@ pub fn handle_announce_requests( answer_receiver.connection_meta, OutMessage::Answer(middleman_answer), ); + ::log::trace!( + "sent middleman answer to {:?}", + answer_receiver.connection_meta + ); wake_socket_workers[answer_receiver.connection_meta.worker_index] = true; } } diff --git a/aquatic_ws/src/lib/network/connection.rs b/aquatic_ws/src/lib/network/connection.rs index 2cc7c99..fbaf299 100644 --- a/aquatic_ws/src/lib/network/connection.rs +++ b/aquatic_ws/src/lib/network/connection.rs @@ -9,7 +9,7 @@ use mio::{Poll, Token}; use native_tls::{MidHandshakeTlsStream, TlsAcceptor, TlsStream}; use tungstenite::handshake::{server::NoCallback, HandshakeError, MidHandshake}; use tungstenite::protocol::WebSocketConfig; -use tungstenite::server::ServerHandshake; +use tungstenite::ServerHandshake; use tungstenite::WebSocket; use crate::common::*; @@ -111,7 +111,7 @@ impl HandshakeMachine { if let Some(tls_acceptor) = opt_tls_acceptor { Self::handle_tls_handshake_result(tls_acceptor.accept(stream)) } else { - let handshake_result = ::tungstenite::server::accept_with_config( + let handshake_result = ::tungstenite::accept_with_config( Stream::TcpStream(stream), Some(ws_config), ); @@ -120,7 +120,7 @@ impl HandshakeMachine { } } HandshakeMachine::TlsStream(stream) => { - let handshake_result = ::tungstenite::server::accept(Stream::TlsStream(stream)); + let handshake_result = ::tungstenite::accept(Stream::TlsStream(stream)); Self::handle_ws_handshake_result(handshake_result) } @@ -138,7 +138,14 @@ impl HandshakeMachine { result: Result, ::native_tls::HandshakeError>, ) -> (Option>, bool) { match result { - Ok(stream) => (Some(Either::Right(Self::TlsStream(stream))), false), + Ok(stream) => { + ::log::trace!( + "established tls handshake with peer with addr: {:?}", + stream.get_ref().peer_addr() + ); + + (Some(Either::Right(Self::TlsStream(stream))), false) + }, Err(native_tls::HandshakeError::WouldBlock(handshake)) => { (Some(Either::Right(Self::TlsMidHandshake(handshake))), true) } @@ -158,7 +165,15 @@ impl HandshakeMachine { Ok(mut ws) => { let peer_addr = ws.get_mut().get_peer_addr(); - let established_ws = EstablishedWs { ws, peer_addr }; + ::log::trace!( + "established ws handshake with peer with addr: {:?}", + peer_addr + ); + + let established_ws = EstablishedWs { + ws, + peer_addr, + }; (Some(Either::Left(established_ws)), false) } diff --git a/aquatic_ws_load_test/Cargo.toml b/aquatic_ws_load_test/Cargo.toml index 54a5923..abf14ab 100644 --- a/aquatic_ws_load_test/Cargo.toml +++ b/aquatic_ws_load_test/Cargo.toml @@ -21,7 +21,7 @@ rand_distr = "0.4" serde = { version = "1", features = ["derive"] } serde_json = "1" slab = "0.4" -tungstenite = "0.13" +tungstenite = "0.15" [dev-dependencies] quickcheck = "1.0" diff --git a/aquatic_ws_protocol/Cargo.toml b/aquatic_ws_protocol/Cargo.toml index 5817ffc..1830725 100644 --- a/aquatic_ws_protocol/Cargo.toml +++ b/aquatic_ws_protocol/Cargo.toml @@ -22,7 +22,7 @@ hashbrown = { version = "0.11.2", features = ["serde"] } serde = { version = "1", features = ["derive"] } serde_json = "1" simd-json = { version = "0.4.7", features = ["allow-non-simd"] } -tungstenite = "0.13" +tungstenite = "0.15" [dev-dependencies] criterion = "0.3"