From c8f1835e1a5770e762b3b812442007a1c884fa00 Mon Sep 17 00:00:00 2001 From: alterdekim Date: Mon, 19 Aug 2024 03:57:28 +0300 Subject: [PATCH] Changes to be committed: modified: Cargo.lock modified: Cargo.toml modified: src/client.rs modified: src/main.rs modified: src/server.rs --- Cargo.lock | 87 +++++++++++++++++++++++ Cargo.toml | 4 +- src/client.rs | 4 +- src/main.rs | 193 ++++++++++++++++++++++++++++++++++++++++---------- src/server.rs | 2 +- 5 files changed, 248 insertions(+), 42 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index bab81b3..e5a0097 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -152,6 +152,12 @@ dependencies = [ "rustc-demangle", ] +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + [[package]] name = "bincode" version = "1.3.3" @@ -395,6 +401,32 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "curve25519-dalek" +version = "4.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" +dependencies = [ + "cfg-if", + "cpufeatures", + "curve25519-dalek-derive", + "fiat-crypto", + "rustc_version", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.72", +] + [[package]] name = "either" version = "1.13.0" @@ -430,6 +462,12 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "fiat-crypto" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" + [[package]] name = "futures" version = "0.3.30" @@ -1214,6 +1252,15 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] + [[package]] name = "rustix" version = "0.38.34" @@ -1235,6 +1282,7 @@ dependencies = [ "aes-gcm", "aes-soft", "anyhow", + "base64", "bincode", "block-modes", "block-padding", @@ -1258,6 +1306,7 @@ dependencies = [ "tokio", "tun", "tun2", + "x25519-dalek", ] [[package]] @@ -1272,6 +1321,12 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "semver" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" + [[package]] name = "serde" version = "1.0.205" @@ -1772,6 +1827,18 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "x25519-dalek" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7e468321c81fb07fa7f4c636c3972b9100f0346e5b6a9f2bd0603a52f7ed277" +dependencies = [ + "curve25519-dalek", + "rand_core", + "serde", + "zeroize", +] + [[package]] name = "zerocopy" version = "0.7.35" @@ -1792,3 +1859,23 @@ dependencies = [ "quote", "syn 2.0.72", ] + +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.72", +] diff --git a/Cargo.toml b/Cargo.toml index 4a565ed..9420478 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,7 @@ tun = "0.6.1" serde = "1.0" serde_derive = "1.0.190" bincode = "1.3" -rand = { version = "0.8.5", features = ["small_rng"] } +rand = { version = "0.8.5", features = ["small_rng", "getrandom", "std_rng"] } anyhow = "1.0" ctrlc = "3.1" aes = "0.7" @@ -33,3 +33,5 @@ pnet = "0.35.0" net-route = "0.4.4" hex = "0.4" serde_yaml = "0.9.34" +x25519-dalek = { version = "2.0.1", features = ["getrandom", "static_secrets"] } +base64 = "0.22.1" diff --git a/src/client.rs b/src/client.rs index 996592e..50b1d02 100644 --- a/src/client.rs +++ b/src/client.rs @@ -11,7 +11,7 @@ use std::collections::HashMap; use std::process::Command; use tokio::io::AsyncReadExt; -use crate::{UDPVpnHandshake, UDPVpnPacket, VpnPacket, ClientConfiguration}; +use crate::{UDPVpnHandshake, UDPVpnPacket, VpnPacket, ClientConfiguration, UDPSerializable}; fn configure_routes() { let ip_output = Command::new("ip") @@ -111,7 +111,7 @@ pub async fn client_mode(client_config: ClientConfiguration) { } }); - let handshake = UDPVpnHandshake{}; + let handshake = UDPVpnHandshake{ public_key: client_config.client.public_key.into_bytes() }; sock_snd.send(&handshake.serialize()).await.unwrap(); loop { diff --git a/src/main.rs b/src/main.rs index 2ba8712..a256323 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,13 +1,16 @@ use tokio::{net::UdpSocket, sync::mpsc}; -use std::{fs, io::{self, Error, Read}, net::{IpAddr, SocketAddr}, sync::Arc, thread, time, str}; +use std::{fs, io::{self, Error, Read}, net::{IpAddr, Ipv4Addr, SocketAddr}, str, sync::Arc, thread, time}; use std::process::Command; -use clap::{App, Arg}; +use clap::{App, Arg, ArgMatches}; use env_logger::Builder; use log::{error, info, warn, LevelFilter}; use tun::platform::Device; use serde_derive::Serialize; use serde_derive::Deserialize; use std::str::FromStr; +use x25519_dalek::{StaticSecret, PublicKey}; +use rand::{rngs::StdRng, SeedableRng}; +use base64; //mod tcp_client; //mod tcp_server; @@ -37,25 +40,26 @@ struct UDPVpnPacket { data: Vec } -impl UDPVpnPacket { +impl UDPSerializable for UDPVpnPacket { fn serialize(&self) -> Vec { let h: &[u8] = &[1]; [h, &self.data[..]].concat() } } -struct UDPVpnHandshake {} +struct UDPVpnHandshake { + public_key: Vec +} -impl UDPVpnHandshake { +impl UDPSerializable for UDPVpnHandshake { fn serialize(&self) -> Vec { - [0, 9, 9, 9, 9, 9, 9].to_vec() + let h: &[u8] = &[0]; + [h, &self.public_key[..]].concat() } } -#[derive(Serialize, Deserialize, PartialEq, Debug)] -enum ServerMode { - VPN, - Hotspot +trait UDPSerializable { + fn serialize(&self) -> Vec; } #[derive(Serialize, Deserialize, PartialEq, Debug)] @@ -63,7 +67,7 @@ struct ServerInterface { bind_address: String, internal_address: String, private_key: String, - mode: ServerMode, + public_key: String, broadcast_mode: bool, keepalive: u8 } @@ -71,7 +75,7 @@ struct ServerInterface { #[derive(Serialize, Deserialize, PartialEq, Debug)] struct ServerPeer { public_key: String, - ip: IpAddr + ip: Ipv4Addr } #[derive(Serialize, Deserialize, PartialEq, Debug)] @@ -95,17 +99,19 @@ pub struct ServerConfiguration { } impl ServerConfiguration { - fn default() -> Self { + fn default(bind_address: &str, internal_address: &str, broadcast_mode: bool, keepalive: u8, obfs_type: ObfsProtocol) -> Self { + let mut csprng = StdRng::from_entropy(); + let secret = StaticSecret::new(&mut csprng); ServerConfiguration { interface: ServerInterface { - bind_address: String::from_str("0.0.0.0:8879").unwrap(), - internal_address: String::from_str("10.8.0.1").unwrap(), - private_key: String::new(), - mode: ServerMode::VPN, - broadcast_mode: true, - keepalive: 10 + bind_address: String::from_str(bind_address).unwrap(), + internal_address: String::from_str(internal_address).unwrap(), + private_key: base64::encode(secret.as_bytes()), + public_key: base64::encode(PublicKey::from(&secret).as_bytes()), + broadcast_mode, + keepalive }, peers: Vec::new(), - obfs: ObfsConfig { protocol: ObfsProtocol::DNSMask }, + obfs: ObfsConfig { protocol: obfs_type }, dns: DNSConfig { enabled: false, net_name: String::from_str("fridah.vpn").unwrap(), entries: Vec::new() } } } @@ -120,20 +126,22 @@ struct DNSConfig { #[derive(Serialize, Deserialize, PartialEq, Debug)] struct DNSEntry { - ip: IpAddr, + ip: Ipv4Addr, subdomain: String } #[derive(Serialize, Deserialize, PartialEq, Debug)] struct ClientInterface { private_key: String, + public_key: String, address: String } #[derive(Serialize, Deserialize, PartialEq, Debug)] struct EndpointInterface { public_key: String, - endpoint: String + endpoint: String, + keepalive: u8 } #[derive(Serialize, Deserialize, PartialEq, Debug)] @@ -143,12 +151,81 @@ pub struct ClientConfiguration { } impl ClientConfiguration { - fn default() -> Self { - ClientConfiguration { client: ClientInterface { private_key: String::new(), address: String::from_str("10.8.0.2").unwrap() }, - server: EndpointInterface { public_key: String::new(), endpoint: String::from_str("192.168.0.2:8879").unwrap() } } + fn default(endpoint: &str, keepalive: u8, public_key: &str, internal_address: &str) -> Self { + let mut csprng = StdRng::from_entropy(); + let secret = StaticSecret::new(&mut csprng); + ClientConfiguration { + client: ClientInterface { + private_key: base64::encode(secret.as_bytes()), + public_key: base64::encode(PublicKey::from(&secret).as_bytes()), + address: String::from_str(internal_address).unwrap() + }, + server: EndpointInterface { + public_key: String::from_str(public_key).unwrap(), + endpoint: String::from_str(endpoint).unwrap(), + keepalive + } + } } } +fn generate_server_config(matches: &ArgMatches, config_path: &str) { + let bind_address = matches.value_of("bind-address").expect("No bind address specified"); + let internal_address = matches.value_of("internal-address").expect("No internal address specified"); + let broadcast_mode = matches.value_of("broadcast-mode").is_some(); + let keepalive: u8 = matches.value_of("keepalive").unwrap().parse().expect("Keepalive argument should be a number"); + let obfs_type = match matches.value_of("obfs-type").expect("Obfs type should be specified") { + "dns" => ObfsProtocol::DNSMask, + "icmp" => ObfsProtocol::ICMPMask, + _ => ObfsProtocol::VEIL + }; + + fs::write(config_path, serde_yaml::to_string(&ServerConfiguration::default(bind_address, internal_address, broadcast_mode, keepalive, obfs_type)).unwrap()); +} + +fn generate_peer_config(matches: &ArgMatches, config_path: &str, cfg_raw: &String) { + let keepalive: u8 = matches.value_of("keepalive").unwrap().parse().expect("Keepalive argument should be a number"); + let grab_endpoint = matches.value_of("grab-endpoint").is_some(); + let endpoint = matches.value_of("endpoint").or(Some("0.0.0.0:0")).unwrap(); + let peer_cfg = matches.value_of("peer-cfg").expect("No peer cfg path specified"); + + let mut config: ServerConfiguration = serde_yaml::from_str(cfg_raw).expect("Bad server config file structure"); + + let mut prs = &mut config.peers[..]; + prs.sort_by(|a, b| a.ip.octets()[3].cmp(&b.ip.octets()[3])); + + let mut internal_address = prs.iter() + .map(|p| p.ip) + .collect::>() + .first() + .or(Some(&config.interface.internal_address.parse::().unwrap())) + .unwrap() + .clone(); + + internal_address = Ipv4Addr::new(internal_address.octets()[0], internal_address.octets()[1], internal_address.octets()[2], internal_address.octets()[3]+1); + + let cl_cfg = &ClientConfiguration::default(if grab_endpoint { &config.interface.bind_address } else { endpoint }, + keepalive, + &config.interface.public_key, + &internal_address.to_string()); + + config.peers.push(ServerPeer { public_key: cl_cfg.client.public_key.clone(), ip: internal_address.clone() }); + + fs::write(peer_cfg, serde_yaml::to_string(cl_cfg).unwrap()); + + fs::write(config_path, serde_yaml::to_string(&config).unwrap()); +} + +async fn init_server(cfg_raw: &str ) { + let config: ServerConfiguration = serde_yaml::from_str(cfg_raw).expect("Bad server config file structure"); + server::server_mode(config).await; +} + +async fn init_client(cfg_raw: &str) { + let config: ClientConfiguration = serde_yaml::from_str(cfg_raw).expect("Bad client config file structure"); + client::client_mode(config).await; +} + #[tokio::main] async fn main() { @@ -164,7 +241,7 @@ async fn main() { .arg(Arg::with_name("mode") .required(true) .index(1) - .possible_values(&["server", "client"]) + .possible_values(&["server", "client", "gen_cfg", "new_peer"]) .help("Runs the program in either server or client mode")) .arg(Arg::with_name("config") .long("config") @@ -172,30 +249,70 @@ async fn main() { .value_name("FILE") .help("The path to VPN configuration file") .takes_value(true)) + .arg(Arg::with_name("peer-cfg") + .long("peer-cfg") + .value_name("FILE") + .help("The path to VPN peer configuration file") + .takes_value(true)) + .arg(Arg::with_name("bind-address") + .long("bind-address") + .value_name("IP:PORT") + .help("The ip:port that would be used to bind server (config)") + .takes_value(true)) + .arg(Arg::with_name("endpoint") + .long("endpoint") + .value_name("IP:PORT") + .help("The ip:port that would be used by client to connect (config)") + .takes_value(true)) + .arg(Arg::with_name("internal-address") + .long("internal-address") + .value_name("IP") + .help("The address of VPN server in it's subnet (config)") + .takes_value(true)) + .arg(Arg::with_name("broadcast-mode") + .long("broadcast-mode") + .help("If set to true, then all incoming traffic with an unknown destination address will be forwarded to all peers (config)") + .takes_value(false)) + .arg(Arg::with_name("grab-endpoint") + .long("grab-endpoint") + .help("If set to true, the endpoint address for peers will be grabbed from server config (config)") + .takes_value(false)) + .arg(Arg::with_name("keepalive") + .long("keepalive") + .required(false) + .value_name("SECONDS") + .default_value("0") + .help("Keepalive packets interval (config)") + .takes_value(true)) + .arg(Arg::with_name("obfs-type") + .long("obfs-type") + .possible_values(&["dns", "icmp", "veil"]) + .takes_value(true) + .value_name("OBFS") + .help("Obfuscation protocol (config)")) .get_matches(); - let is_server_mode = matches.value_of("mode").unwrap() == "server"; + let mode = matches.value_of("mode").unwrap(); if let Some(config_path) = matches.value_of("config") { let data = fs::read(config_path); if data.is_err() { - warn!("There is no config file. Generating it."); - if is_server_mode { - fs::write(config_path, serde_yaml::to_string(&ServerConfiguration::default()).unwrap()); - return; + match mode { + "gen_cfg" => generate_server_config(&matches, config_path), + _ => error!("There is no config file.") } - fs::write(config_path, serde_yaml::to_string(&ClientConfiguration::default()).unwrap()); return; } - if is_server_mode { - let config: ServerConfiguration = serde_yaml::from_str(&String::from_utf8(data.unwrap()).unwrap()).unwrap(); - server::server_mode(config).await; - return; + let cfg_raw = &String::from_utf8(data.unwrap()).unwrap(); + + match mode { + "server" => init_server(cfg_raw).await, + "client" => init_client(cfg_raw).await, + "new_peer" => generate_peer_config(&matches, config_path, cfg_raw), + _ => error!("There is config file already") } - let config: ClientConfiguration = serde_yaml::from_str(&String::from_utf8(data.unwrap()).unwrap()).unwrap(); - client::client_mode(config).await; } } \ No newline at end of file diff --git a/src/server.rs b/src/server.rs index 0a03bf3..2340688 100644 --- a/src/server.rs +++ b/src/server.rs @@ -11,7 +11,7 @@ use std::collections::HashMap; use tokio::io::AsyncReadExt; use std::process::Command; -use crate::{ VpnPacket, ServerConfiguration }; +use crate::{ VpnPacket, ServerConfiguration, UDPSerializable }; pub async fn server_mode(server_config: ServerConfiguration) { info!("Starting server...");