Skip to main content

ombrac_client/config/
cli.rs

1use std::net::SocketAddr;
2use std::path::PathBuf;
3
4use clap::Parser;
5use clap::builder::Styles;
6use clap::builder::styling::{AnsiColor, Style};
7
8use ombrac_transport::quic::Congestion;
9
10use crate::config::{EndpointConfig, TlsMode, TransportConfig};
11
12/// Command-line arguments for the ombrac client
13#[derive(Parser, Debug)]
14#[command(version, about, long_about = None, styles = styles())]
15pub struct Args {
16    /// Path to the JSON configuration file
17    #[clap(long, short = 'c', value_name = "FILE")]
18    pub config: Option<PathBuf>,
19
20    /// Protocol Secret
21    #[clap(
22        long,
23        short = 'k',
24        help_heading = "Required",
25        value_name = "STR",
26        required_unless_present = "config"
27    )]
28    pub secret: Option<String>,
29
30    /// Address of the server to connect to
31    #[clap(
32        long,
33        short = 's',
34        help_heading = "Required",
35        value_name = "ADDR",
36        required_unless_present = "config"
37    )]
38    pub server: Option<String>,
39
40    /// Extended parameter of the protocol, used for authentication related information
41    #[clap(
42        long,
43        help_heading = "Protocol",
44        value_name = "STR",
45        alias = "handshake-option"
46    )]
47    pub auth_option: Option<String>,
48
49    #[clap(flatten)]
50    pub endpoint: CliEndpointConfig,
51
52    #[clap(flatten)]
53    pub transport: CliTransportConfig,
54
55    #[cfg(feature = "tracing")]
56    #[clap(flatten)]
57    pub logging: CliLoggingConfig,
58}
59
60/// CLI-specific endpoint configuration
61#[derive(Parser, Debug, Clone, Default)]
62pub struct CliEndpointConfig {
63    /// The address to bind for the HTTP/HTTPS server
64    #[cfg(feature = "endpoint-http")]
65    #[clap(long, value_name = "ADDR", help_heading = "Endpoint")]
66    pub http: Option<SocketAddr>,
67
68    /// The address to bind for the SOCKS server
69    #[cfg(feature = "endpoint-socks")]
70    #[clap(long, value_name = "ADDR", help_heading = "Endpoint")]
71    pub socks: Option<SocketAddr>,
72
73    #[cfg(feature = "endpoint-tun")]
74    #[clap(flatten)]
75    pub tun: Option<CliTunConfig>,
76}
77
78impl CliEndpointConfig {
79    /// Convert CLI endpoint config to internal EndpointConfig
80    pub fn into_endpoint_config(self) -> EndpointConfig {
81        EndpointConfig {
82            #[cfg(feature = "endpoint-http")]
83            http: self.http,
84            #[cfg(feature = "endpoint-socks")]
85            socks: self.socks,
86            #[cfg(feature = "endpoint-tun")]
87            tun: self.tun.map(|t| t.into_tun_config()),
88        }
89    }
90}
91
92/// CLI-specific TUN configuration
93#[cfg(feature = "endpoint-tun")]
94#[derive(Parser, Debug, Clone, Default)]
95pub struct CliTunConfig {
96    /// Use a pre-existing TUN device by providing its file descriptor
97    /// `tun_ipv4`, `tun_ipv6`, and `tun_mtu` will be ignored
98    #[clap(long, help_heading = "Endpoint", value_name = "FD")]
99    pub tun_fd: Option<i32>,
100
101    /// The IPv4 address and subnet for the TUN device, in CIDR notation
102    #[clap(long, help_heading = "Endpoint", value_name = "CIDR")]
103    pub tun_ipv4: Option<String>,
104
105    /// The IPv6 address and subnet for the TUN device, in CIDR notation
106    #[clap(long, help_heading = "Endpoint", value_name = "CIDR")]
107    pub tun_ipv6: Option<String>,
108
109    /// The Maximum Transmission Unit (MTU) for the TUN device. [default: 1500]
110    #[clap(long, help_heading = "Endpoint", value_name = "MTU")]
111    pub tun_mtu: Option<u16>,
112
113    /// The IPv4 address pool for the built-in fake DNS server, in CIDR notation. [default: 198.18.0.0/16]
114    #[clap(long, help_heading = "Endpoint", value_name = "CIDR")]
115    pub fake_dns: Option<String>,
116
117    /// Disable UDP traffic to port 443
118    #[clap(long, help_heading = "Endpoint", value_name = "BOOL")]
119    pub disable_udp_443: Option<bool>,
120}
121
122#[cfg(feature = "endpoint-tun")]
123impl CliTunConfig {
124    /// Convert CLI TUN config to internal TunConfig
125    pub fn into_tun_config(self) -> crate::config::TunConfig {
126        crate::config::TunConfig {
127            tun_fd: self.tun_fd,
128            tun_ipv4: self.tun_ipv4,
129            tun_ipv6: self.tun_ipv6,
130            tun_mtu: self.tun_mtu,
131            fake_dns: self.fake_dns,
132            disable_udp_443: self.disable_udp_443,
133        }
134    }
135}
136
137/// CLI-specific transport configuration
138#[derive(Parser, Debug, Clone)]
139pub struct CliTransportConfig {
140    /// The address to bind for transport
141    #[clap(long, help_heading = "Transport", value_name = "ADDR")]
142    pub bind: Option<SocketAddr>,
143
144    /// Name of the server to connect (derived from `server` if not provided)
145    #[clap(long, help_heading = "Transport", value_name = "STR")]
146    pub server_name: Option<String>,
147
148    /// Set the TLS mode for the connection
149    #[clap(long, value_enum, help_heading = "Transport")]
150    pub tls_mode: Option<TlsMode>,
151
152    /// Path to the Certificate Authority (CA) certificate file
153    /// in 'TLS' mode, if not provided, the system's default root certificates are used
154    #[clap(long, help_heading = "Transport", value_name = "FILE")]
155    pub ca_cert: Option<PathBuf>,
156
157    /// Path to the client's TLS certificate for mTLS
158    #[clap(long, help_heading = "Transport", value_name = "FILE")]
159    pub client_cert: Option<PathBuf>,
160
161    /// Path to the client's TLS private key for mTLS
162    #[clap(long, help_heading = "Transport", value_name = "FILE")]
163    pub client_key: Option<PathBuf>,
164
165    /// Enable 0-RTT for faster connection establishment
166    #[clap(long, help_heading = "Transport", value_name = "BOOL")]
167    pub zero_rtt: Option<bool>,
168
169    /// Application-Layer protocol negotiation (ALPN) protocols [default: h3]
170    #[clap(
171        long,
172        help_heading = "Transport",
173        value_name = "PROTOCOLS",
174        value_delimiter = ','
175    )]
176    pub alpn_protocols: Option<Vec<Vec<u8>>>,
177
178    /// Congestion control algorithm to use (e.g. bbr, cubic, newreno) [default: bbr]
179    #[clap(long, help_heading = "Transport", value_name = "ALGORITHM")]
180    pub congestion: Option<Congestion>,
181
182    /// Initial congestion window size in bytes
183    #[clap(long, help_heading = "Transport", value_name = "NUM")]
184    pub cwnd_init: Option<u64>,
185
186    /// Maximum idle time (in milliseconds) before closing the connection [default: 30000]
187    #[clap(long, help_heading = "Transport", value_name = "TIME")]
188    pub idle_timeout: Option<u64>,
189
190    /// Keep-alive interval (in milliseconds) [default: 8000]
191    #[clap(long, help_heading = "Transport", value_name = "TIME")]
192    pub keep_alive: Option<u64>,
193
194    /// Maximum number of bidirectional streams that can be open simultaneously [default: 100]
195    #[clap(long, help_heading = "Transport", value_name = "NUM")]
196    pub max_streams: Option<u64>,
197}
198
199/// CLI-specific logging configuration
200#[cfg(feature = "tracing")]
201#[derive(Parser, Debug, Clone)]
202pub struct CliLoggingConfig {
203    /// Logging level (e.g., INFO, WARN, ERROR) [default: INFO]
204    #[clap(long, help_heading = "Logging", value_name = "LEVEL")]
205    pub log_level: Option<String>,
206}
207
208impl CliTransportConfig {
209    /// Convert CLI transport config to internal TransportConfig
210    pub fn into_transport_config(self) -> TransportConfig {
211        TransportConfig {
212            bind: self.bind,
213            server_name: self.server_name,
214            tls_mode: self.tls_mode,
215            ca_cert: self.ca_cert,
216            client_cert: self.client_cert,
217            client_key: self.client_key,
218            zero_rtt: self.zero_rtt,
219            alpn_protocols: self.alpn_protocols,
220            congestion: self.congestion,
221            cwnd_init: self.cwnd_init,
222            idle_timeout: self.idle_timeout,
223            keep_alive: self.keep_alive,
224            max_streams: self.max_streams,
225        }
226    }
227}
228
229#[cfg(feature = "tracing")]
230impl CliLoggingConfig {
231    /// Convert CLI logging config to internal LoggingConfig
232    pub fn into_logging_config(self) -> crate::config::LoggingConfig {
233        crate::config::LoggingConfig {
234            log_level: self.log_level,
235        }
236    }
237}
238
239/// Represents CLI arguments as a partial configuration
240#[derive(Debug)]
241pub struct CliConfig {
242    pub secret: Option<String>,
243    pub server: Option<String>,
244    pub auth_option: Option<String>,
245    pub endpoint: EndpointConfig,
246    pub transport: TransportConfig,
247    #[cfg(feature = "tracing")]
248    pub logging: crate::config::LoggingConfig,
249}
250
251impl Args {
252    /// Parse CLI arguments and convert to CliConfig
253    pub fn parse_to_config() -> CliConfig {
254        let args = Self::parse();
255        CliConfig {
256            secret: args.secret,
257            server: args.server,
258            auth_option: args.auth_option,
259            endpoint: args.endpoint.into_endpoint_config(),
260            transport: args.transport.into_transport_config(),
261            #[cfg(feature = "tracing")]
262            logging: args.logging.into_logging_config(),
263        }
264    }
265
266    /// Get the config file path if specified
267    pub fn config_path() -> Option<PathBuf> {
268        Self::parse().config
269    }
270}
271
272fn styles() -> Styles {
273    Styles::styled()
274        .header(Style::new().bold().fg_color(Some(AnsiColor::Green.into())))
275        .usage(Style::new().bold().fg_color(Some(AnsiColor::Green.into())))
276        .literal(Style::new().bold().fg_color(Some(AnsiColor::Cyan.into())))
277        .placeholder(Style::new().fg_color(Some(AnsiColor::Cyan.into())))
278        .valid(Style::new().bold().fg_color(Some(AnsiColor::Cyan.into())))
279        .invalid(Style::new().bold().fg_color(Some(AnsiColor::Yellow.into())))
280        .error(Style::new().bold().fg_color(Some(AnsiColor::Red.into())))
281}