Skip to content

Commit

Permalink
Add readme and top level docs
Browse files Browse the repository at this point in the history
  • Loading branch information
crazyscot committed Oct 25, 2024
1 parent b63ec54 commit f638dc7
Show file tree
Hide file tree
Showing 14 changed files with 459 additions and 72 deletions.
4 changes: 4 additions & 0 deletions .github/FUNDING.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
github: crazyscot
ko_fi: rossyounger
buy_me_a_coffee: rossyounger
# custom: [] # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "qcp"
description = "A remote file copy utility like scp, which uses the QUIC protocol over UDP"
description = "Secure remote file copy utility which uses the QUIC protocol over UDP"
rust-version = "1.81.0"
resolver = "2"
version = "0.1.0"
Expand Down
110 changes: 110 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
The Quic Copier (`qcp`) is an experimental
high-performance remote file copy utility for long-distance internet connections.

[//]: # (TODO: Badges, after first publication. Crates, docs, ci, license.)

## 📋 Features

- 🔧 Drop-in replacement for `scp` or `rcp`
- 🛡️ Similar security to `scp`, using existing, well-known mechanisms
- 🚀 Better throughput on congested networks

#### Platform support status

- Well tested: Debian and Ubuntu using OpenSSH
- Tested: Ubuntu on WSL
- Untested: OSX/BSD family
- Not currently supported: Windows

## 🧰 Getting Started

* You must have ssh access to the target machine.
* Install the `qcp` binary on both machines. It needs to be in your `PATH` on the remote machine.
* Run `qcp --help-buffers` and follow its instructions.

Install it from crates.io using `cargo`:

```bash
cargo install qcp
```

Or, clone the repo and build it manually:

```bash
git clone https://github.com/crazyscot/qcp
cd qcp
cargo build --release --locked
```

#### If you are new to Rust and don't have the tools installed

* Install the `rustup` tool via your package manager, or see [Rust installation](https://www.rust-lang.org/tools/install)
* `rustup toolchain install stable`
* Proceed as above

## ⚙️ Usage

The basic syntax is the same as scp or rcp.

You can run the program like this:

```bash
$ qcp my-server:/tmp/testfile /tmp/
⠂ Transferring data, instant rate: 2.1MB/s
testfile ████████████████████████████████████░░░░░░░░░░░░░░░░░░░░░░░░ 1s @ 6.71 MB/s [60%/10.49 MB]
```

The program uses ssh to connect to the target machine and run `qcp --server`. ssh will check the remote host key and prompt you for a password or passphrase in the usual way.

The default options are for a 100Mbit connection, with 300ms round-trip time to the target server.

You may care to set the options for your internet connection. For example, if you have 300Mbit/s (37.5MB/s) download and 100Mbit/s (12.5MB/s) upload:

```bash
qcp my-server:/tmp/testfile /tmp/ --tx 12M --rx 37M
```

[//]: # (TODO: link to crates.rs: Performance is a tricky subject, more fully discussed in ...)

## ⚖️ License

The initial release is made under the [GNU Affero General Public License](LICENSE).

## 🧑‍🏭 Contributing

Feel free to report bugs via the [bug tracker].

I'd particularly welcome performance reports from BSD/OSX users as that's not a platform I use regularly.

While suggestions and feature requests are welcome, please be aware that I mostly work on this project in my own time.

## 💸 Supporting the project

If you find this software useful and would like to support me, please consider [buying me a coffee] (or via [ko-fi]).

If you're a business and need a formal invoice for your accountant, my freelancing company can issue the paperwork. For this, and any other commercial enquiries (alternative licensing, support, sponsoring features) please get in touch.

Please also consider supporting the galaxy of projects this work builds upon.
Most notably, [Quinn] is a pure-Rust implementation of the [QUIC] protocol, without which qcp simply wouldn't exist in its current form.

### 💡 Roadmap

Some ideas for the future, in no particular order:

* A local config mechanism, so you don't have to type out the network parameters every time
* Support for copying multiple files (e.g. shell globs or `scp -r`)
* Windows native support, at least for client mode
* Firewall/NAT traversal
* Interactive file transfer (akin to `ftp`)
* Smart file copy using the `rsync` protocol or similar (send only the sections you need to)
* Graphical interface for ftp mode
* Review the protocol and perhaps pivot to using capnp RPC
* Bind a daemon to a fixed port, for better firewall/NAT traversal properties but at the cost of having to implement user authentication.
* _The same thing we do every night, Pinky. We try to take over the world!_

[bug tracker]: https://github.com/crazyscot/qcp/issues
[quic]: https://quicwg.github.io/
[Quinn]: https://opencollective.com/quinn-rs
[rfc9000]: https://www.rfc-editor.org/rfc/rfc9000.html
[buying me a coffee]: https://buymeacoffee.com/rossyounger
[ko-fi]: https://ko-fi.com/rossyounger
3 changes: 1 addition & 2 deletions schema/session.capnp
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,7 @@ struct Command {
# C->S: FileHeader, file data, FileTrailer
# S->C: Response (showing transfer status)
# Then close the stream.
# If the server needs to abort the transfer:
# S->C: Status (explaining why), then close the stream.
# If the server needs to abort the transfer, it may send a Response explaining why, then close the stream.
}

struct GetCmdArgs {
Expand Down
2 changes: 1 addition & 1 deletion src/client/args.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//! qcp Client parameters
//! Parameters specific to qcp client-mode
// (c) 2024 Ross Younger

use clap::Parser;
Expand Down
2 changes: 1 addition & 1 deletion src/client/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//! qcp client main loop
//! client-side (_initiator_) main loop and supporting structures
pub mod args;
pub mod control;
Expand Down
4 changes: 4 additions & 0 deletions src/doc/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
//! 📖 Additional documentation
pub mod performance;
pub mod troubleshooting;
56 changes: 56 additions & 0 deletions src/doc/performance.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// (c) 2024 Ross Younger

//! # 🚀 Performance tuning
//!
//! There's probably a whole book to be written about this.
//!
//! I've spent some time tuning this for my use case and leave some hooks so you can experiment.
//!
//! **It is critical to understand that the Internet is a strange place with many variables, which will likely confound any experiment you may try.**
//!
//! In my experience, long-distance traffic flows vary wildly from second to second.
//! This is why I added a near-instant (last 1s) bandwidth readout, as well the average.
//!
//! I found that the throughput from my build server (data flow from Europe to NZ) is sometimes very
//! fast, able to saturate my 300Mbit last-mile downlink, and sometimes falls back to hundreds of
//! kilobits or even worse. But the way QUIC applies congestion control worked around this really well.
//! Throughput accelerates rapidly when congestion clears; I _think_ this is subjectively much faster than scp does, but I've not yet gathered the data to do a proper statistical analysis.
//!
//! Yes, it's inefficient to do an ssh handshake and then a QUIC/TLS handshake.
//! But the cost of doing so isn't much in absolute terms (sometimes a few seconds),
//! and this amortises nicely over a large file transfer.
//!
//! ### Tips
//!
//! * When qcp tells you to set up the kernel UDP buffers, do so; they really make a difference. **You need to do this on both machines.**
//! * Run `qcp -h` and study the network tuning options available to you.
//! * With bandwidth and RTT - at least on my network conditions - I've found that perfect accuracy of configuration isn't so important, as long as it's in the right ballpark.
//! * In many cases your _last-mile_ bandwidth, i.e. whatever you're paying your ISP for,
//! is a good setting to use.
//! But there's a trap here: ISPs often describe their packages in bits per second,
//! but qcp expects a configuration in bytes!
//! * Try out `--congestion bbr` if you like. Sometimes it helps.
//! But understand that it remains experimental, and it does send out a lot more packets.
//! _If you have a metered connection, this may be an issue!_
//! [More about BBR](https://github.com/google/bbr/blob/master/Documentation/bbr-faq.md).
//! * Mess with the initial congestion window if you like, but I didn't find it reliably useful.
//! * Watch out for either end becoming CPU bound. One of my test machines on my local LAN was unable to move more than 7MB/s. It turned out that its CPU was so old it didn't have a useful crypto accelerator. If that applies to you, unfortunately you're not going to be able to move data any faster without a hardware upgrade.
//! * If you want to copy multiple files to/from the same remote machine, ssh connection multiplexing will save you a few seconds for each. (You can visualise the difference with the `--profile` option.)
//! * The `--debug` option will report additional information that might help you diagnose configuration issues.
//!
//! qcp will report the number of congestion events it detected, unless you run in `-q` mode.
//!
//! You might find it useful to run in `--stats` mode for additional insights; here's the output from a typical run:
//!
//! ```log
//! 2024-10-14T09:20:52.543540Z INFO Transferred 104.9MB in 12.75s; average 8.2MB/s
//! 2024-10-14T09:20:52.543782Z INFO Total packets sent: 3,279 by us; 75,861 by remote
//! 2024-10-14T09:20:52.543955Z WARN Congestion events detected: 2
//! 2024-10-14T09:20:52.544138Z WARN Remote lost packets: 112/75.9k (0.15%, for 157kB)
//! 2024-10-14T09:20:52.544320Z INFO Path MTU 1452, round-trip time 303.1ms, final congestion window 15,537,114
//! 2024-10-14T09:20:52.544530Z INFO 3.3k datagrams sent, 75.7k received, 0 black holes detected
//! 2024-10-14T09:20:52.544715Z INFO 107,526,015 total bytes sent for 104,857,600 bytes payload (2.54% overhead/loss)
//! 2024-10-14T09:20:52.544903Z WARN Measured path RTT 303.128843ms was greater than configuration 300; for better performance, next time try --rtt 304
//! ```
//!
//! (This was with a 100MB test file, which isn't always enough for the protocol to get fully up to speed.)
55 changes: 55 additions & 0 deletions src/doc/troubleshooting.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// (c) 2024 Ross Younger

//! 🕵️ Troubleshooting
//!
//! ## General
//!
//! The `--debug` and `--remote-debug` options report information that may help you diagnose issues.
//!
//! This program also understands the `RUST_LOG` environment variable which might let you probe deeper.
//! Some possible settings for this variable are:
//!
//! * `qcp=trace` outputs tracing-level output from this crate
//! * `trace` sets all the Rust components to trace mode, which includes an _awful lot_ of output from quinn (the QUIC implementation).
//!
//! Note that this variable setting applies to the local machine, not the remote. If you arrange to set it on the remote, the output will come back over the ssh channel; **this may impact performance**.
//!
//! ### You can't ssh to the remote machine
//!
//! Sorry, that's a prerequisite. Get that working first, then come back to qcp.
//!
//! qcp calls ssh directly; ssh will prompt you for a password and may invite you to verify the remote host key.
//!
//! ### The QUIC connection times out
//!
//! * Does the remote host firewall inbound UDP connections?
//! If so, you will need to allocate and open up a small range of inbound ports for use by qcp.
//! Use the `--remote-port` option to tell it which.
//! * Is the remote host behind NAT? Sorry, NAT traversal is not currently supported.
//! At best, you might be able to open up a small range of UDP ports on the NAT gateway which are directly forwarded to the target machine.
//! Use the `--remote-port` option to tell it which.
//! * Are outbound UDP packets from the initiator firewalled?
//! You will need to open up some outbound ports; use the `--port` option to tell qcp which.
//!
//! ### Performance is poor?
//!
//! (This became a separate doc. See [performance](super::performance).)
//!
//! ### Excess bandwidth usage
//!
//! This utility is designed to soak up all the bandwidth it can.
//!
//! When there is little packet loss, the overhead is minimal (2-3%). However when packets do go astray, the retransmits can add up. If you use the BBR congestion controller, this will add up much faster as it tries to keep the pipe fuller; I've seen it report over 20% packet loss.
//!
//! If you're on 95th percentile billing, you may need to take this into account. But if you are on that sort of deal, you are hopefully already spending time to understand and optimise your traffic profile.
//!
//! ### Using qcp interferes with video calls / Netflix / VOIP / etc
//!
//! This utility is designed to soak up all the bandwidth it can.
//!
//! QUIC packets are UDP over IP, the same underlying protocol used for streaming video, calls and so forth.
//! They are quite literally competing with any A/V data you may be running.
//!
//! If this bothers you, you might want to look into setting up QoS on your router.
//!
//! There might be some mileage in having qcp try to limit its bandwidth use or tune it to be less aggressive in the face of congestion, but that's not implemented at the moment.
53 changes: 51 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,53 @@
//! QCP client & server library
// (c) 2024 Ross Younger

#![allow(clippy::doc_markdown)]
//! The Quic Copier is an experimental high-performance remote file copy utility,
//! intended for long-distance internet connections.
//!
//! ## Overview
//! - 🔧 Drop-in replacement for `scp` or `rcp`
//! - 🛡️ Similar security to `scp`, using well-known and trustworthy mechanisms
//! - User authentication uses `ssh` to establish a control channel and exchange TLS certificates. No PKI is necessary.
//! - Data in transit is protected by TLS, with strict certificate checks in both directions
//! - 🚀 Better throughput on congested networks
//! - Data is transported using the [QUIC](https://quicwg.github.io/) protocol over UDP
//! - Tunable network properties
//!
//! ### Use case
//!
//! I was inspired to write this when I needed to copy a load of multi-GB files from a server on the other side of the planet.
//!
//! If you want to copy **large** files (tens of MB or more),
//! from _point to point_ over a _long, fat, congested pipe_,
//! then you may find it useful too.
//!
//! #### Limitations
//! - You must be able to ssh directly to the remote machine, and exchange UDP packets with it on a given port. (If the local machine is behind connection-tracking NAT, things work just fine. This is the case for the vast majority of home and business network connections.)
//! - Network security systems can't readily identify QUIC traffic as such. It's opaque, and high bandwidth. Some security systems might flag it as a potential threat.
//!
//! #### What qcp is not
//!
//! * A way to serve files to the public (Use http3.)
//! * A way to speed up downloads from sites you do not control (It's up to whoever runs those sites to install http3 or set up a [CDN].)
//! * Peer to peer file transfer (Use [BitTorrent]?)
//! * An improvement for interactive shells (Use [mosh].)
//! * Delta-based copying (Use [rsync].)
//!
//! ## How it works 📖
//!
//! See [protocol].
//!
//! ## Getting the best out of qcp
//!
//! See [performance](doc::performance) and [troubleshooting](doc::troubleshooting).
//!
//! [QUIC]: https://quicwg.github.io/
//! [ssh]: https://en.wikipedia.org/wiki/Secure_Shell
//! [CDN]: https://en.wikipedia.org/wiki/Content_delivery_network
//! [BitTorrent]: https://en.wikipedia.org/wiki/BitTorrent
//! [rsync]: https://en.wikipedia.org/wiki/Rsync
//! [mosh]: https://mosh.org/
mod cli;
pub use cli::cli; // needs to be re-exported for the binary crate

Expand All @@ -10,9 +57,11 @@ pub mod server;
pub mod transport;
pub mod util;

pub mod doc;

mod os;

/// Build-time info (from `built`)
/// Build-time info (autogenerated by `built`)
pub mod build_info {
include!(concat!(env!("OUT_DIR"), "/built.rs"));
}
2 changes: 1 addition & 1 deletion src/os/unix.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ pub(crate) fn print_udp_buffer_size_help_message(rmem: u64, wmem: u64) {
println!(
r#"For best performance, it is necessary to set the kernel UDP buffer size limits.
This program attempts to automatically set buffer sizes for itself,
but this requires elevated privileges."#
but doing so requires elevated privileges."#
);

if bsdish() {
Expand Down
Loading

0 comments on commit f638dc7

Please sign in to comment.