Changes to be committed:

modified:   Cargo.lock
	modified:   Cargo.toml
	modified:   src/client.rs
	modified:   src/main.rs
	modified:   src/server.rs
This commit is contained in:
Michael Wain 2024-08-19 03:57:28 +03:00
parent 8bf6d57ba8
commit c8f1835e1a
5 changed files with 248 additions and 42 deletions

87
Cargo.lock generated
View File

@ -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",
]

View File

@ -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"

View File

@ -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 {

View File

@ -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<u8>
}
impl UDPVpnPacket {
impl UDPSerializable for UDPVpnPacket {
fn serialize(&self) -> Vec<u8> {
let h: &[u8] = &[1];
[h, &self.data[..]].concat()
}
}
struct UDPVpnHandshake {}
struct UDPVpnHandshake {
public_key: Vec<u8>
}
impl UDPVpnHandshake {
impl UDPSerializable for UDPVpnHandshake {
fn serialize(&self) -> Vec<u8> {
[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<u8>;
}
#[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::<Vec<Ipv4Addr>>()
.first()
.or(Some(&config.interface.internal_address.parse::<Ipv4Addr>().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;
}
}

View File

@ -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...");