add P2P protocol

This commit is contained in:
PoliEcho 2025-07-30 12:50:02 +02:00
parent b1335bef08
commit ddbe156846
10 changed files with 401 additions and 25 deletions

10
Cargo.lock generated
View File

@ -346,6 +346,15 @@ version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
[[package]]
name = "colored"
version = "3.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fde0e0ec90c9dfb3b4b1a0891a7dcd0e2bffde2f7efed5fe7c9bb00e5bfb915e"
dependencies = [
"windows-sys 0.59.0",
]
[[package]]
name = "concurrent-queue"
version = "2.5.0"
@ -738,6 +747,7 @@ dependencies = [
"chrono",
"cipher",
"clap",
"colored",
"hmac",
"orx-concurrent-vec",
"pbkdf2",

View File

@ -18,6 +18,7 @@ cbc = "0.1.2"
chrono = "0.4.41"
cipher = { version = "0.4.4", features = ["block-padding", "alloc"] }
clap = { version = "4.5.41", features = ["derive"] }
colored = "3.0.0"
hmac = "0.12.1"
orx-concurrent-vec = "3.6.0"
pbkdf2 = "0.12.2"

View File

@ -1,9 +1,17 @@
mod net;
mod tun;
mod types;
use colored::Colorize;
use pea_2_pea::*;
use rand::RngCore;
use rayon::prelude::*;
use std::{net::UdpSocket, process::exit, time::Duration};
use std::{
net::UdpSocket,
process::exit,
sync::{Arc, RwLock},
time::Duration,
};
use crate::types::Network;
@ -42,7 +50,8 @@ fn main() -> std::io::Result<()> {
eprintln!("network id cannot have more then 255 charactes");
exit(7); // posix for E2BIG
}
{
let mut buf: [u8; UDP_BUFFER_SIZE] = [0; UDP_BUFFER_SIZE];
let (socket, virtual_network, my_public_sock_addr) = {
let socket: UdpSocket = (|| -> std::io::Result<UdpSocket> {
match UdpSocket::bind("0.0.0.0:0") {
// bind to OS assigned random port
@ -67,7 +76,6 @@ fn main() -> std::io::Result<()> {
.parse()
.unwrap();
let mut buf: [u8; BUFFER_SIZE] = [0; BUFFER_SIZE];
// query here
let public_sock_addr_raw: String =
match net::query_request(&mut buf, &server_SocketAddr, &socket) {
@ -77,7 +85,7 @@ fn main() -> std::io::Result<()> {
let mut salt: [u8; SALT_AND_IV_SIZE] = [0u8; SALT_AND_IV_SIZE];
let mut iv: [u8; SALT_AND_IV_SIZE] = [0u8; SALT_AND_IV_SIZE];
let (public_sock_addr, encryption_key) = match cli.password {
let (mut public_sock_addr, encryption_key) = match cli.password {
Some(ref p) => {
let mut rng = rand::rng();
rng.fill_bytes(&mut salt);
@ -104,7 +112,7 @@ fn main() -> std::io::Result<()> {
),
};
let virtual_network: Network = {
let virtual_network: Arc<RwLock<Network>> = RwLock::new({
match net::get_request(
&mut buf,
&server_SocketAddr,
@ -114,14 +122,16 @@ fn main() -> std::io::Result<()> {
) {
Ok(n) => {
eprintln!("Network exists joining it");
public_sock_addr =
shared::crypto::encrypt(&n.key, &iv, public_sock_addr_raw.as_bytes())
.unwrap()
.into_boxed_slice();
let _ = net::send_heartbeat(
&mut buf,
&server_SocketAddr,
&socket,
&n,
&shared::crypto::encrypt(&n.key, &iv, public_sock_addr_raw.as_bytes())
.unwrap()
.into_boxed_slice(),
&public_sock_addr,
&iv,
);
n
@ -154,7 +164,96 @@ fn main() -> std::io::Result<()> {
exit(5); //EIO
}
}
};
})
.into();
(
socket,
virtual_network,
types::EncryptablePulicSockAddr::new(iv, public_sock_addr),
)
};
{
// all loops here will be auto skiped if there are no peers yet
let mut ips_used: [bool; u8::MAX as usize + 1] = [false; u8::MAX as usize + 1];
ips_used[0] = true; // ignore net addr
ips_used[u8::MAX as usize] = true; // ignore broadcast
eprintln!(
"{} reaching to other peers to obtain ip address",
"[LOG]".blue()
);
virtual_network
.write()
.unwrap()
.peers
.iter_mut()
.for_each(|peer| {
match net::P2P_query(&mut buf, &peer.sock_addr, &socket, virtual_network.clone()) {
Ok(ip) => {
ips_used[ip.octets()[3] as usize] = true;
peer.private_ip = ip;
}
Err(e) => eprintln!(
"{} while getting ip from peer: {}, Error: {}",
"[ERROR]".red(),
peer.sock_addr,
e
),
};
});
virtual_network.write().unwrap().private_ip = std::net::Ipv4Addr::new(
DEFAULT_NETWORK_PREFIX[0],
DEFAULT_NETWORK_PREFIX[1],
DEFAULT_NETWORK_PREFIX[2],
ips_used.par_iter().position_first(|&b| !b).unwrap() as u8,
); // find first element that is false
virtual_network
.write()
.unwrap()
.peers
.retain(|peer| peer.private_ip != std::net::Ipv4Addr::UNSPECIFIED); // remove all peers without ip
virtual_network
.read()
.unwrap()
.peers
.iter()
.for_each(|peer| {
match net::P2P_hello(
&mut buf,
&peer.sock_addr,
&socket,
virtual_network.read().unwrap().private_ip,
virtual_network.clone(),
) {
Ok(_) => eprintln!(
"{} registered with peer: {}",
"[SUCCESS]".green(),
peer.sock_addr
),
Err(e) => eprintln!(
"{} failed to register with peer: {}, Error: {}",
"[ERROR]".red(),
peer.sock_addr,
e
),
}
});
}
let tun_iface = match tun::create_tun_interface(virtual_network.read().unwrap().private_ip) {
Ok(t) => t,
Err(e) => {
eprintln!(
"{} failed to create Tun interface, Error: {}, are you running as root?",
"[CRITICAL]".red().bold(),
e
);
return Err(e);
}
};
Ok(())
}

View File

@ -1,16 +1,21 @@
use std::{
io::ErrorKind,
net::{SocketAddr, UdpSocket},
net::{Ipv4Addr, SocketAddr, UdpSocket},
str::FromStr,
sync::{Arc, RwLock},
};
use pea_2_pea::*;
use rand::{RngCore, rng};
use tappers::Netmask;
use crate::types::Peer;
use super::types;
// return data_lenght and number of retryes
pub fn send_and_recv_with_retry(
buf: &mut [u8; BUFFER_SIZE],
buf: &mut [u8; UDP_BUFFER_SIZE],
send_buf: &[u8],
dst: &SocketAddr,
socket: &UdpSocket,
@ -77,7 +82,7 @@ pub fn send_and_recv_with_retry(
}
pub fn query_request(
buf: &mut [u8; BUFFER_SIZE],
buf: &mut [u8; UDP_BUFFER_SIZE],
dst: &SocketAddr,
socket: &UdpSocket,
) -> Result<String, ServerErrorResponses> {
@ -104,7 +109,7 @@ pub fn query_request(
}
pub fn register_request(
buf: &mut [u8; BUFFER_SIZE],
buf: &mut [u8; UDP_BUFFER_SIZE],
dst: &SocketAddr,
socket: &UdpSocket,
network: &types::Network,
@ -167,7 +172,7 @@ pub fn register_request(
}
pub fn get_request(
buf: &mut [u8; BUFFER_SIZE],
buf: &mut [u8; UDP_BUFFER_SIZE],
dst: &SocketAddr,
socket: &UdpSocket,
network_id: &String,
@ -214,7 +219,7 @@ pub fn get_request(
.unwrap();
let mut offset: usize = 0;
let mut peers: Vec<SocketAddr> = Vec::with_capacity(1); // at least one client
let mut peers: Vec<Peer> = Vec::with_capacity(1); // at least one client
let key: [u8; 32] = match password {
Some(p) => shared::crypto::derive_key_from_password(p.as_bytes(), &salt),
@ -302,7 +307,7 @@ pub fn get_request(
}
};
peers.push(peer);
peers.push(types::Peer::new(peer, None));
break;
}
offset += SALT_AND_IV_SIZE as usize + sock_addr_len as usize + 1 /*for size byte */;
@ -319,7 +324,7 @@ pub fn get_request(
}
pub fn send_heartbeat(
buf: &mut [u8; BUFFER_SIZE],
buf: &mut [u8; UDP_BUFFER_SIZE],
dst: &SocketAddr,
socket: &UdpSocket,
network: &types::Network,
@ -371,3 +376,106 @@ pub fn send_heartbeat(
Err(e) => return Err(e),
}
}
#[allow(non_snake_case)]
pub fn P2P_query(
buf: &mut [u8; UDP_BUFFER_SIZE],
dst: &SocketAddr,
socket: &UdpSocket,
network: Arc<std::sync::RwLock<types::Network>>,
) -> Result<std::net::Ipv4Addr, Box<dyn std::error::Error>> {
#[cfg(debug_assertions)]
println!("P2P QUERY method");
let (data_lenght, _) = send_and_recv_with_retry(
buf,
&[P2PMethods::PEER_QUERY as u8],
dst,
socket,
STANDARD_RETRY_MAX,
)?;
let iv: [u8; SALT_AND_IV_SIZE] = buf[P2PStandardDataPositions::IV as usize
..P2PStandardDataPositions::IV as usize + SALT_AND_IV_SIZE]
.try_into()
.expect("this should never happen");
let tmp_decrypted: Vec<u8>;
return Ok(std::net::Ipv4Addr::from_str(
if network.read().unwrap().encrypted {
match shared::crypto::decrypt(
&network.read().unwrap().key,
&iv,
&buf[P2PStandardDataPositions::DATA as usize..data_lenght - 1],
) {
Ok(decrypted) => {
tmp_decrypted = decrypted;
match std::str::from_utf8(&tmp_decrypted) {
Ok(s) => s,
Err(e) => return Err(Box::new(e)),
}
}
Err(e) => {
return Err(Box::new(ServerErrorResponses::GENERAL_ERROR(format!(
"{}",
e
))));
}
}
} else {
match std::str::from_utf8(
&buf[P2PStandardDataPositions::DATA as usize..data_lenght - 1],
) {
Ok(s) => s,
Err(e) => return Err(Box::new(e)),
}
},
)?);
}
#[allow(non_snake_case)]
pub fn P2P_hello(
buf: &mut [u8; UDP_BUFFER_SIZE],
dst: &SocketAddr,
socket: &UdpSocket,
private_ip: Ipv4Addr,
network: Arc<RwLock<types::Network>>,
) -> Result<usize, ServerErrorResponses> {
let private_ip_str = private_ip.to_string();
let (private_ip_final, iv) = if network.read().unwrap().encrypted {
let mut rng = rng();
let mut iv: [u8; SALT_AND_IV_SIZE] = [0u8; SALT_AND_IV_SIZE];
rng.fill_bytes(&mut iv);
(
shared::crypto::encrypt(
&network.read().unwrap().key,
&iv,
&private_ip_str.as_bytes(),
)
.unwrap()
.into_boxed_slice(),
iv,
)
} else {
(
private_ip_str.as_bytes().to_vec().into_boxed_slice(),
[0u8; SALT_AND_IV_SIZE],
)
};
let mut send_buf: Box<[u8]> =
vec![0u8; 1 + P2PStandardDataPositions::DATA as usize + private_ip_final.len()].into();
send_buf[0] = P2PMethods::PEER_HELLO as u8;
send_buf[P2PStandardDataPositions::IV as usize
..P2PStandardDataPositions::IV as usize + SALT_AND_IV_SIZE]
.copy_from_slice(&iv);
send_buf[P2PStandardDataPositions::DATA as usize..].copy_from_slice(&private_ip_final);
match send_and_recv_with_retry(buf, &send_buf, dst, socket, STANDARD_RETRY_MAX) {
Ok((data_lenght, _)) => return Ok(data_lenght),
Err(e) => return Err(e),
}
}

102
src/client/tun.rs Normal file
View File

@ -0,0 +1,102 @@
use std::sync::{Arc, RwLock};
use pea_2_pea::*;
use rand::RngCore;
use rayon::prelude::*;
use crate::types::Network;
pub fn create_tun_interface(
private_ip: std::net::Ipv4Addr,
) -> Result<tappers::Tun, std::io::Error> {
let mut tun_iface: tappers::Tun = tappers::Tun::new_named(tappers::Interface::new("pea0")?)?;
let mut addr_req = tappers::AddAddressV4::new(private_ip);
addr_req.set_netmask(24);
let mut broadcast_addr_oct = private_ip.octets();
broadcast_addr_oct[3] = 255;
addr_req.set_broadcast(std::net::Ipv4Addr::from(broadcast_addr_oct));
tun_iface.add_addr(private_ip)?;
tun_iface.set_up()?;
return Ok(tun_iface);
}
pub async fn read_tun_iface(
tun_iface: &tappers::Tun,
socket: std::net::UdpSocket,
network: Arc<RwLock<Network>>,
) {
let mut buf: [u8; IP_BUFFER_SIZE] = [0u8; IP_BUFFER_SIZE];
smol::block_on(async {
loop {
let data_lenght = tun_iface.recv(&mut buf).unwrap(); // build in auto termination, isn't it great
smol::spawn(handle_ip_packet(
buf[..data_lenght - 1].to_vec().into(),
network.clone(),
socket.try_clone().expect("couldn't clone the socket"),
))
.detach();
}
});
}
pub async fn handle_ip_packet(
packet_data: Box<[u8]>,
network: Arc<RwLock<Network>>,
socket: std::net::UdpSocket,
) {
let dst_ip = std::net::Ipv4Addr::from(
match <[u8; 4]>::try_from(
&packet_data[DEST_IN_IPV4_OFFSET..DEST_IN_IPV4_OFFSET + IPV4_SIZE],
) {
Ok(slice) => slice,
Err(e) => {
eprintln!("Procesing of IP packet failed, Invalid dst IP: {}", e);
return;
}
},
);
let mut rng = rand::rng();
let mut iv: [u8; SALT_AND_IV_SIZE] = [0u8; SALT_AND_IV_SIZE];
rng.fill_bytes(&mut iv);
let mut encrypted_data =
match shared::crypto::encrypt(&network.read().unwrap().key, &iv, &packet_data) {
Ok(cr) => cr,
Err(e) => {
eprintln!("Failed to encrypt packet droping it: {}", e);
return;
}
};
encrypted_data.insert(0, P2PMethods::PACKET as u8);
encrypted_data.splice(1..1, iv);
if dst_ip.octets()[3] == 255 {
network.read().unwrap().peers.par_iter().for_each(|peer| {
// broadcast
match socket.send_to(&encrypted_data, peer.sock_addr) {
Ok(_) => {}
Err(e) => eprintln!("failed to send packet: {}", e),
};
});
} else {
let dst = match network
.read()
.unwrap()
.peers
.par_iter()
.find_any(|&p| p.private_ip == dst_ip)
.map(|p| p.sock_addr)
{
Some(sa) => sa,
None => return,
};
match socket.send_to(&encrypted_data, dst) {
Ok(_) => {}
Err(e) => eprintln!("failed to send packet: {}", e),
};
}
}

View File

@ -1,4 +1,23 @@
use pea_2_pea::*;
#[readonly::make]
pub struct Peer {
#[readonly]
pub sock_addr: std::net::SocketAddr,
pub private_ip: std::net::Ipv4Addr,
}
impl Peer {
pub fn new(sock_addr: std::net::SocketAddr, private_ip: Option<std::net::Ipv4Addr>) -> Self {
Peer {
sock_addr,
private_ip: match private_ip {
Some(ip) => ip,
None => std::net::Ipv4Addr::UNSPECIFIED,
},
}
}
}
#[readonly::make]
pub struct Network {
#[readonly]
@ -9,8 +28,8 @@ pub struct Network {
pub net_id: String,
#[readonly]
pub salt: [u8; SALT_AND_IV_SIZE as usize],
#[readonly]
pub peers: Vec<std::net::SocketAddr>,
pub peers: Vec<Peer>,
pub private_ip: std::net::Ipv4Addr,
}
impl Network {
@ -19,7 +38,7 @@ impl Network {
key: [u8; 32],
net_id: String,
salt: [u8; SALT_AND_IV_SIZE as usize],
peers: Vec<std::net::SocketAddr>,
peers: Vec<Peer>,
) -> Self {
Network {
encrypted,
@ -27,6 +46,21 @@ impl Network {
net_id,
salt,
peers,
private_ip: std::net::Ipv4Addr::UNSPECIFIED,
}
}
}
#[readonly::make]
pub struct EncryptablePulicSockAddr {
#[readonly]
pub iv: [u8; SALT_AND_IV_SIZE],
#[readonly]
pub sock_addr: Box<[u8]>,
}
impl EncryptablePulicSockAddr {
pub fn new(iv: [u8; SALT_AND_IV_SIZE], sock_addr: Box<[u8]>) -> Self {
EncryptablePulicSockAddr { iv, sock_addr }
}
}

View File

@ -1,12 +1,18 @@
use core::fmt;
pub const SERVER_PORT: u16 = 3543;
pub const BUFFER_SIZE: usize = 65535;
pub const UDP_BUFFER_SIZE: usize = 65527;
pub const IP_BUFFER_SIZE: usize = 65535;
pub const DEFAULT_TIMEOUT: u64 = 30;
pub const VERSION: &str = "v0.1";
pub const SALT_AND_IV_SIZE: usize = 16;
pub const STANDARD_RETRY_MAX: usize = 10;
pub const DEST_IN_IPV4_OFFSET: usize = 16;
pub const IPV4_SIZE: usize = 4;
pub const DEFAULT_NETWORK_PREFIX: [u8; 3] = [172, 22, 44];
#[repr(u8)]
pub enum ServerMethods {
QUERY = 0, // return IP and port of the client
@ -106,4 +112,19 @@ pub enum HeartBeatRequestDataPositions {
DATA = (HeartBeatRequestDataPositions::IV as usize + SALT_AND_IV_SIZE as usize) as usize, // first ID than sockaddr
}
#[allow(non_camel_case_types)]
#[repr(u8)]
pub enum P2PMethods {
PEER_QUERY = 20, // responds with its private ip
PEER_HELLO = 21, // sends private ip encrypted if on
PEER_GOODBYE = 22, // sends private ip encrypted if on
PACKET = 23, // sends IP packet encrypted if on
}
#[repr(usize)]
pub enum P2PStandardDataPositions {
// sould apply to all P2P Methods
IV = 1,
DATA = P2PStandardDataPositions::IV as usize + SALT_AND_IV_SIZE,
}
pub mod shared;

View File

@ -24,7 +24,7 @@ fn main() -> std::io::Result<()> {
let registration_vector: Arc<ConcurrentVec<types::Registration>> =
Arc::new(orx_concurrent_vec::ConcurrentVec::new());
let mut buf: [u8; pea_2_pea::BUFFER_SIZE] = [0u8; pea_2_pea::BUFFER_SIZE];
let mut buf: [u8; pea_2_pea::UDP_BUFFER_SIZE] = [0u8; pea_2_pea::UDP_BUFFER_SIZE];
smol::block_on(async {
loop {
buf.fill(0);

View File

@ -9,7 +9,7 @@ use rayon::prelude::*;
use std::sync::Arc;
use std::u8;
pub async fn handle_request(
buf: [u8; BUFFER_SIZE],
buf: [u8; UDP_BUFFER_SIZE],
socket: std::sync::Arc<std::net::UdpSocket>,
src: core::net::SocketAddr,
data_len: usize,
@ -115,7 +115,7 @@ pub async fn handle_request(
send_vec.extend_from_slice(&client.client_sock_addr);
});
if send_vec.len() > BUFFER_SIZE {
if send_vec.len() > UDP_BUFFER_SIZE {
send_general_error_to_client(
src,
std::io::Error::new(
@ -327,7 +327,7 @@ pub async fn handle_request(
Some(reg) => {
let current_time = chrono::Utc::now().timestamp();
reg.update(|r| {r.last_heart_beat = current_time;
match r.clients.par_iter_mut().find_first(|c| *c.client_sock_addr == *sock_addr && c.iv == iv) {
match r.clients.par_iter_mut().find_any(|c| *c.client_sock_addr == *sock_addr && c.iv == iv) {
Some(c) => c.last_heart_beat = current_time,
None => {// add new client if it isn't found
r.clients.push(types::Client::new(sock_addr.clone(), current_time, iv));

View File

@ -36,6 +36,7 @@ pub fn decrypt(
}
}
#[cfg(debug_assertions)]
pub fn test_all_crypto_functions() {
// Test data
let password = b"test_password_123";