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;