Skip to main content

hala_qtun/
app.rs

1use std::{
2    io,
3    net::{SocketAddr, ToSocketAddrs},
4    ops::Range,
5    path::PathBuf,
6    time::Duration,
7};
8
9use clap::{Parser, ValueEnum};
10use hala_future::executor::future_spawn;
11use hala_io::sleep;
12use hala_quic::{Config, CongestionControlAlgorithm};
13use hala_rproxy::{Handshaker, Rproxy};
14
15type SocketAddrs = Vec<SocketAddr>;
16
17fn clap_parse_duration(s: &str) -> Result<Duration, String> {
18    let duration = duration_str::parse(s).map_err(|err| format!("{}", err))?;
19
20    Ok(duration)
21}
22
23#[derive(ValueEnum, Clone, Debug)]
24pub enum QuicCongestionControlAlgorithm {
25    /// Reno congestion control algorithm. `reno` in a string form.
26    Reno = 0,
27    /// CUBIC congestion control algorithm (default). `cubic` in a string form.
28    CUBIC = 1,
29    /// BBR congestion control algorithm. `bbr` in a string form.
30    BBR = 2,
31    /// BBRv2 congestion control algorithm. `bbr2` in a string form.
32    BBR2 = 3,
33}
34
35impl From<QuicCongestionControlAlgorithm> for CongestionControlAlgorithm {
36    fn from(value: QuicCongestionControlAlgorithm) -> Self {
37        match value {
38            QuicCongestionControlAlgorithm::Reno => CongestionControlAlgorithm::Reno,
39            QuicCongestionControlAlgorithm::CUBIC => CongestionControlAlgorithm::CUBIC,
40            QuicCongestionControlAlgorithm::BBR => CongestionControlAlgorithm::BBR,
41            QuicCongestionControlAlgorithm::BBR2 => CongestionControlAlgorithm::BBR2,
42        }
43    }
44}
45
46/// parse
47fn clap_parse_ports(s: &str) -> Result<Range<u16>, String> {
48    let splites = s.split("-");
49
50    let splites = splites.collect::<Vec<_>>();
51
52    if splites.len() == 2 {
53        Ok(Range {
54            start: splites[0].parse().map_err(|err| format!("{}", err))?,
55            end: splites[1].parse().map_err(|err| format!("{}", err))?,
56        })
57    } else if splites.len() == 1 {
58        let start = splites[0].parse().map_err(|err| format!("{}", err))?;
59        Ok(Range {
60            start,
61            end: start + 1,
62        })
63    } else {
64        Err(format!(
65            "Invalid port-range arg, the desired format is `a-b` or `a`"
66        ))
67    }
68}
69
70fn clap_parse_sockaddrs(s: &str) -> Result<Vec<SocketAddr>, String> {
71    let splits = s.split(":").collect::<Vec<_>>();
72
73    if splits.len() != 2 {
74        return Err(format!(
75            "Invalid address string: {}. the desired format is `ip_or_domain_name:port-range`",
76            s
77        ));
78    }
79
80    let mut parsed_addrs = vec![];
81
82    for port in clap_parse_ports(splits[1])? {
83        let mut addrs = (splits[0], port)
84            .to_socket_addrs()
85            .map_err(|err| err.to_string())?
86            .collect::<Vec<_>>();
87
88        parsed_addrs.append(&mut addrs);
89    }
90
91    Ok(parsed_addrs)
92}
93
94#[derive(Parser, Debug, Clone)]
95#[command(author, version, about, long_about = None)]
96pub struct QuicTunnelConfig {
97    /// The local listen on addresses.
98    #[arg(long, value_parser = clap_parse_sockaddrs)]
99    pub laddrs: SocketAddrs,
100
101    /// The forwarding to addresses.
102    #[arg(long, value_parser = clap_parse_sockaddrs)]
103    pub raddrs: SocketAddrs,
104
105    /// Specifies a file where trusted CA certificates are stored for the
106    /// purposes of quic certificate verification.
107    #[arg(long)]
108    pub ca_file: Option<PathBuf>,
109
110    /// The cert chain file path for quic connection.
111    ///
112    /// The content of `file` is parsed as a PEM-encoded leaf certificate,
113    /// followed by optional intermediate certificates.
114    #[arg(long)]
115    pub cert_chain_file: PathBuf,
116
117    /// The private key file path for quic connection.
118    ///
119    /// The content of `file` is parsed as a PEM-encoded private key.
120    #[arg(long)]
121    pub key_file: PathBuf,
122
123    /// Specifies the quic max transfer packet length
124    #[arg(long, default_value_t = 1350)]
125    pub mtu: usize,
126
127    /// Specifies the quic congestion control algorithm.
128    #[arg(long, value_enum, default_value_t = QuicCongestionControlAlgorithm::CUBIC)]
129    pub cc: QuicCongestionControlAlgorithm,
130
131    /// Bytes of incoming stream data to be buffered for each quic stream, set '0' to prevent receiving any data.
132    #[arg(long, default_value_t = 1048576)]
133    pub buf: u64,
134
135    /// Only allow `mux` number of concurrent quic streams to be open in one quic connection, set '0' to prevent open any quic stream.
136    #[arg(long, default_value_t = 100)]
137    pub mux: u64,
138
139    /// Quic connection max idle timeout, e.g., `10s`,`1m`
140    #[arg(long, value_parser = clap_parse_duration, default_value="5s")]
141    pub timeout: Duration,
142
143    /// Maximum number of connections between client and server
144    #[arg(long, default_value_t = 100)]
145    pub max_conns: usize,
146
147    /// Sets the maximum size of the connection window.
148    #[arg(long, default_value_t = 24 * 1024 * 1024)]
149    pub max_conn_win: u64,
150
151    /// Sets the maximum size of the stream window.
152    #[arg(long, default_value_t = 16 * 1024 * 1024)]
153    pub max_stream_win: u64,
154
155    /// The interval at which reverse proxy statistics are printed,
156    /// setting this value to `0s` stops the printing of statistics.
157    #[arg(long, value_parser = clap_parse_duration, default_value="1m")]
158    pub print_stats: Duration,
159}
160
161fn make_config(quic_tunn_config: &QuicTunnelConfig) -> Config {
162    let mut config = Config::new().unwrap();
163
164    config
165        .load_cert_chain_from_pem_file(quic_tunn_config.cert_chain_file.to_str().unwrap())
166        .unwrap();
167
168    config
169        .load_priv_key_from_pem_file(quic_tunn_config.key_file.to_str().unwrap())
170        .unwrap();
171
172    if let Some(ca_file) = &quic_tunn_config.ca_file {
173        config.verify_peer(true);
174
175        config
176            .load_verify_locations_from_file(ca_file.to_str().unwrap())
177            .unwrap();
178    }
179
180    config.set_application_protos(&[b"qtun"]).unwrap();
181
182    config.set_max_idle_timeout(quic_tunn_config.timeout.as_millis() as u64);
183    config.set_max_datagram_size(quic_tunn_config.mtu);
184    config.set_initial_max_data(quic_tunn_config.buf * quic_tunn_config.mux);
185    config.set_initial_max_stream_data_bidi_local(quic_tunn_config.buf);
186    config.set_initial_max_stream_data_bidi_remote(quic_tunn_config.buf);
187    config.set_initial_max_streams_bidi(quic_tunn_config.mux);
188    config.set_initial_max_streams_uni(quic_tunn_config.mux);
189    config.set_disable_active_migration(false);
190    config.set_cc_algorithm(quic_tunn_config.cc.clone().into());
191    config.set_max_connection_window(quic_tunn_config.max_conn_win);
192    config.set_max_stream_window(quic_tunn_config.max_stream_win);
193
194    config
195}
196
197fn print_stats<H: Handshaker + Sync + Send + 'static>(
198    quic_tun_config: &QuicTunnelConfig,
199    rproxy: Rproxy<H>,
200) {
201    if !quic_tun_config.print_stats.is_zero() {
202        let duration = quic_tun_config.print_stats.clone();
203
204        future_spawn(async move {
205            loop {
206                let stats = rproxy.stats();
207
208                log::info!("{}", stats);
209
210                sleep(duration).await.unwrap();
211            }
212        });
213    }
214}
215
216#[cfg(feature = "server")]
217pub async fn run_server() -> io::Result<()> {
218    use crate::server::TcpForwardHandshaker;
219    use hala_quic::QuicListener;
220    use hala_rproxy::listener::quic::QuicStreamListener;
221
222    let quic_tun_config = QuicTunnelConfig::parse();
223
224    let rproxy = Rproxy::new(TcpForwardHandshaker::new(
225        quic_tun_config.raddrs.as_slice(),
226    )?);
227
228    let quic_config = make_config(&quic_tun_config);
229
230    let quic_listener = QuicListener::bind(quic_tun_config.laddrs.as_slice(), quic_config)?;
231
232    print_stats(&quic_tun_config, rproxy.clone());
233
234    rproxy.accept(QuicStreamListener::from(quic_listener)).await;
235
236    Ok(())
237}
238
239#[cfg(feature = "client")]
240pub async fn run_client() -> io::Result<()> {
241    use hala_quic::QuicConnPool;
242    use hala_tcp::TcpListener;
243
244    use crate::client::QuicTunnHandshaker;
245
246    let quic_tun_config = QuicTunnelConfig::parse();
247
248    let quic_config = make_config(&quic_tun_config);
249
250    let quic_conn_pool = QuicConnPool::new(
251        quic_tun_config.max_conns,
252        quic_tun_config.raddrs.as_slice(),
253        quic_config,
254    )?;
255
256    let rproxy = Rproxy::new(QuicTunnHandshaker::from(quic_conn_pool));
257
258    let tcp_listener = TcpListener::bind(quic_tun_config.laddrs.as_slice())?;
259
260    print_stats(&quic_tun_config, rproxy.clone());
261
262    rproxy.accept(tcp_listener).await;
263
264    Ok(())
265}