pasque/
lib.rs

1//! [Pasque] is an UDP over HTTP/3 ([RFC 9298]) and IP over HTTP/3
2//! implementation ([RFC 9484]). Built using [Quiche] as the HTTP/3 & QUIC
3//! implementation and [Tokio] for async operations. The project is yet under
4//! construction, and some features from the RFCs are still missing.
5//! 
6//! [Pasque]: https://github.com/PasiSa/pasque
7//! [Quiche]: https://crates.io/crates/quiche
8//! [Tokio]: https://crates.io/crates/tokio
9//! [RFC 9298]: https://datatracker.ietf.org/doc/html/rfc9298/
10//! [RFC 9484]: https://datatracker.ietf.org/doc/html/rfc9484/
11//! 
12//! ## Starting a server
13//! 
14//! [psq-server.rs] is a simple example of a server implementation using Pasque.
15//! For example, to start an UDP tunnel endpoint at path "udp", you could have:
16//! 
17//! ```no_run
18//! use pasque::{server::Config, PsqServer, UdpEndpoint};
19//! use std::net::SocketAddr;
20//! use std::str::FromStr;
21//! #[tokio::main]
22//! async fn main() {
23//!     let config = Config::read_from_file("server.json").unwrap();
24//!     let mut psqserver = PsqServer::start(
25//!         &vec![SocketAddr::from_str("0.0.0.0:443").unwrap()],
26//!         &config,
27//!     ).await.unwrap();
28//!     psqserver.add_endpoint("udp", Box::new(UdpEndpoint::new())).await;
29//! }
30//! ```
31//! 
32//! (of course with proper error handling). First, certificate information is
33//! read from a config file, then HTTP/3 / QUIC server is started, binding to
34//! UDP port 4433. And a [`UdpEndpoint`] is added for proxying UDP datagrams.
35//! [`IpEndpoint`] can be used for proxying IP packets from TUN interface (needs
36//! sudo privilege, only tested on Linux for the time being).
37//! 
38//! [psq-server.rs]: https://github.com/PasiSa/pasque/blob/main/src/bin/psq-server.rs
39//! 
40//! ## Starting a client
41//! 
42//! [psq-client.rs] is an example of a client implementation using Pasque. To
43//! match the above server example, a client-end of the UDP tunnel would be:
44//!  
45//! [psq-client.rs]: https://github.com/PasiSa/pasque/blob/main/src/bin/psq-client.rs
46//!
47//! ```no_run
48//! use pasque::{PsqClient, UdpTunnel};
49//! #[tokio::main]
50//! async fn main() {
51//!     let mut psqconn = PsqClient::connect("https://localhost", false).await.unwrap();
52//!     let udptunnel = UdpTunnel::connect(
53//!         &mut psqconn,
54//!         "udp",
55//!         "130.233.224.196", 9000,
56//!         "127.0.0.1:0".parse().unwrap(),
57//!     ).await.unwrap();
58//!     println!("UDP datagrams to {} are forwarded to HTTP tunnel.", udptunnel.sockaddr().unwrap());
59//! }
60//! ```
61//! 
62//! The above first opens a HTTP/3 / QUIC connection to given server. Then UDP
63//! tunnel is connected to "udp" endpoint, for destination address
64//! 130.233.224.196, UDP port 9000. The client opens a local UDP socket that is
65//! used to deliver packets to and from the the tunnel. User can specify the
66//! address and port to bind, or if none is given, the bound address can be
67//! queried using the [`UdpTunnel::sockaddr()`] function.
68//! 
69//! [`IpTunnel`] is available for establishing IP tunnels from TUN interface
70//! (requires sudo privileges, tested only on Linux).
71
72#[macro_use]
73extern crate log;
74
75use quiche::ConnectionId;
76use thiserror::Error;
77
78pub use crate::{
79    client::PsqClient,
80    server::PsqServer,
81    stream::{
82        iptunnel::{ IpTunnel, IpEndpoint },
83        udptunnel::{ UdpTunnel, UdpEndpoint },
84        filestream::{ FileStream, Files },
85    },
86};
87
88const VERSION_IDENTIFICATION: &str = env!("CARGO_PKG_VERSION");
89
90
91#[derive(Error, Debug)]
92pub enum PsqError {
93
94    #[error("HTTP/3 capsule error: {0}")]
95    H3Capsule(String),
96
97    #[error("Not supported: {0}")]
98    NotSupported(String),
99
100    #[error("HTTP response error: {0}")]
101    HttpResponse(u16, String),
102
103    #[error("Stream closing: {0}")]
104    StreamClose(String),
105
106    #[error("IO error: {0}")]
107    Io(#[from] std::io::Error),
108
109    #[error("URL parse error: {0}")]
110    UrlParse(#[from] url::ParseError),
111
112    #[error("IP Address parse error: {0}")]
113    AddressParse(#[from] ipnetwork::IpNetworkError),
114
115    #[error("QUIC error: {0}")]
116    Quiche(#[from] quiche::Error),
117
118    #[error("HTTP/3 error: {0}")]
119    Http3(#[from] quiche::h3::Error),
120
121    #[error("Octets buffer error: {0}")]
122    Octets(#[from] octets::BufferTooShortError),
123
124    #[error("TUN interface error: {0}")]
125    Tun(#[from] tun::Error),
126
127    #[error("UTF8 parsing error: {0}")]
128    Utf8(#[from] std::str::Utf8Error),
129
130    #[error("JSON Web Token error: {0}")]
131    Jwt(#[from] jsonwebtoken::errors::Error),
132
133    #[error("Custom error: {0}")]
134    Custom(String),
135
136    #[error("Unimplemented")]
137    Unimplemented,
138}
139
140
141pub fn set_qlog(conn: &mut quiche::Connection, scid: &ConnectionId<'_>) {
142    if let Some(dir) = std::env::var_os("QLOGDIR") {
143        let id = format!("{scid:?}");
144        let writer = make_qlog_writer(&dir, "client", &id);
145
146        conn.set_qlog(
147            std::boxed::Box::new(writer),
148            "quiche-client qlog".to_string(),
149            format!("{} id={}", "quiche-client qlog", id),
150        );
151    }
152}
153
154
155fn make_qlog_writer(
156    dir: &std::ffi::OsStr, role: &str, id: &str,
157) -> std::io::BufWriter<std::fs::File> {
158    let mut path = std::path::PathBuf::from(dir);
159    let filename = format!("{role}-{id}.sqlog");
160    path.push(filename);
161
162    match std::fs::File::create(&path) {
163        Ok(f) => std::io::BufWriter::new(f),
164
165        Err(e) => panic!(
166            "Error creating qlog file attempted path was {:?}: {}",
167            path, e
168        ),
169    }
170}
171
172
173pub mod client;
174pub mod jwt;
175pub mod platform;
176pub mod server;
177pub mod stream;
178
179mod util;
180
181pub mod test_utils;