Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ async-trait = "0.1.77"
bytes = "1.5.0"
bytesize = { version = "1.3.0", features = ["serde"] }
clap = { version = "4.4.7", features = ["derive"] }
clap_mangen = "0.2"
ctrlc = { version = "3.4.6", features = ["termination"] }
delegate = "0.12.0"
educe = { version = "0.6.0", default-features = false, features = ["Debug"] }
Expand Down
8 changes: 5 additions & 3 deletions Earthfile
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,12 @@
SET target = "riscv64gc-unknown-linux-gnu"
END

DO lib-rust+CARGO --args="build --release --features io-uring --target=$target" --output="$target/release/lightway-(client|server)$"
DO lib-rust+CARGO --args="build --release --features io-uring --target=$target"

SAVE ARTIFACT ./target/$target/release/lightway-client AS LOCAL ./target/$target/release/
SAVE ARTIFACT ./target/$target/release/lightway-server AS LOCAL ./target/$target/release/
DO lib-rust+COPY_OUTPUT --output="$target/release/lightway-(client|server)$"

Check failure on line 62 in Earthfile

View workflow job for this annotation

GitHub Actions / earthly (fmt)

Error

The command RUN set -e; cargo $args; cargo sweep -r -t $EARTHLY_SWEEP_DAYS; cargo sweep -r -i; $EARTHLY_FUNCTIONS_HOME/copy-output.sh "$output"; did not complete successfully. Exit code 1

Check failure on line 62 in Earthfile

View workflow job for this annotation

GitHub Actions / earthly (lint)

Error

The command RUN set -e; cargo $args; cargo sweep -r -t $EARTHLY_SWEEP_DAYS; cargo sweep -r -i; $EARTHLY_FUNCTIONS_HOME/copy-output.sh "$output"; did not complete successfully. Exit code 101
DO lib-rust+COPY_OUTPUT --output="$target/release/man/lightway-(client|server).1$"

SAVE ARTIFACT ./target/$target/release AS LOCAL ./target/$target/release

# build-arm64 build for arm64. Support building from an amd64 or arm64 host
build-arm64:
Expand Down
10 changes: 10 additions & 0 deletions lightway-client/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ repository.workspace = true
edition.workspace = true
authors.workspace = true
license.workspace = true
build = "build.rs"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

Expand Down Expand Up @@ -40,6 +41,15 @@ tracing.workspace = true
tracing-subscriber = { workspace = true}
twelf.workspace = true

[build-dependencies]
anyhow = { workspace = true }
bytesize = { workspace = true }
clap = { workspace = true }
clap_mangen = { workspace = true }
lightway-app-utils = { workspace = true }
lightway-core = { workspace = true }
twelf = { workspace = true }

[dev-dependencies]
more-asserts.workspace = true
test-case.workspace = true
103 changes: 103 additions & 0 deletions lightway-client/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
use std::env;
use std::fs;
use std::path::Path;

use clap::CommandFactory;

#[path = "src/args.rs"]
mod args;

fn create_client_command() -> clap::Command {
// Get the base command from the derive macro
let cmd = args::Config::command();

// Add the detailed help text
let long_about = [
"Lightway is a modern VPN client that implements the Lightway protocol. It provides",
"a fast, secure, and reliable VPN connection using modern cryptographic algorithms",
"and optimized network protocols.",
"",
"The client connects to a Lightway server and establishes a secure tunnel for",
"routing network traffic. It supports both TCP and UDP transport protocols and",
"includes advanced features like Path MTU Discovery, keepalive mechanisms, and",
"io_uring optimization on Linux.",
"",
"Configuration can be provided via YAML files, environment variables (LW_CLIENT_*),",
"or command-line arguments. Command-line arguments have the highest priority.",
"",
"Security Note: Avoid passing passwords via command-line arguments as they may be",
"visible to other users. Use configuration files or environment variables instead.",
]
.join("\n");

let after_help = [
"CONFIGURATION FILE:",
"The client requires a configuration file in YAML format. Environment variables can",
"override configuration file settings using the LW_CLIENT_ prefix. Command-line",
"arguments have the highest priority.",
"",
"Example configuration:",
" mode: tcp",
" server: \"vpn.example.com:27690\"",
" user: \"myuser\"",
" password: \"mypassword\"",
" ca_cert: \"/etc/lightway/ca.crt\"",
" log_level: info",
"",
"AUTHENTICATION:",
"The client supports two authentication methods:",
" • Username/Password: Traditional username and password authentication",
" • JWT Token: JSON Web Token authentication using RS256 algorithm",
"",
"If both token and username/password are provided, token authentication takes precedence.",
"",
"EXAMPLES:",
" lightway-client --config-file /etc/lightway/client.yaml",
" lightway-client -c client.yaml --server vpn.example.com:27690 --log-level debug",
" lightway-client -c client.yaml --mode udp --enable-pmtud",
"",
"FILES:",
" /etc/lightway/client.yaml System-wide client configuration",
" ~/.config/lightway/client.yaml User-specific client configuration",
" ./ca_cert.crt Default CA certificate location",
"",
"ENVIRONMENT:",
" LW_CLIENT_SERVER Server address",
" LW_CLIENT_USER Username for authentication",
" LW_CLIENT_PASSWORD Password for authentication",
" LW_CLIENT_LOG_LEVEL Logging level",
"",
"SEE ALSO:",
" lightway-server(1), ip(8), iptables(8)",
"",
"REPORT BUGS:",
" https://github.com/expressvpn/lightway/issues",
]
.join("\n");

cmd.long_about(long_about)
.after_help(after_help)
}

fn main() -> std::io::Result<()> {
// Generate man page using clap_mangen
let cmd = create_client_command();
let man = clap_mangen::Man::new(cmd);
let mut buffer = Vec::new();
man.render(&mut buffer)?;

// We can't use CARGO_TARGET_DIR in build.rs, ugly hack to get the target directory
let out_dir = env::var("OUT_DIR").unwrap();
let target_path = Path::new(&out_dir).ancestors().nth(3).unwrap();

// Create man directory if it doesn't exist
let man_path = target_path.join("man");
fs::create_dir_all(&man_path).unwrap();

// Write to file
let name = env::var("CARGO_PKG_NAME").unwrap();
let manpage = man_path.join(format!("{}.1", name));
fs::write(manpage, buffer)?;

Ok(())
}
165 changes: 101 additions & 64 deletions lightway-client/src/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,129 +8,166 @@ use twelf::config;

#[config]
#[derive(Parser, Debug)]
#[command(about = "A lightway client")]
#[command(
about = "Lightway client - high-performance, secure, reliable VPN protocol in Rust",
version,
author = "ExpressVPN <[email protected]>",
after_help = concat!(
"EXAMPLES:\n",
" lightway-client -c client.yaml\n",
" lightway-client -c client.yaml --server vpn.example.com:27690\n",
" lightway-client -c client.yaml --mode udp --enable-pmtud\n",
"\n",
"See lightway-client(1) manpage for detailed configuration and usage information."
)
)]
pub struct Config {
/// Config File to load
#[clap(short, long)]
/// Configuration file path (YAML format)
///
/// Supports both absolute and relative paths
#[clap(short, long, value_name = "FILE")]
pub config_file: PathBuf,

/// Connection mode
#[clap(short, long, value_enum, default_value_t = ConnectionType::Tcp)]
/// Transport protocol to use for VPN connection
/// TCP provides reliability, UDP provides better performance
#[clap(short, long, value_enum, default_value_t = ConnectionType::Tcp, value_name = "PROTOCOL")]
pub mode: ConnectionType,

/// Auth token
/// If both token and user/pass are provided, token auth will
/// be used. user/pass will be ignored in this case
#[clap(long, hide = true)]
/// JWT authentication token (takes precedence over username/password)
/// Use configuration file or environment variable instead of CLI argument
#[clap(long, value_name = "TOKEN", hide = true)]
pub token: Option<String>,

/// Username for auth
#[clap(short, long, hide = true)]
/// Username for authentication
/// Use configuration file or environment variable instead of CLI argument
#[clap(short, long, value_name = "USER")]
pub user: Option<String>,

/// Password for auth
#[clap(short, long, hide = true)]
/// Password for authentication
/// WARNING: Visible to other users when passed via CLI. Use config file or LW_CLIENT_PASSWORD env var
#[clap(short, long, value_name = "PASSWORD")]
pub password: Option<String>,

/// CA certificate
#[clap(long, default_value = "./ca_cert.crt")]
/// Path to CA certificate file for server validation
/// Ensures secure connection to authentic Lightway server
#[clap(long, default_value = "./ca_cert.crt", value_name = "FILE")]
pub ca_cert: PathBuf,

/// Outside (wire) MTU
#[clap(long, default_value_t = MAX_OUTSIDE_MTU)]
/// Maximum Transmission Unit for network packets
/// Adjust based on your network infrastructure to avoid fragmentation
#[clap(long, default_value_t = MAX_OUTSIDE_MTU, value_name = "SIZE")]
pub outside_mtu: usize,

/// Inside (tunnel) MTU (requires `CAP_NET_ADMIN`)
#[clap(long)]
/// MTU for tunnel interface (requires CAP_NET_ADMIN capability)
/// Override default MTU of tunnel device for performance tuning
#[clap(long, value_name = "SIZE")]
pub inside_mtu: Option<u16>,

/// Tun device name to use
#[clap(short, long, default_value = None)]
/// TUN device name (leave empty for auto-assignment)
/// On macOS, must follow format 'utun[0-9]+' or leave empty
#[clap(short, long, value_name = "NAME")]
pub tun_name: Option<String>,

/// Local IP to use in Tun device
#[clap(long, default_value = "100.64.0.6")]
/// Local IP address for tunnel interface
/// Must be within the same subnet as peer IP
#[clap(long, default_value = "100.64.0.6", value_name = "IP")]
pub tun_local_ip: Ipv4Addr,

/// Peer IP to use in Tun device
#[clap(long, default_value = "100.64.0.5")]
/// Peer IP address for tunnel interface
/// Represents the server endpoint within the tunnel
#[clap(long, default_value = "100.64.0.5", value_name = "IP")]
pub tun_peer_ip: Ipv4Addr,

/// DNS IP to use in Tun device
#[clap(long, default_value = "100.64.0.1")]
/// DNS server IP address for tunnel traffic
/// Used for resolving domain names through the VPN
#[clap(long, default_value = "100.64.0.1", value_name = "IP")]
pub tun_dns_ip: Ipv4Addr,

/// Cipher to use for encryption
#[clap(long, value_enum, default_value_t = Cipher::Aes256)]
/// Encryption cipher algorithm
/// Both ciphers offer strong security.
/// However, if hardware acceleration for AES-256 is not available,
/// ChaCha20 may provide better performance in software implementations
#[clap(long, value_enum, default_value_t = Cipher::Aes256, value_name = "CIPHER")]
pub cipher: Cipher,

/// Enable Post Quantum Crypto
/// Enable Post-Quantum Cryptography
/// Provides protection against future quantum computing attacks
#[cfg(feature = "postquantum")]
#[clap(long, default_value_t)]
#[clap(long)]
pub enable_pqc: bool,

/// Interval between keepalives
#[clap(long, default_value = "0s")]
/// Interval between keepalive packets (0s = disabled)
/// Helps maintain connection through NAT devices and firewalls
#[clap(long, default_value = "0s", value_name = "DURATION")]
pub keepalive_interval: Duration,

/// Keepalive timeout
#[clap(long, default_value = "0s")]
/// Timeout for keepalive responses (0s = disabled)
/// Connection considered dead if no response within this time
#[clap(long, default_value = "0s", value_name = "DURATION")]
pub keepalive_timeout: Duration,

/// Socket send buffer size
#[clap(long)]
/// Socket send buffer size for performance tuning
/// Larger buffers may improve throughput on high-bandwidth connections
#[clap(long, value_name = "SIZE")]
pub sndbuf: Option<ByteSize>,
/// Socket receive buffer size
#[clap(long)]
/// Socket receive buffer size for performance tuning
/// Larger buffers may improve throughput on high-bandwidth connections
#[clap(long, value_name = "SIZE")]
pub rcvbuf: Option<ByteSize>,

/// Log level to use
#[clap(long, value_enum, default_value_t = LogLevel::Info)]
/// Logging verbosity level
/// Use 'debug' or 'trace' for troubleshooting connection issues
#[clap(long, value_enum, default_value_t = LogLevel::Info, value_name = "LEVEL")]
pub log_level: LogLevel,

/// Enable PMTU discovery for [`ConnectionType::Udp`] connections
/// Enable Path MTU Discovery for UDP connections
/// Automatically determines optimal packet size for the network path
#[clap(long)]
pub enable_pmtud: bool,

/// Base MTU to use for PMTU discovery
#[clap(long)]
/// Starting MTU size for Path MTU Discovery process
/// Only used when --enable-pmtud is set
#[clap(long, value_name = "SIZE")]
pub pmtud_base_mtu: Option<u16>,

/// Enable IO-uring interface for Tunnel
#[clap(long, default_value_t)]
/// Enable io_uring for high-performance tunnel I/O (Linux only)
/// Provides better performance but requires recent Linux kernel
#[clap(long)]
pub enable_tun_iouring: bool,

/// IO-uring submission queue count. Only applicable when
/// `enable_tun_iouring` is `true`
// Any value more than 1024 negatively impact the throughput
#[clap(long, default_value_t = 1024)]
/// io_uring submission queue size (max 1024 for optimal performance)
/// Only used when --enable-tun-iouring is enabled
#[clap(long, default_value_t = 1024, value_name = "COUNT")]
pub iouring_entry_count: usize,

/// IO-uring sqpoll idle time. If non-zero use a kernel thread to
/// perform submission queue polling. After the given idle time
/// the thread will go to sleep.
#[clap(long, default_value = "100ms")]
/// io_uring kernel polling idle time (0 = disabled)
/// Uses kernel thread for polling; reduces CPU usage but may increase latency
#[clap(long, default_value = "100ms", value_name = "DURATION")]
pub iouring_sqpoll_idle_time: Duration,

/// Server domain name
#[clap(long, default_value = None)]
/// Server domain name for certificate validation
/// Used to verify server certificate matches expected hostname
#[clap(long, value_name = "DOMAIN")]
pub server_dn: Option<String>,

/// Server to connect to
#[clap(short, long, default_value_t)]
/// Server address to connect to (host:port)
/// Can be IP address or domain name with port number
#[clap(short, long, value_name = "ADDRESS")]
pub server: String,

/// Enable inside packet encoding once lightway connects
/// Only used if a codec is set
#[clap(short, long, default_value_t)]
/// Enable packet encoding after connection
/// Provides additional traffic encoding when codec is configured
#[clap(short, long)]
pub enable_inside_pkt_encoding_at_connect: bool,

/// File path to save wireshark keylog
/// Path to save TLS keylog for Wireshark decryption (only available with `debug` feature)
/// Enables traffic analysis and debugging of encrypted connections
#[cfg(feature = "debug")]
#[clap(long, default_value = None)]
#[clap(long, value_name = "FILE")]
pub keylog: Option<PathBuf>,

/// Enable WolfSSL debug logging
/// Enable detailed TLS/SSL debug logging (only available with `debug` feature)
/// Provides verbose cryptographic handshake information
#[cfg(feature = "debug")]
#[clap(long)]
pub tls_debug: bool,
Expand Down
Loading
Loading