Remove last bevnet system and prepare for the relay system (#47)
All checks were successful
Rust Checks / checks (push) Successful in 1m12s

Reviewed-on: corentin/border-wars#47
Reviewed-by: Corentin <solois.corentin@gmail.com>
Co-authored-by: Tipragot <contact@tipragot.fr>
Co-committed-by: Tipragot <contact@tipragot.fr>
This commit is contained in:
Tipragot 2024-02-12 23:52:01 +00:00 committed by Corentin
parent b74bd7c6f7
commit e1a191a539
3 changed files with 10 additions and 574 deletions

211
Cargo.lock generated
View file

@ -86,41 +86,6 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
[[package]]
name = "aead"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0"
dependencies = [
"crypto-common",
"generic-array",
]
[[package]]
name = "aes"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac1f845298e95f983ff1944b728ae08b8cebab80d684f0a832ed0fc74dfa27e2"
dependencies = [
"cfg-if",
"cipher",
"cpufeatures",
]
[[package]]
name = "aes-gcm"
version = "0.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1"
dependencies = [
"aead",
"aes",
"cipher",
"ctr",
"ghash",
"subtle",
]
[[package]]
name = "ahash"
version = "0.8.7"
@ -368,18 +333,6 @@ version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
[[package]]
name = "attohttpc"
version = "0.16.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fdb8867f378f33f78a811a8eb9bf108ad99430d7aad43315dd9319c827ef6247"
dependencies = [
"http 0.2.11",
"log",
"url",
"wildmatch",
]
[[package]]
name = "autocfg"
version = "1.1.0"
@ -397,7 +350,7 @@ dependencies = [
"base64 0.21.7",
"bytes",
"futures-util",
"http 1.0.0",
"http",
"http-body",
"http-body-util",
"hyper",
@ -432,7 +385,7 @@ dependencies = [
"async-trait",
"bytes",
"futures-util",
"http 1.0.0",
"http",
"http-body",
"http-body-util",
"mime",
@ -475,10 +428,7 @@ checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567"
name = "bevnet"
version = "0.2.0"
dependencies = [
"aes-gcm",
"base64 0.21.7",
"igd",
"local-ip-address",
"relay-client",
]
[[package]]
@ -1429,16 +1379,6 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e"
[[package]]
name = "cipher"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
dependencies = [
"crypto-common",
"inout",
]
[[package]]
name = "clang-sys"
version = "1.7.0"
@ -1673,19 +1613,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
dependencies = [
"generic-array",
"rand_core",
"typenum",
]
[[package]]
name = "ctr"
version = "0.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835"
dependencies = [
"cipher",
]
[[package]]
name = "d3d12"
version = "0.7.0"
@ -2174,16 +2104,6 @@ dependencies = [
"wasm-bindgen",
]
[[package]]
name = "ghash"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d930750de5717d2dd0b8c0d42c076c0e884c81a73e6cab859bbd2339c71e3e40"
dependencies = [
"opaque-debug",
"polyval",
]
[[package]]
name = "gilrs"
version = "0.10.4"
@ -2378,7 +2298,7 @@ dependencies = [
"futures-core",
"futures-sink",
"futures-util",
"http 1.0.0",
"http",
"indexmap 2.2.2",
"slab",
"tokio",
@ -2449,17 +2369,6 @@ dependencies = [
"windows-sys 0.52.0",
]
[[package]]
name = "http"
version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8947b1a6fad4393052c7ba1f4cd97bed3e953a95c79c92ad9b051a04611d9fbb"
dependencies = [
"bytes",
"fnv",
"itoa",
]
[[package]]
name = "http"
version = "1.0.0"
@ -2478,7 +2387,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643"
dependencies = [
"bytes",
"http 1.0.0",
"http",
]
[[package]]
@ -2489,7 +2398,7 @@ checksum = "41cb79eb393015dadd30fc252023adb0b2400a0caee0fa2a077e6e21a551e840"
dependencies = [
"bytes",
"futures-util",
"http 1.0.0",
"http",
"http-body",
"pin-project-lite",
]
@ -2516,7 +2425,7 @@ dependencies = [
"futures-channel",
"futures-util",
"h2",
"http 1.0.0",
"http",
"http-body",
"httparse",
"httpdate",
@ -2533,7 +2442,7 @@ checksum = "ca38ef113da30126bbff9cd1705f9273e15d45498615d138b0c20279ac7a76aa"
dependencies = [
"bytes",
"futures-util",
"http 1.0.0",
"http",
"http-body",
"hyper",
"pin-project-lite",
@ -2551,19 +2460,6 @@ dependencies = [
"unicode-normalization",
]
[[package]]
name = "igd"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "556b5a75cd4adb7c4ea21c64af1c48cefb2ce7d43dc4352c720a1fe47c21f355"
dependencies = [
"attohttpc",
"log",
"rand",
"url",
"xmltree",
]
[[package]]
name = "image"
version = "0.24.8"
@ -2624,15 +2520,6 @@ dependencies = [
"libc",
]
[[package]]
name = "inout"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5"
dependencies = [
"generic-array",
]
[[package]]
name = "instant"
version = "0.1.12"
@ -2834,18 +2721,6 @@ dependencies = [
"pkg-config",
]
[[package]]
name = "local-ip-address"
version = "0.5.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "612ed4ea9ce5acfb5d26339302528a5e1e59dfed95e9e11af3c083236ff1d15d"
dependencies = [
"libc",
"neli",
"thiserror",
"windows-sys 0.48.0",
]
[[package]]
name = "lock_api"
version = "0.4.11"
@ -3029,31 +2904,6 @@ dependencies = [
"jni-sys",
]
[[package]]
name = "neli"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1100229e06604150b3becd61a4965d5c70f3be1759544ea7274166f4be41ef43"
dependencies = [
"byteorder",
"libc",
"log",
"neli-proc-macros",
]
[[package]]
name = "neli-proc-macros"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c168194d373b1e134786274020dae7fc5513d565ea2ebb9bc9ff17ffb69106d4"
dependencies = [
"either",
"proc-macro2",
"quote",
"serde",
"syn 1.0.109",
]
[[package]]
name = "nix"
version = "0.24.3"
@ -3313,12 +3163,6 @@ version = "1.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
[[package]]
name = "opaque-debug"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
[[package]]
name = "openssl-probe"
version = "0.1.5"
@ -3487,18 +3331,6 @@ dependencies = [
"miniz_oxide",
]
[[package]]
name = "polyval"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d52cff9d1d4dee5fe6d03729099f4a310a41179e0a10dbf542039873f2e826fb"
dependencies = [
"cfg-if",
"cpufeatures",
"opaque-debug",
"universal-hash",
]
[[package]]
name = "pp-rs"
version = "0.2.1"
@ -4418,7 +4250,7 @@ dependencies = [
"byteorder",
"bytes",
"data-encoding",
"http 1.0.0",
"http",
"httparse",
"log",
"rand",
@ -4480,16 +4312,6 @@ version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c"
[[package]]
name = "universal-hash"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea"
dependencies = [
"crypto-common",
"subtle",
]
[[package]]
name = "untrusted"
version = "0.9.0"
@ -4778,12 +4600,6 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "653f141f39ec16bba3c5abe400a0c60da7468261cc2cbf36805022876bc721a8"
[[package]]
name = "wildmatch"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f44b95f62d34113cf558c93511ac93027e03e9c29a60dd0fd70e6e025c7270a"
[[package]]
name = "winapi"
version = "0.3.9"
@ -5176,15 +4992,6 @@ version = "0.8.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fcb9cbac069e033553e8bb871be2fbdffcab578eb25bd0f7c508cedc6dcd75a"
[[package]]
name = "xmltree"
version = "0.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7d8a75eaf6557bb84a65ace8609883db44a29951042ada9b393151532e41fcb"
dependencies = [
"xml-rs",
]
[[package]]
name = "zerocopy"
version = "0.7.32"

View file

@ -12,7 +12,4 @@ categories = ["network-programming", "game-development"]
workspace = true
[dependencies]
local-ip-address = "0.5.7"
aes-gcm = "0.10.3"
base64 = "0.21.7"
igd = "0.12.1"
relay-client = { path = "../relay-client" }

View file

@ -1,369 +1 @@
//! A networking library for Bevy.
//!
//! This contains an implementation of a non-blocking tcp connection and
//! listener with encryption and auto port forwarding.
//!
//! # Example
//! ```no_run
//! use std::io;
//!
//! use bevnet::{Connection, Listener};
//!
//! # fn main() -> io::Result<()> {
//! let listener = Listener::new()?;
//! let mut connection = Connection::connect(&listener.connection_string())?;
//!
//! // The accept operation is not blocking. So we need to loop here.
//! let mut server_connection;
//! loop {
//! if let Some(new_connection) = listener.accept()? {
//! server_connection = new_connection;
//! break;
//! }
//! }
//!
//! // We don't need to loop here because the send operation just appends to the send buffer.
//! connection.send(b"Hello, world!")?;
//!
//! // To be sure the message has been sent, we need to update the connection.
//! while !connection.update()? {
//! // Wait until the connection is updated.
//! std::thread::yield_now();
//! }
//!
//! // The receive operation is not blocking. So we need to loop here.
//! loop {
//! if let Some(message) = server_connection.receive()? {
//! assert_eq!(message, b"Hello, world!");
//! break;
//! }
//! }
//! # Ok(())
//! # }
//! ```
use std::collections::LinkedList;
use std::io::{self, Read, Write};
use std::net::{IpAddr, Ipv4Addr, SocketAddrV4, TcpListener, TcpStream};
use aes_gcm::aead::{Aead, AeadCore, KeyInit, OsRng};
use aes_gcm::{Aes128Gcm, Key, Nonce};
use base64::prelude::*;
use igd::{Gateway, PortMappingProtocol};
use local_ip_address::local_ip;
/// A non-blocking tcp connection.
pub struct Connection {
/// The underlying [TcpStream] used for the connection.
stream: TcpStream,
/// Contains the buffers that are not yet being sent.
send_buffers: LinkedList<(usize, Vec<u8>)>,
/// The length of the next message to be received.
///
/// `None` if the message length is not yet received.
receive_message_len: Option<u16>,
/// The nonce used for encryption.
receive_message_nonce: Option<Vec<u8>>,
/// The length of the received byte block.
///
/// Used by [Connection::receive_partial] to determine if the block is
/// complete.
receive_filled_len: usize,
/// The buffer used to receive a byte block.
receive_buffer: Vec<u8>,
/// The cipher used for encryption and decryption.
cipher: Aes128Gcm,
}
impl Connection {
/// Creates a new [Connection] from a [TcpStream].
fn new(stream: TcpStream, secret_key: &Key<Aes128Gcm>) -> io::Result<Self> {
stream.set_nonblocking(true)?;
Ok(Self {
stream,
send_buffers: LinkedList::new(),
receive_message_len: None,
receive_message_nonce: None,
receive_filled_len: 0,
receive_buffer: Vec::new(),
cipher: Aes128Gcm::new(secret_key),
})
}
/// Creates a new [Connection] that connects to the given connection string.
///
/// This function is blocking.
pub fn connect(connection_string: &str) -> io::Result<Self> {
let data = BASE64_URL_SAFE_NO_PAD
.decode(connection_string)
.map_err(io::Error::other)?;
if data.len() != 22 {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
format!("invalid connection string: {}", connection_string),
));
}
let address = SocketAddrV4::new(
Ipv4Addr::new(data[0], data[1], data[2], data[3]),
u16::from_ne_bytes(data[4..=5].try_into().map_err(|_| {
io::Error::new(
io::ErrorKind::InvalidInput,
format!("invalid connection string: {}", connection_string),
)
})?),
);
Self::new(
TcpStream::connect(address)?,
Key::<Aes128Gcm>::from_slice(&data[6..]),
)
}
/// Sends a message over the connection.
///
/// Returns `true` if the message has been sent directly and `false`
/// if the message is still in the send queue.
///
/// This function is not blocking.
pub fn send(&mut self, message: &[u8]) -> io::Result<bool> {
// Encrypt the message.
let nonce = Aes128Gcm::generate_nonce(OsRng);
let message = self.cipher.encrypt(&nonce, message).map_err(|e| {
io::Error::new(
io::ErrorKind::InvalidData,
format!("failed to encrypt message: {}", e),
)
})?;
// Get the length of the message as a u16.
let message_len: u16 = match message.len().try_into() {
Ok(len) => len,
Err(_) => {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
format!("message length is too large: {}", message.len()),
));
}
};
// Add a new buffer to the send queue.
self.send_buffers
.push_back((0, message_len.to_ne_bytes().to_vec()));
self.send_buffers.push_back((0, nonce.to_vec()));
self.send_buffers.push_back((0, message));
// Update the connection.
self.update()
}
/// Updates the connection.
///
/// This function sends any pending messages that have not been sent yet.
/// It returns `true` if there is no remaining data to send after updating
/// the connection and `false` otherwise.
///
/// This function is not blocking.
pub fn update(&mut self) -> io::Result<bool> {
// Looping over the send buffers.
while let Some((offset, buffer)) = self.send_buffers.front_mut() {
// Writing the buffer to the stream.
match self.stream.write(&buffer[*offset..]) {
Ok(n) => *offset += n,
Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => break,
Err(ref e) if e.kind() == io::ErrorKind::Interrupted => break,
Err(e) => return Err(e),
}
// Removing the buffer if it is fully sent.
if *offset >= buffer.len() {
self.send_buffers.pop_front();
}
}
// Returning success.
Ok(self.send_buffers.is_empty())
}
/// Receives a byte block from the connection.
///
/// This function fills the receive buffer and returns `true` if the
/// buffer is successfully filled with `len` bytes and `false` if the
/// buffer is not filled yet.
///
/// This function mustn't be called for different byte block sequentially
/// because the function can only process one byte block at a time.
///
/// This function is not blocking.
fn receive_partial(&mut self, len: u16) -> io::Result<bool> {
let len = len as usize;
// Resizing the buffer if it is not large enough.
if self.receive_buffer.len() < len {
self.receive_buffer.resize(len, 0);
}
// Checking if the buffer is already filled.
if self.receive_filled_len >= len {
self.receive_filled_len = 0;
return Ok(true);
}
// Reading from the stream.
let start_index = self.receive_filled_len;
let receive_buffer = &mut self.receive_buffer[start_index..start_index + len];
let received_len = self.stream.read(receive_buffer);
self.receive_filled_len += match received_len {
Ok(0) => {
return Err(io::Error::new(
io::ErrorKind::ConnectionAborted,
"connection closed by remote peer",
));
}
Ok(n) => n,
Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => return Ok(false),
Err(ref e) if e.kind() == io::ErrorKind::Interrupted => return Ok(false),
Err(e) => return Err(e),
};
// Checking if the buffer is filled.
if self.receive_filled_len >= len {
self.receive_filled_len = 0;
return Ok(true);
}
Ok(false)
}
/// Receives a message from the connection.
///
/// If no message is available, returns `None`.
///
/// This function is not blocking.
pub fn receive(&mut self) -> io::Result<Option<Vec<u8>>> {
// Receiving the message length.
let message_len = match self.receive_message_len {
Some(message_len) => message_len,
None => {
// If the message length is not received yet, return `None`.
if !self.receive_partial(2)? {
return Ok(None);
}
// Setting the message length.
let message_len =
u16::from_ne_bytes(self.receive_buffer[..2].try_into().map_err(|_| {
io::Error::new(io::ErrorKind::InvalidData, "invalid message length")
})?);
self.receive_message_len = Some(message_len);
// Returning the message length.
message_len
}
};
if self.receive_message_nonce.is_none() {
// If the nonce is not received yet, return `None`.
if !self.receive_partial(12)? {
return Ok(None);
}
// Setting the nonce.
self.receive_message_nonce = Some(self.receive_buffer[..12].to_vec());
}
// Receiving the message.
if !self.receive_partial(message_len)? {
return Ok(None);
}
let message = &self.receive_buffer[..message_len as usize];
// Getting the nonce.
let nonce = self
.receive_message_nonce
.as_ref()
.ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "missing nonce"))?;
// Decrypting the message.
let message = self
.cipher
.decrypt(Nonce::from_slice(nonce), message)
.map_err(|e| {
io::Error::new(
io::ErrorKind::InvalidData,
format!("failed to decrypt message: {}", e),
)
})?;
// Resetting the message length and nonce.
self.receive_message_len = None;
self.receive_message_nonce = None;
// Returning the message.
Ok(Some(message))
}
}
/// A non-blocking tcp listener.
pub struct Listener(TcpListener, Gateway, SocketAddrV4, Key<Aes128Gcm>);
impl Listener {
/// Creates a new listener.
pub fn new() -> io::Result<Self> {
let local_address = match local_ip().map_err(io::Error::other)? {
IpAddr::V4(address) => address,
IpAddr::V6(_) => unreachable!(),
};
let listener = TcpListener::bind(SocketAddrV4::new(local_address, 0))?;
let gateway = igd::search_gateway(Default::default()).map_err(io::Error::other)?;
let opened_port = gateway
.add_any_port(
PortMappingProtocol::TCP,
SocketAddrV4::new(local_address, listener.local_addr()?.port()),
3600 * 24,
"bevnet",
)
.map_err(io::Error::other)?;
let external_address = gateway.get_external_ip().map_err(io::Error::other)?;
listener.set_nonblocking(true)?;
Ok(Self(
listener,
gateway,
SocketAddrV4::new(external_address, opened_port),
Aes128Gcm::generate_key(OsRng),
))
}
/// Accepts a new [Connection].
///
/// This function is not blocking.
pub fn accept(&self) -> io::Result<Option<Connection>> {
match self.0.accept() {
Ok((stream, _)) => Connection::new(stream, &self.3).map(Some),
Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => Ok(None),
Err(ref e) if e.kind() == io::ErrorKind::Interrupted => Ok(None),
Err(e) => Err(e),
}
}
/// Returns the connection string that can be used to connect to th
/// listener.
pub fn connection_string(&self) -> String {
let mut data = Vec::with_capacity(22);
data.extend(self.2.ip().octets());
data.extend(self.2.port().to_ne_bytes());
data.extend(self.3);
BASE64_URL_SAFE_NO_PAD.encode(&data)
}
}
impl Drop for Listener {
fn drop(&mut self) {
self.1
.remove_port(PortMappingProtocol::TCP, self.2.port())
.ok();
}
}