diff --git a/Cargo.lock b/Cargo.lock index d38db88..bab81b3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -414,6 +414,12 @@ dependencies = [ "termcolor", ] +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + [[package]] name = "errno" version = "0.3.9" @@ -556,6 +562,12 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + [[package]] name = "hermit-abi" version = "0.1.19" @@ -601,6 +613,16 @@ dependencies = [ "phf", ] +[[package]] +name = "indexmap" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93ead53efc7ea8ed3cfb0c79fc8023fbb782a5432b52830b6518941cebe6505c" +dependencies = [ + "equivalent", + "hashbrown", +] + [[package]] name = "inout" version = "0.1.3" @@ -640,6 +662,12 @@ dependencies = [ "either", ] +[[package]] +name = "itoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" + [[package]] name = "lazy_static" version = "1.5.0" @@ -1201,7 +1229,7 @@ dependencies = [ [[package]] name = "rustvpn" -version = "0.1.0" +version = "0.1.2" dependencies = [ "aes 0.7.5", "aes-gcm", @@ -1225,12 +1253,19 @@ dependencies = [ "rand", "serde", "serde_derive", + "serde_yaml", "socket2 0.4.10", "tokio", "tun", "tun2", ] +[[package]] +name = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + [[package]] name = "scopeguard" version = "1.2.0" @@ -1257,6 +1292,19 @@ dependencies = [ "syn 2.0.72", ] +[[package]] +name = "serde_yaml" +version = "0.9.34+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" +dependencies = [ + "indexmap", + "itoa", + "ryu", + "serde", + "unsafe-libyaml", +] + [[package]] name = "shlex" version = "1.3.0" @@ -1473,6 +1521,12 @@ dependencies = [ "subtle", ] +[[package]] +name = "unsafe-libyaml" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" + [[package]] name = "vec_map" version = "0.8.2" diff --git a/Cargo.toml b/Cargo.toml index da529e2..4a565ed 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rustvpn" -version = "0.1.0" +version = "0.1.2" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -31,4 +31,5 @@ ctrlc2 = "3.5" crossbeam-channel = "0.5.13" pnet = "0.35.0" net-route = "0.4.4" -hex = "0.4" \ No newline at end of file +hex = "0.4" +serde_yaml = "0.9.34" diff --git a/README.md b/README.md index 48d6203..db3dcf5 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,34 @@ # Frida -A basic VPN tunnel with data obfuscation. +![GitHub last commit](https://img.shields.io/github/last-commit/alterdekim/Frida) +![GitHub Release Date](https://img.shields.io/github/release-date/alterdekim/Frida) +![Jenkins Build](https://img.shields.io/jenkins/build) +![docs.rs](https://img.shields.io/docsrs/:crate) + +A lightweight VPN software, focused on scalability, traffic obfuscation and simplicity. + +## Documentation + +Check the [repository wiki]() to access the documentation, tutorials. + +## Installation + +On Linux, you can run this in a terminal (sudo required): + +``` +curl --proto '=https' --tlsv1.2 -sSf https://get-frida.awain.net | sh +``` + +Also you can download latest version from the releases page. + +## Android / IOS + +There is an app for both Android and IOS devices. + +### Android links + - Google play: ... + - Github: ... + +### IOS links + - Github: ... diff --git a/src/client.rs b/src/client.rs index 065c6dd..996592e 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}; +use crate::{UDPVpnHandshake, UDPVpnPacket, VpnPacket, ClientConfiguration}; fn configure_routes() { let ip_output = Command::new("ip") @@ -58,15 +58,15 @@ fn configure_routes() { } } -pub async fn client_mode(remote_addr: String) { +pub async fn client_mode(client_config: ClientConfiguration) { info!("Starting client..."); let mut config = tun2::Configuration::default(); - config.address("10.8.0.2"); - config.netmask("128.0.0.0"); - config.destination("0.0.0.0"); - config.name("tun0"); - config.up(); + config.address(&client_config.client.address) + .netmask("128.0.0.0") + .destination("0.0.0.0") + .name("tun0") + .up(); #[cfg(target_os = "linux")] config.platform_config(|config| { @@ -80,7 +80,7 @@ pub async fn client_mode(remote_addr: String) { configure_routes(); let sock = UdpSocket::bind("0.0.0.0:59611").await.unwrap(); - sock.connect(&remote_addr).await.unwrap(); + sock.connect(&client_config.server.endpoint).await.unwrap(); let sock_rec = Arc::new(sock); let sock_snd = sock_rec.clone(); diff --git a/src/main.rs b/src/main.rs index 805c950..2ba8712 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,22 +1,21 @@ use tokio::{net::UdpSocket, sync::mpsc}; -use std::{io::{self, Error, Read}, net::SocketAddr, sync::Arc, thread, time}; +use std::{fs, io::{self, Error, Read}, net::{IpAddr, SocketAddr}, sync::Arc, thread, time, str}; use std::process::Command; use clap::{App, Arg}; use env_logger::Builder; -use log::{error, info, LevelFilter}; +use log::{error, info, warn, LevelFilter}; use tun::platform::Device; use serde_derive::Serialize; use serde_derive::Deserialize; +use std::str::FromStr; -mod tcp_client; -mod tcp_server; +//mod tcp_client; +//mod tcp_server; mod server; mod client; struct VpnPacket { - //start: Vec data: Vec - //end: Vec } impl VpnPacket { @@ -34,11 +33,8 @@ impl VpnPacket { } } - struct UDPVpnPacket { - //start: Vec data: Vec - //end: Vec } impl UDPVpnPacket { @@ -56,6 +52,103 @@ impl UDPVpnHandshake { } } +#[derive(Serialize, Deserialize, PartialEq, Debug)] +enum ServerMode { + VPN, + Hotspot +} + +#[derive(Serialize, Deserialize, PartialEq, Debug)] +struct ServerInterface { + bind_address: String, + internal_address: String, + private_key: String, + mode: ServerMode, + broadcast_mode: bool, + keepalive: u8 +} + +#[derive(Serialize, Deserialize, PartialEq, Debug)] +struct ServerPeer { + public_key: String, + ip: IpAddr +} + +#[derive(Serialize, Deserialize, PartialEq, Debug)] +enum ObfsProtocol { + DNSMask, + ICMPMask, + VEIL +} + +#[derive(Serialize, Deserialize, PartialEq, Debug)] +struct ObfsConfig { + protocol: ObfsProtocol +} + +#[derive(Serialize, Deserialize, PartialEq, Debug)] +pub struct ServerConfiguration { + interface: ServerInterface, + peers: Vec, + obfs: ObfsConfig, + dns: DNSConfig +} + +impl ServerConfiguration { + fn default() -> Self { + 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 + }, + peers: Vec::new(), + obfs: ObfsConfig { protocol: ObfsProtocol::DNSMask }, + dns: DNSConfig { enabled: false, net_name: String::from_str("fridah.vpn").unwrap(), entries: Vec::new() } + } + } +} + +#[derive(Serialize, Deserialize, PartialEq, Debug)] +struct DNSConfig { + enabled: bool, + net_name: String, + entries: Vec +} + +#[derive(Serialize, Deserialize, PartialEq, Debug)] +struct DNSEntry { + ip: IpAddr, + subdomain: String +} + +#[derive(Serialize, Deserialize, PartialEq, Debug)] +struct ClientInterface { + private_key: String, + address: String +} + +#[derive(Serialize, Deserialize, PartialEq, Debug)] +struct EndpointInterface { + public_key: String, + endpoint: String +} + +#[derive(Serialize, Deserialize, PartialEq, Debug)] +pub struct ClientConfiguration { + client: ClientInterface, + server: EndpointInterface +} + +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() } } + } +} + #[tokio::main] async fn main() { @@ -65,7 +158,7 @@ async fn main() { .init(); let matches = App::new("Frida VPN") - .version("1.0") + .version("0.1.2") .author("alterwain") .about("VPN software") .arg(Arg::with_name("mode") @@ -73,34 +166,36 @@ async fn main() { .index(1) .possible_values(&["server", "client"]) .help("Runs the program in either server or client mode")) - .arg(Arg::with_name("vpn-server") - .long("vpn-server") - .value_name("IP") - .help("The IP address of the VPN server to connect to (client mode only)") - .takes_value(true)) - .arg(Arg::with_name("bind-to") - .long("bind-to") - .value_name("IP") - .help("The IP address of the VPN server to bind to (server mode only)") + .arg(Arg::with_name("config") + .long("config") + .required(true) + .value_name("FILE") + .help("The path to VPN configuration file") .takes_value(true)) .get_matches(); let is_server_mode = matches.value_of("mode").unwrap() == "server"; - // "192.168.0.4:8879" - if is_server_mode { - if let Some(vpn_server_ip) = matches.value_of("bind-to") { - let server_address = format!("{}:8879", vpn_server_ip); - server::server_mode(server_address).await; - } else { - eprintln!("Error: For server mode, you shall provide the '--bind-to' argument."); + + 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; + } + fs::write(config_path, serde_yaml::to_string(&ClientConfiguration::default()).unwrap()); + return; } - - } else { - if let Some(vpn_server_ip) = matches.value_of("vpn-server") { - let server_address = format!("{}:8879", vpn_server_ip); - client::client_mode(server_address).await; - } else { - eprintln!("Error: For client mode, you shall provide the '--vpn-server' argument."); + + 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 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 832c841..0a03bf3 100644 --- a/src/server.rs +++ b/src/server.rs @@ -11,16 +11,16 @@ use std::collections::HashMap; use tokio::io::AsyncReadExt; use std::process::Command; -use crate::VpnPacket; +use crate::{ VpnPacket, ServerConfiguration }; -pub async fn server_mode(bind_addr: String) { +pub async fn server_mode(server_config: ServerConfiguration) { info!("Starting server..."); let mut config = tun2::Configuration::default(); - config.address("10.8.0.1"); - config.netmask("255.255.255.0"); - config.tun_name("tun0"); - config.up(); + config.address(&server_config.interface.internal_address) + .netmask("255.255.255.0") + .tun_name("tun0") + .up(); #[cfg(target_os = "linux")] config.platform_config(|config| { @@ -30,7 +30,7 @@ pub async fn server_mode(bind_addr: String) { let dev = tun2::create(&config).unwrap(); let (mut dev_reader, mut dev_writer) = dev.split(); - let sock = UdpSocket::bind(bind_addr).await.unwrap(); + let sock = UdpSocket::bind(&server_config.interface.bind_address).await.unwrap(); let sock_rec = Arc::new(sock); let sock_snd = sock_rec.clone(); let addresses = Arc::new(Mutex::new(HashMap::::new())); @@ -49,19 +49,17 @@ pub async fn server_mode(bind_addr: String) { tokio::spawn(async move { let mut buf = vec![0; 4096]; while let Ok(n) = dev_reader.read(&mut buf) { - // 16..=19 - if n > 19 { - let ip = IpAddr::V4(Ipv4Addr::new(buf[16], buf[17], buf[18], buf[19])); - let mp = addrs_cl.lock().await; - if let Some(peer) = mp.get(&ip) { - //info!("Sent to client"); - sock_snd.send_to(&buf[..n], peer.addr).await; - } else { - mp.values().for_each(| peer | { sock_snd.send_to(&buf[..n], peer.addr); }); - error!("UDPeer not found {:?}; what we have {:?}", ip, mp.keys().collect::>()); - } - drop(mp); + if n <= 19 { continue; } + + let ip = IpAddr::V4(Ipv4Addr::new(buf[16], buf[17], buf[18], buf[19])); + let mp = addrs_cl.lock().await; + if let Some(peer) = mp.get(&ip) { + sock_snd.send_to(&buf[..n], peer.addr).await; + } else { + // TODO: check in config is broadcast mode enabled (if not, do not send this to everyone) + mp.values().for_each(| peer | { sock_snd.send_to(&buf[..n], peer.addr); }); } + drop(mp); } }); @@ -85,9 +83,7 @@ pub async fn server_mode(bind_addr: String) { send2tun.send((&buf[1..len]).to_vec()); } }, // payload - _ => { - error!("Unexpected header value."); - } + _ => error!("Unexpected header value.") } }, None => error!("There is no header")