Skip to content

Commit 3cb6a88

Browse files
feat: implement p2p based networking using iroh-net (#404)
This the start to run the networking connections over [iroh](https://github.com/n0-computer/iroh), allowing direct connections between players. ## General Architecture - The online matchmaking server does not proxy connections anymore, it only introduces players to each other. After that `iroh` connections are used (which are either direct or relayed, in all cases e2e encrypted) - The Lan and Online discovery services, only function as such, discovering other players, after this has happened, all work is passed to `Socket` which is the p2p version of transferring game state between players ## Testing it out There is a branch of `jumpy` using this, which can be used to try it out https://github.com/dignifiedquire/jumpy/tree/feat-iroh-networking ### Matchmaker Servers - Europe: `dkv5qztdu75wgtkyukkmhemz25adco7jrplzockgqgzvzl3d3z4q` - US: `tkj2gohdqx2fjqm24s6l6m7ehecntlcz72kzliwjy5ezs6yfdo6q ` ## Work to be done - [ ] browser, will likely have to be in some form of disabling this when running in wasm (for now) - [x] cleanup - [x] better error handling
1 parent aecd998 commit 3cb6a88

22 files changed

Lines changed: 3692 additions & 1815 deletions

File tree

Cargo.lock

Lines changed: 2774 additions & 289 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

framework_crates/bones_framework/Cargo.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,6 @@ async-channel = "1.9"
7878
bevy_tasks = "0.11"
7979
bytemuck = "1.12"
8080
either = "1.8"
81-
futures-lite = "2.3"
8281
glam = "0.24"
8382
hex = "0.4"
8483
instant = { version = "0.1", features = ["wasm-bindgen"] }
@@ -129,5 +128,6 @@ postcard = { version = "1.0", features = ["alloc"] }
129128
rcgen = "0.12"
130129
rustls = { version = "0.21", features = ["dangerous_configuration", "quic"] }
131130
smallvec = "1.10"
132-
quinn = { version = "0.10", default-features = false, features = ["native-certs", "tls-rustls"] }
133-
quinn_runtime_bevy = { version = "0.3", path = "../../other_crates/quinn_runtime_bevy" }
131+
iroh-quinn = { version = "0.10" }
132+
iroh-net = { version = "0.16" }
133+
tokio = { version = "1", features = ["rt-multi-thread", "macros"] }

framework_crates/bones_framework/src/networking.rs

Lines changed: 44 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
use std::{fmt::Debug, marker::PhantomData, sync::Arc};
44

5+
use bones_matchmaker_proto::{MATCH_ALPN, PLAY_ALPN};
56
use ggrs::{NetworkStats, P2PSession, PlayerHandle};
67
use instant::Duration;
78
use once_cell::sync::Lazy;
@@ -12,15 +13,20 @@ use crate::prelude::*;
1213
use self::{
1314
debug::{NetworkDebugMessage, NETWORK_DEBUG_CHANNEL},
1415
input::{DenseInput, NetworkInputConfig, NetworkPlayerControl, NetworkPlayerControls},
16+
socket::Socket,
1517
};
1618
use crate::input::PlayerControls as PlayerControlsTrait;
1719

18-
pub mod certs;
1920
pub mod debug;
2021
pub mod input;
2122
pub mod lan;
2223
pub mod online;
2324
pub mod proto;
25+
pub mod socket;
26+
27+
/// Runtime, needed to execute network related calls.
28+
pub static RUNTIME: Lazy<tokio::runtime::Runtime> =
29+
Lazy::new(|| tokio::runtime::Runtime::new().expect("unable to crate tokio runtime"));
2430

2531
/// Indicates if input from networking is confirmed, predicted, or if player is disconnected.
2632
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
@@ -45,7 +51,7 @@ impl From<ggrs::InputStatus> for NetworkInputStatus {
4551

4652
/// Module prelude.
4753
pub mod prelude {
48-
pub use super::{certs, debug::prelude::*, input, lan, online, proto, NetworkInfo};
54+
pub use super::{debug::prelude::*, input, lan, online, proto, NetworkInfo, RUNTIME};
4955
}
5056

5157
/// Muliplier for framerate that will be used when playing an online match.
@@ -66,12 +72,8 @@ pub const NETWORK_MAX_PREDICTION_WINDOW_DEFAULT: usize = 7;
6672
/// Amount of frames GGRS will delay local input.
6773
pub const NETWORK_LOCAL_INPUT_DELAY_DEFAULT: usize = 2;
6874

69-
// TODO: Remove this limitation on max players, a variety of types use this for static arrays,
70-
// should either figure out how to make this a compile-time const value specified by game, or
71-
// use dynamic arrays.
72-
//
73-
/// Max players in networked game
74-
pub const MAX_PLAYERS: usize = 4;
75+
#[doc(inline)]
76+
pub use bones_matchmaker_proto::MAX_PLAYERS;
7577

7678
/// Possible errors returned by network loop.
7779
pub enum NetworkError {
@@ -92,40 +94,32 @@ impl<T: DenseInput + Debug> ggrs::Config for GgrsConfig<T> {
9294
type Address = usize;
9395
}
9496

95-
/// The network endpoint used for all QUIC network communications.
96-
pub static NETWORK_ENDPOINT: Lazy<quinn::Endpoint> = Lazy::new(|| {
97-
// Generate certificate
98-
let (cert, key) = certs::generate_self_signed_cert().unwrap();
99-
100-
let mut transport_config = quinn::TransportConfig::default();
101-
transport_config.keep_alive_interval(Some(std::time::Duration::from_secs(5)));
102-
103-
let mut server_config = quinn::ServerConfig::with_single_cert([cert].to_vec(), key).unwrap();
104-
server_config.transport = Arc::new(transport_config);
105-
106-
// Open Socket and create endpoint
107-
let port = THREAD_RNG.with(|rng| rng.u16(10000..=11000));
108-
info!(port, "Started network endpoint");
109-
let socket = std::net::UdpSocket::bind(("0.0.0.0", port)).unwrap();
110-
111-
let client_config = rustls::ClientConfig::builder()
112-
.with_safe_defaults()
113-
.with_custom_certificate_verifier(certs::SkipServerVerification::new())
114-
.with_no_client_auth();
115-
let client_config = quinn::ClientConfig::new(Arc::new(client_config));
116-
117-
let mut endpoint = quinn::Endpoint::new(
118-
quinn::EndpointConfig::default(),
119-
Some(server_config),
120-
socket,
121-
Arc::new(quinn_runtime_bevy::BevyIoTaskPoolExecutor),
122-
)
123-
.unwrap();
124-
125-
endpoint.set_default_client_config(client_config);
126-
127-
endpoint
128-
});
97+
/// The network endpoint used for all network communications.
98+
static NETWORK_ENDPOINT: tokio::sync::OnceCell<iroh_net::MagicEndpoint> =
99+
tokio::sync::OnceCell::const_new();
100+
101+
/// Get the network endpoint used for all communications.
102+
pub async fn get_network_endpoint() -> &'static iroh_net::MagicEndpoint {
103+
NETWORK_ENDPOINT
104+
.get_or_init(|| async move {
105+
let secret_key = iroh_net::key::SecretKey::generate();
106+
iroh_net::MagicEndpoint::builder()
107+
.alpns(vec![MATCH_ALPN.to_vec(), PLAY_ALPN.to_vec()])
108+
.discovery(Box::new(
109+
iroh_net::discovery::ConcurrentDiscovery::from_services(vec![
110+
Box::new(iroh_net::discovery::dns::DnsDiscovery::n0_dns()),
111+
Box::new(iroh_net::discovery::pkarr_publish::PkarrPublisher::n0_dns(
112+
secret_key.clone(),
113+
)),
114+
]),
115+
))
116+
.secret_key(secret_key)
117+
.bind(0)
118+
.await
119+
.unwrap()
120+
})
121+
.await
122+
}
129123

130124
/// Resource containing the [`NetworkSocket`] implementation while there is a connection to a
131125
/// network game.
@@ -135,17 +129,6 @@ pub static NETWORK_ENDPOINT: Lazy<quinn::Endpoint> = Lazy::new(|| {
135129
#[schema(no_default)]
136130
pub struct NetworkMatchSocket(Arc<dyn NetworkSocket>);
137131

138-
/// A type-erased [`ggrs::NonBlockingSocket`]
139-
/// implementation.
140-
#[derive(Deref, DerefMut)]
141-
pub struct BoxedNonBlockingSocket(Box<dyn GgrsSocket>);
142-
143-
impl Clone for BoxedNonBlockingSocket {
144-
fn clone(&self) -> Self {
145-
self.ggrs_socket()
146-
}
147-
}
148-
149132
/// Wraps [`ggrs::Message`] with included `match_id`, used to determine if message received
150133
/// from current match.
151134
#[derive(Serialize, Deserialize, Debug, Clone)]
@@ -160,23 +143,13 @@ pub struct GameMessage {
160143
pub trait GgrsSocket: NetworkSocket + ggrs::NonBlockingSocket<usize> {}
161144
impl<T> GgrsSocket for T where T: NetworkSocket + ggrs::NonBlockingSocket<usize> {}
162145

163-
impl ggrs::NonBlockingSocket<usize> for BoxedNonBlockingSocket {
164-
fn send_to(&mut self, msg: &ggrs::Message, addr: &usize) {
165-
self.0.send_to(msg, addr)
166-
}
167-
168-
fn receive_all_messages(&mut self) -> Vec<(usize, ggrs::Message)> {
169-
self.0.receive_all_messages()
170-
}
171-
}
172-
173146
/// Trait that must be implemented by socket connections establish by matchmakers.
174147
///
175148
/// The [`NetworkMatchSocket`] resource will contain an instance of this trait and will be used by
176149
/// the game to send network messages after a match has been established.
177150
pub trait NetworkSocket: Sync + Send {
178151
/// Get a GGRS socket from this network socket.
179-
fn ggrs_socket(&self) -> BoxedNonBlockingSocket;
152+
fn ggrs_socket(&self) -> Socket;
180153
/// Send a reliable message to the given [`SocketTarget`].
181154
fn send_reliable(&self, target: SocketTarget, message: &[u8]);
182155
/// Receive reliable messages from other players. The `usize` is the index of the player that
@@ -216,7 +189,7 @@ pub struct NetworkInfo {
216189
pub last_confirmed_frame: i32,
217190

218191
/// Socket
219-
pub socket: BoxedNonBlockingSocket,
192+
pub socket: Socket,
220193
}
221194

222195
/// [`SessionRunner`] implementation that uses [`ggrs`] for network play.
@@ -251,7 +224,7 @@ pub struct GgrsSessionRunner<'a, InputTypes: NetworkInputConfig<'a>> {
251224
pub input_collector: InputTypes::InputCollector,
252225

253226
/// Store copy of socket to be able to restart session runner with existing socket.
254-
socket: BoxedNonBlockingSocket,
227+
socket: Socket,
255228

256229
/// Local input delay ggrs session was initialized with
257230
local_input_delay: usize,
@@ -261,7 +234,7 @@ pub struct GgrsSessionRunner<'a, InputTypes: NetworkInputConfig<'a>> {
261234
#[derive(Clone)]
262235
pub struct GgrsSessionRunnerInfo {
263236
/// The socket that will be converted into GGRS socket implementation.
264-
pub socket: BoxedNonBlockingSocket,
237+
pub socket: Socket,
265238
/// The list of local players.
266239
pub player_is_local: [bool; MAX_PLAYERS],
267240
/// the player count.
@@ -282,12 +255,12 @@ pub struct GgrsSessionRunnerInfo {
282255
impl GgrsSessionRunnerInfo {
283256
/// See [`GgrsSessionRunnerInfo`] fields for info on arguments.
284257
pub fn new(
285-
socket: BoxedNonBlockingSocket,
258+
socket: Socket,
286259
max_prediction_window: Option<usize>,
287260
local_input_delay: Option<usize>,
288261
) -> Self {
289-
let player_is_local = socket.0.player_is_local();
290-
let player_count = socket.0.player_count();
262+
let player_is_local = socket.player_is_local();
263+
let player_count = socket.player_count();
291264
Self {
292265
socket,
293266
player_is_local,
@@ -591,7 +564,7 @@ where
591564

592565
// Increment match id so messages from previous match that are still in flight
593566
// will be filtered out.
594-
self.socket.0.increment_match_id();
567+
self.socket.increment_match_id();
595568

596569
let runner_info = GgrsSessionRunnerInfo {
597570
socket: self.socket.clone(),

framework_crates/bones_framework/src/networking/certs.rs

Lines changed: 0 additions & 40 deletions
This file was deleted.

0 commit comments

Comments
 (0)