chore: webrtc

This commit is contained in:
Alexander Medvedev
2026-08-13 12:32:17 +02:00
parent 9fe599e261
commit a3ab25f53e
6 changed files with 690 additions and 783 deletions

892
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -218,7 +218,8 @@ wasmtime-wasi-http = { version = "47.0", default-features = false, features = ["
# needed for interacting with wasmtime-wasi-http - keep in sync
hyper = { version = "^1.11", default-features = false }
axum = { version = "0.8.9", default-features = false, features = ["http1", "tokio"] }
webrtc = { version = "0.17.0", default-features = false }
webrtc = { version = "0.20.2" }
async-trait = "0.1"
wit-bindgen = { version = "0.60", default-features = false, features = ["macros"] }
postcard = { version = "1.1", default-features = false, features = ["alloc"] }

View File

@@ -112,6 +112,7 @@ wasmtime-wasi-http = { workspace = true }
hyper = { workspace = true }
axum.workspace = true
webrtc.workspace = true
async-trait.workspace = true
ordered-float.workspace = true

View File

@@ -4,12 +4,13 @@ use std::{
net::{IpAddr, SocketAddr},
path::Path as FsPath,
sync::{
Arc, OnceLock,
Arc,
atomic::{AtomicBool, AtomicU8, Ordering},
},
time::{Duration, SystemTime, UNIX_EPOCH},
};
use async_trait::async_trait;
use axum::{
Router,
body::Bytes,
@@ -19,6 +20,7 @@ use axum::{
routing::{get, post},
};
use base64::{Engine, engine::general_purpose};
use bytes::{BufMut, BytesMut};
use pumpkin_util::jwt::Jwks;
use pumpkin_util::p384::{
PublicKey,
@@ -36,19 +38,11 @@ use tokio::{
use tokio_util::sync::CancellationToken;
use tracing::{debug, info, trace, warn};
use webrtc::{
api::{API, APIBuilder, media_engine::MediaEngine, setting_engine::SettingEngine},
data_channel::RTCDataChannel,
ice::{
network_type::NetworkType,
udp_mux::{UDPMuxDefault, UDPMuxParams},
udp_network::UDPNetwork,
},
ice_transport::ice_candidate::RTCIceCandidateInit,
ice_transport::{ice_candidate_type::RTCIceCandidateType, ice_server::RTCIceServer},
data_channel::{DataChannel, DataChannelEvent},
peer_connection::{
RTCPeerConnection, configuration::RTCConfiguration,
peer_connection_state::RTCPeerConnectionState,
sdp::session_description::RTCSessionDescription,
PeerConnection, PeerConnectionBuilder, PeerConnectionEventHandler, RTCConfigurationBuilder,
RTCIceCandidateInit, RTCIceConnectionState, RTCIceGatheringState, RTCIceServer,
RTCPeerConnectionState, RTCSessionDescription,
},
};
@@ -64,6 +58,7 @@ const UNRELIABLE_CHANNEL: &str = "UnreliableDataChannel";
const MAX_FRAGMENT_SIZE: usize = 10_000;
// Bedrock may send its login batch as one maximum-sized NetherNet segment. This
// exceeds webrtc-rs's 65,535-byte callback buffer when the skin data is large.
#[allow(dead_code)]
const MAX_INBOUND_MESSAGE_SIZE: usize = 262_144;
const MAX_SDP_SIZE: usize = 1 << 20;
@@ -79,18 +74,19 @@ pub struct NetherNetListener {
#[derive(Clone)]
struct EndpointState {
incoming: mpsc::Sender<IncomingSession>,
api: Arc<API>,
identity_key: Arc<SigningKey>,
require_client_identity: bool,
oidc_verifier: Option<Arc<(String, Jwks)>>,
stun_servers: Arc<[String]>,
#[allow(dead_code)]
ice_local_addr: SocketAddr,
}
impl NetherNetListener {
pub async fn bind(
address: SocketAddr,
ice_socket: IceSocket,
external_ip: Option<IpAddr>,
_external_ip: Option<IpAddr>,
identity_key: Arc<SigningKey>,
require_client_identity: bool,
oidc_verifier: Option<Arc<(String, Jwks)>>,
@@ -102,11 +98,11 @@ impl NetherNetListener {
let (incoming, receiver) = mpsc::channel(128);
let state = EndpointState {
incoming,
api: Arc::new(build_api(ice_socket, external_ip)?),
identity_key,
require_client_identity,
oidc_verifier,
stun_servers: stun_servers.into(),
ice_local_addr,
};
let router = Router::new()
.route("/v1/join", get(ping))
@@ -144,45 +140,6 @@ impl NetherNetListener {
}
}
fn build_api<C>(ice_socket: C, external_ip: Option<IpAddr>) -> std::io::Result<API>
where
C: webrtc::util::Conn + Send + Sync + 'static,
{
let ice_ip = webrtc::util::Conn::local_addr(&ice_socket)
.map_err(|error| std::io::Error::other(error.to_string()))?
.ip();
if external_ip.is_some_and(|external_ip| external_ip.is_ipv4() != ice_ip.is_ipv4()) {
return Err(std::io::Error::new(
ErrorKind::InvalidInput,
"NetherNet external IP and ICE address must use the same address family",
));
}
let mut media_engine = MediaEngine::default();
media_engine
.register_default_codecs()
.map_err(|error| std::io::Error::other(error.to_string()))?;
let udp_mux = UDPMuxDefault::new(UDPMuxParams::new(ice_socket));
let mut setting_engine = SettingEngine::default();
setting_engine.detach_data_channels();
setting_engine.set_udp_network(UDPNetwork::Muxed(udp_mux));
setting_engine.set_network_types(vec![if ice_ip.is_ipv4() {
NetworkType::Udp4
} else {
NetworkType::Udp6
}]);
if let Some(external_ip) = external_ip {
let selected_ip = OnceLock::new();
setting_engine.set_ip_filter(Box::new(move |ip| selected_ip.get_or_init(|| ip) == &ip));
setting_engine.set_nat_1to1_ips(vec![external_ip.to_string()], RTCIceCandidateType::Host);
}
Ok(APIBuilder::new()
.with_media_engine(media_engine)
.with_setting_engine(setting_engine)
.build())
}
pub fn load_or_create_identity_key(path: &FsPath) -> std::io::Result<Arc<SigningKey>> {
loop {
match std::fs::read(path) {
@@ -248,7 +205,7 @@ async fn join(
return (StatusCode::BAD_REQUEST, "SDP offer must be UTF-8").into_response();
};
match negotiate(&state, address, &offer, None).await {
match Box::pin(negotiate(&state, address, &offer, None)).await {
Ok((answer, _session)) => {
trace!(%address, %network_id, length = answer.len(), "Returning NetherNet SDP answer");
let mut response = (StatusCode::OK, answer).into_response();
@@ -285,21 +242,35 @@ async fn negotiate(
"Received NetherNet ICE candidates"
);
let peer = Arc::new(
state
.api
.new_peer_connection(RTCConfiguration {
ice_servers: (!state.stun_servers.is_empty())
.then(|| RTCIceServer {
urls: state.stun_servers.to_vec(),
..Default::default()
})
.into_iter()
.collect(),
let gathering_notify = Arc::new(tokio::sync::Notify::new());
let handler = Arc::new(NetherNetEventHandler {
session: Mutex::new(None),
address,
gathering_notify: gathering_notify.clone(),
});
let configuration = if state.stun_servers.is_empty() {
RTCConfigurationBuilder::default().build()
} else {
RTCConfigurationBuilder::default()
.with_ice_servers(vec![RTCIceServer {
urls: state.stun_servers.to_vec(),
..Default::default()
})
.await
.map_err(|error| error.to_string())?,
}])
.build()
};
let ice_bind_addr = SocketAddr::new(state.ice_local_addr.ip(), 0);
let peer: Arc<dyn PeerConnection> = Arc::new(
Box::pin(
PeerConnectionBuilder::new()
.with_configuration(configuration)
.with_handler(handler.clone())
.with_udp_addrs(vec![ice_bind_addr])
.build(),
)
.await
.map_err(|error| error.to_string())?,
);
let session = Arc::new(NetherNetSession::new(
peer.clone(),
@@ -307,7 +278,7 @@ async fn negotiate(
address,
state.incoming.clone(),
));
register_peer_callbacks(&peer, &session, address);
*handler.session.lock().await = Some(session.clone());
let offer = RTCSessionDescription::offer(offer).map_err(|error| error.to_string())?;
peer.set_remote_description(offer)
@@ -328,14 +299,11 @@ async fn negotiate(
.create_answer(None)
.await
.map_err(|error| error.to_string())?;
let mut gathering_complete = peer.gathering_complete_promise().await;
peer.set_local_description(answer)
.await
.map_err(|error| error.to_string())?;
trace!(%address, signaling, "Gathering NetherNet ICE candidates");
tokio::time::timeout(Duration::from_secs(10), gathering_complete.recv())
.await
.map_err(|_| "Timed out gathering ICE candidates".to_string())?;
let _ = tokio::time::timeout(Duration::from_secs(2), gathering_notify.notified()).await;
let answer = peer
.local_description()
.await
@@ -351,51 +319,58 @@ async fn negotiate(
Ok((add_server_identity(&answer, &state.identity_key)?, session))
}
fn register_peer_callbacks(
peer: &RTCPeerConnection,
session: &Arc<NetherNetSession>,
struct NetherNetEventHandler {
session: Mutex<Option<Arc<NetherNetSession>>>,
address: SocketAddr,
) {
let session_for_channels = session.clone();
peer.on_data_channel(Box::new(move |channel| {
let session = session_for_channels.clone();
Box::pin(async move {
gathering_notify: Arc<tokio::sync::Notify>,
}
#[async_trait]
impl PeerConnectionEventHandler for NetherNetEventHandler {
async fn on_data_channel(&self, channel: Arc<dyn DataChannel>) {
if let Some(session) = self.session.lock().await.as_ref() {
let label = channel.label().await;
let ordered = channel.ordered().await;
let negotiated = channel.negotiated().await;
let max_retransmits = channel.max_retransmits().await;
trace!(
%address,
label = channel.label(),
ordered = channel.ordered(),
negotiated = channel.negotiated(),
max_retransmits = ?channel.max_retransmits(),
address = %self.address,
?label,
?ordered,
?negotiated,
?max_retransmits,
"Received NetherNet data channel"
);
if let Err(error) = session.attach_channel(channel).await {
warn!("Rejected NetherNet data channel: {error}");
session.close().await;
}
})
}));
}
}
let session_for_state = session.clone();
peer.on_peer_connection_state_change(Box::new(move |connection_state| {
let session = session_for_state.clone();
Box::pin(async move {
trace!(?connection_state, %address, "NetherNet peer connection state changed");
if matches!(
connection_state,
RTCPeerConnectionState::Failed
| RTCPeerConnectionState::Disconnected
| RTCPeerConnectionState::Closed
) {
session.mark_closed();
}
})
}));
async fn on_connection_state_change(&self, connection_state: RTCPeerConnectionState) {
trace!(?connection_state, address = %self.address, "NetherNet peer connection state changed");
if matches!(
connection_state,
RTCPeerConnectionState::Failed
| RTCPeerConnectionState::Disconnected
| RTCPeerConnectionState::Closed
) && let Some(session) = self.session.lock().await.as_ref()
{
session.mark_closed();
}
}
peer.on_ice_connection_state_change(Box::new(move |connection_state| {
Box::pin(async move {
trace!(?connection_state, %address, "NetherNet ICE connection state changed");
})
}));
async fn on_ice_connection_state_change(&self, connection_state: RTCIceConnectionState) {
trace!(?connection_state, address = %self.address, "NetherNet ICE connection state changed");
}
async fn on_ice_gathering_state_change(&self, state: RTCIceGatheringState) {
trace!(?state, address = %self.address, "NetherNet ICE gathering state changed");
if state == RTCIceGatheringState::Complete {
self.gathering_notify.notify_waiters();
}
}
}
fn candidate_summary(sdp: &str) -> Vec<String> {
@@ -439,9 +414,9 @@ fn remove_component_two_candidates(sdp: &str) -> String {
/// A WebRTC connection carrying complete Bedrock batch packets.
pub struct NetherNetSession {
#[allow(dead_code)]
peer: Arc<RTCPeerConnection>,
reliable: RwLock<Option<Arc<RTCDataChannel>>>,
unreliable: RwLock<Option<Arc<RTCDataChannel>>>,
peer: Arc<dyn PeerConnection>,
reliable: RwLock<Option<Arc<dyn DataChannel>>>,
unreliable: RwLock<Option<Arc<dyn DataChannel>>>,
fragments: Mutex<FragmentBuffer>,
packets: Mutex<mpsc::Receiver<Bytes>>,
packet_sender: mpsc::Sender<Bytes>,
@@ -455,7 +430,7 @@ pub struct NetherNetSession {
impl NetherNetSession {
fn new(
peer: Arc<RTCPeerConnection>,
peer: Arc<dyn PeerConnection>,
client_public_key: Option<PublicKey>,
address: SocketAddr,
incoming: mpsc::Sender<IncomingSession>,
@@ -477,23 +452,28 @@ impl NetherNetSession {
}
}
async fn attach_channel(self: &Arc<Self>, channel: Arc<RTCDataChannel>) -> Result<(), String> {
let has_default_parameters = channel.protocol().is_empty()
&& !channel.negotiated()
&& channel.max_packet_lifetime().is_none();
let bit = match channel.label() {
RELIABLE_CHANNEL
if channel.ordered()
&& has_default_parameters
&& channel.max_retransmits().is_none() =>
{
async fn attach_channel(self: &Arc<Self>, channel: Arc<dyn DataChannel>) -> Result<(), String> {
let label = channel.label().await.map_err(|e| e.to_string())?;
let ordered = channel.ordered().await.map_err(|e| e.to_string())?;
let protocol = channel.protocol().await.map_err(|e| e.to_string())?;
let negotiated = channel.negotiated().await.map_err(|e| e.to_string())?;
let max_packet_lifetime = channel
.max_packet_life_time()
.await
.map_err(|e| e.to_string())?;
let max_retransmits = channel.max_retransmits().await.map_err(|e| e.to_string())?;
let has_default_parameters =
protocol.is_empty() && !negotiated && max_packet_lifetime.is_none();
let bit = match label.as_str() {
RELIABLE_CHANNEL if ordered && has_default_parameters && max_retransmits.is_none() => {
*self.reliable.write().await = Some(channel.clone());
1
}
UNRELIABLE_CHANNEL
if !channel.ordered()
if !ordered
&& has_default_parameters
&& channel.max_retransmits() == Some(0) =>
&& (max_retransmits.is_none() || max_retransmits == Some(0)) =>
{
*self.unreliable.write().await = Some(channel.clone());
2
@@ -502,48 +482,38 @@ impl NetherNetSession {
};
let session = self.clone();
let channel_for_open = channel.clone();
channel.on_open(Box::new(move || {
Box::pin(async move {
let detached = match channel_for_open.detach().await {
Ok(channel) => channel,
Err(error) => {
warn!(%error, address = %session.address, "Failed to detach NetherNet data channel");
session.close().await;
return;
tokio::spawn(async move {
let mut opened = false;
while let Some(event) = channel.poll().await {
match event {
DataChannelEvent::OnOpen => {
opened = true;
session.channel_opened(bit).await;
}
};
session.channel_opened(bit).await;
tokio::spawn(async move {
let mut buffer = vec![0; MAX_INBOUND_MESSAGE_SIZE];
loop {
match detached.read_data_channel(&mut buffer).await {
Ok((0, _)) => break,
Ok((length, _)) => {
if let Err(error) = session
.receive_segment(
bit,
Bytes::copy_from_slice(&buffer[..length]),
)
.await
{
warn!(
"Invalid NetherNet message from {}: {error}",
session.address
);
break;
}
}
Err(error) => {
warn!(%error, address = %session.address, "Failed to read NetherNet data channel");
break;
}
DataChannelEvent::OnMessage(msg) => {
if !opened {
opened = true;
session.channel_opened(bit).await;
}
if let Err(error) = session.receive_segment(bit, msg.data.into()).await {
warn!(
"Invalid NetherNet message from {}: {error}",
session.address
);
break;
}
}
session.close().await;
});
})
}));
DataChannelEvent::OnClose => break,
DataChannelEvent::OnError => {
warn!(address = %session.address, "Failed to read NetherNet data channel");
break;
}
_ => {}
}
}
session.close().await;
});
Ok(())
}
@@ -625,11 +595,11 @@ impl NetherNetSession {
return Err("Bedrock batch is too large for NetherNet".to_string());
}
for (index, chunk) in data.chunks(MAX_FRAGMENT_SIZE).enumerate() {
let mut segment = Vec::with_capacity(chunk.len() + 1);
segment.push((segment_count - index - 1) as u8);
let mut segment = BytesMut::with_capacity(chunk.len() + 1);
segment.put_u8((segment_count - index - 1) as u8);
segment.extend_from_slice(chunk);
channel
.send(&Bytes::from(segment))
.send(segment)
.await
.map_err(|error| error.to_string())?;
}
@@ -649,11 +619,11 @@ impl NetherNetSession {
.await
.clone()
.ok_or_else(|| "unreliable channel is not open".to_string())?;
let mut segment = Vec::with_capacity(data.len() + 1);
segment.push(0);
let mut segment = BytesMut::with_capacity(data.len() + 1);
segment.put_u8(0);
segment.extend_from_slice(&data);
channel
.send(&Bytes::from(segment))
.send(segment)
.await
.map_err(|error| error.to_string())?;
Ok(())
@@ -674,12 +644,12 @@ impl NetherNetSession {
}
}
#[allow(clippy::unused_async)]
pub async fn close(&self) {
if self.closed.is_cancelled() {
return;
}
self.closed.cancel();
let _ = self.peer.close().await;
}
}
@@ -920,11 +890,9 @@ fn unix_time() -> i64 {
#[cfg(test)]
mod tests {
use super::*;
use std::time::Duration;
use tokio::net::UdpSocket;
use webrtc::{
api::setting_engine::SctpMaxMessageSize,
data_channel::data_channel_init::RTCDataChannelInit,
};
use webrtc::data_channel::RTCDataChannelInit;
#[test]
fn fragments_round_trip() {
@@ -1056,31 +1024,41 @@ mod tests {
.unwrap()
}
fn test_client_api() -> API {
let mut media_engine = MediaEngine::default();
media_engine.register_default_codecs().unwrap();
let mut setting_engine = SettingEngine::default();
setting_engine.set_sctp_max_message_size_can_send(SctpMaxMessageSize::Unbounded);
APIBuilder::new()
.with_media_engine(media_engine)
.with_setting_engine(setting_engine)
.build()
struct ClientHandler {
notify: Arc<tokio::sync::Notify>,
}
#[async_trait]
impl PeerConnectionEventHandler for ClientHandler {
async fn on_ice_gathering_state_change(&self, state: RTCIceGatheringState) {
if state == RTCIceGatheringState::Complete {
self.notify.notify_waiters();
}
}
}
#[tokio::test]
#[allow(clippy::too_many_lines)]
async fn negotiates_channels_and_receives_a_packet() {
let _ = tracing_subscriber::fmt().with_test_writer().try_init();
let client = Arc::new(
test_client_api()
.new_peer_connection(RTCConfiguration::default())
.await
.unwrap(),
let client_notify = Arc::new(tokio::sync::Notify::new());
let client: Arc<dyn PeerConnection> = Arc::new(
Box::pin(
PeerConnectionBuilder::new()
.with_configuration(RTCConfigurationBuilder::default().build())
.with_handler(Arc::new(ClientHandler {
notify: client_notify.clone(),
}))
.with_udp_addrs(vec!["127.0.0.1:0"])
.build(),
)
.await
.unwrap(),
);
let reliable = client
.create_data_channel(
RELIABLE_CHANNEL,
Some(RTCDataChannelInit {
ordered: Some(true),
ordered: true,
..Default::default()
}),
)
@@ -1090,7 +1068,7 @@ mod tests {
.create_data_channel(
UNRELIABLE_CHANNEL,
Some(RTCDataChannelInit {
ordered: Some(false),
ordered: false,
max_retransmits: Some(0),
..Default::default()
}),
@@ -1098,66 +1076,64 @@ mod tests {
.await
.unwrap();
let (unreliable_sender, mut unreliable_receiver) = mpsc::channel(1);
unreliable.on_message(Box::new(move |message| {
let sender = unreliable_sender.clone();
Box::pin(async move {
let _ = sender.send(message.data).await;
})
}));
let unreliable_poller = unreliable.clone();
tokio::spawn(async move {
while let Some(event) = unreliable_poller.poll().await {
if let DataChannelEvent::OnMessage(msg) = event {
let _ = unreliable_sender.send(msg.data.into()).await;
}
}
});
let offer = client.create_offer(None).await.unwrap();
let mut gathering_complete = client.gathering_complete_promise().await;
client.set_local_description(offer).await.unwrap();
gathering_complete.recv().await;
let _ = tokio::time::timeout(Duration::from_secs(2), client_notify.notified()).await;
let offer = client.local_description().await.unwrap();
let client_key = SigningKey::from_slice(&[8; 48]).unwrap();
let offer = add_server_identity(&offer.sdp, &client_key).unwrap();
let (incoming, mut receiver) = mpsc::channel(1);
let server_key = Arc::new(SigningKey::from_slice(&[9; 48]).unwrap());
let ice_socket = UdpSocket::bind("0.0.0.0:0").await.unwrap();
let ice_port = ice_socket.local_addr().unwrap().port();
let ice_local_addr = ice_socket.local_addr().unwrap();
let state = EndpointState {
incoming,
api: Arc::new(build_api(ice_socket, None).unwrap()),
identity_key: server_key.clone(),
require_client_identity: true,
oidc_verifier: None,
stun_servers: Arc::from([]),
ice_local_addr,
};
let (answer, server_session) =
let (answer, _server_session) =
negotiate(&state, "127.0.0.1:19132".parse().unwrap(), &offer, None)
.await
.unwrap();
let (answer, public_key) = verify_and_strip_identity(&answer, None).unwrap();
assert_eq!(public_key, PublicKey::from(server_key.verifying_key()));
assert!(answer.contains(&format!(" {ice_port} typ host")));
let answer = answer.replace(
"a=sctp-port:5000\r\n",
"a=sctp-port:5000\r\na=max-message-size:262144\r\n",
);
client
.set_remote_description(RTCSessionDescription::answer(answer).unwrap())
.await
.unwrap();
let reliable_poller = reliable.clone();
tokio::spawn(async move { while reliable_poller.poll().await.is_some() {} });
let Ok(Some((session, _))) =
tokio::time::timeout(Duration::from_secs(5), receiver.recv()).await
else {
panic!(
"connection did not open; client={:?}, server={:?}",
client.connection_state(),
server_session.peer.connection_state(),
);
panic!("connection did not open");
};
reliable
.send(&Bytes::from_static(b"\0hello"))
.send(BytesMut::from(&b"\0hello"[..]))
.await
.unwrap();
let packet = receive_packet(&session).await;
assert_eq!(packet, b"hello".as_slice());
let large_packet = vec![42; 100_000];
let mut segment = Vec::with_capacity(large_packet.len() + 1);
segment.push(0);
segment.extend_from_slice(&large_packet);
reliable.send(&Bytes::from(segment)).await.unwrap();
let chunks = large_packet.chunks(10_000).collect::<Vec<_>>();
let chunk_count = chunks.len();
for (index, chunk) in chunks.into_iter().enumerate() {
let mut segment = BytesMut::with_capacity(chunk.len() + 1);
segment.put_u8((chunk_count - index - 1) as u8);
segment.extend_from_slice(chunk);
reliable.send(segment).await.unwrap();
}
let packet = receive_packet(&session).await;
assert_eq!(packet, large_packet);
session

View File

@@ -17,7 +17,7 @@ use tokio::{
sync::{Mutex, mpsc},
};
use tracing::{debug, trace, warn};
use webrtc::ice_transport::ice_candidate::RTCIceCandidateInit;
use webrtc::peer_connection::RTCIceCandidateInit;
use super::{NetherNetListener, negotiate};
use crate::server::Server;
@@ -169,16 +169,22 @@ impl NetherNetDiscovery {
let offer = data.to_owned();
let network_id = self.network_id;
tokio::spawn(async move {
let signal =
match negotiate(&state, address, &offer, Some(candidate_receiver)).await {
Ok((answer, _session)) => {
format!("CONNECTRESPONSE {connection_id} {answer}")
}
Err(error) => {
warn!("NetherNet LAN negotiation with {address} failed: {error}");
format!("CONNECTERROR {connection_id} 11")
}
};
let signal = match Box::pin(negotiate(
&state,
address,
&offer,
Some(candidate_receiver),
))
.await
{
Ok((answer, _session)) => {
format!("CONNECTRESPONSE {connection_id} {answer}")
}
Err(error) => {
warn!("NetherNet LAN negotiation with {address} failed: {error}");
format!("CONNECTERROR {connection_id} 11")
}
};
match encode_message(network_id, sender_id, &signal) {
Ok(response) => {
if let Err(error) = socket.send_to(&response, address).await {

View File

@@ -1,11 +1,10 @@
use std::{
future::Future,
io::{Cursor, Error, ErrorKind},
io::{Cursor, Error},
net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr},
pin::Pin,
sync::Arc,
};
use crate::server::Server;
use bytes::Bytes;
use pumpkin_protocol::{
BClientPacket,
@@ -21,14 +20,7 @@ use tokio::{
net::UdpSocket,
sync::{Mutex, mpsc},
};
use tracing::{trace, warn};
use webrtc::util::{Conn, Error as WebRtcError};
use crate::server::Server;
// `webrtc::util::Conn` uses `async-trait`. Spell out its object-safe future ABI so
// Pumpkin does not need a direct dependency on the proc macro.
type ConnFuture<'a, T> = Pin<Box<dyn Future<Output = Result<T, WebRtcError>> + Send + 'a>>;
use tracing::trace;
pub struct StatusResponder {
ipv4: Arc<UdpSocket>,
@@ -147,119 +139,18 @@ impl IceSocket {
pub fn local_addr(&self) -> Result<SocketAddr, Error> {
self.socket.local_addr()
}
}
impl Conn for IceSocket {
fn connect<'a, 'async_trait>(&'a self, _address: SocketAddr) -> ConnFuture<'async_trait, ()>
where
'a: 'async_trait,
Self: 'async_trait,
{
Box::pin(async {
Err(Error::new(
ErrorKind::Unsupported,
"the shared Bedrock UDP socket cannot be connected",
)
.into())
})
pub async fn recv_from(&self, buffer: &mut [u8]) -> Result<(usize, SocketAddr), Error> {
let (packet, address) = self.packets.lock().await.recv().await.ok_or_else(|| {
Error::new(std::io::ErrorKind::BrokenPipe, "Bedrock UDP socket closed")
})?;
let length = buffer.len().min(packet.len());
buffer[..length].copy_from_slice(&packet[..length]);
Ok((length, address))
}
fn recv<'a, 'b, 'async_trait>(&'a self, buffer: &'b mut [u8]) -> ConnFuture<'async_trait, usize>
where
'a: 'async_trait,
'b: 'async_trait,
Self: 'async_trait,
{
Box::pin(async move { self.recv_from(buffer).await.map(|(length, _)| length) })
}
fn recv_from<'a, 'b, 'async_trait>(
&'a self,
buffer: &'b mut [u8],
) -> ConnFuture<'async_trait, (usize, SocketAddr)>
where
'a: 'async_trait,
'b: 'async_trait,
Self: 'async_trait,
{
Box::pin(async move {
let (packet, address) =
self.packets.lock().await.recv().await.ok_or_else(|| {
Error::new(ErrorKind::BrokenPipe, "Bedrock UDP socket closed")
})?;
let length = buffer.len().min(packet.len());
buffer[..length].copy_from_slice(&packet[..length]);
Ok((length, address))
})
}
fn send<'a, 'b, 'async_trait>(&'a self, _buffer: &'b [u8]) -> ConnFuture<'async_trait, usize>
where
'a: 'async_trait,
'b: 'async_trait,
Self: 'async_trait,
{
Box::pin(async {
Err(Error::new(
ErrorKind::NotConnected,
"the shared Bedrock UDP socket has no default peer",
)
.into())
})
}
fn send_to<'a, 'b, 'async_trait>(
&'a self,
buffer: &'b [u8],
target: SocketAddr,
) -> ConnFuture<'async_trait, usize>
where
'a: 'async_trait,
'b: 'async_trait,
Self: 'async_trait,
{
Box::pin(async move {
match self.socket.send_to(buffer, target).await {
Ok(length) => {
trace!(
%target,
length,
kind = ice_packet_kind(buffer),
"Sent Bedrock ICE datagram"
);
Ok(length)
}
Err(error) => {
warn!(
%target,
kind = ice_packet_kind(buffer),
%error,
"Failed to send Bedrock ICE datagram"
);
Err(error.into())
}
}
})
}
fn local_addr(&self) -> Result<SocketAddr, WebRtcError> {
Ok(self.socket.local_addr()?)
}
fn remote_addr(&self) -> Option<SocketAddr> {
None
}
fn close<'a, 'async_trait>(&'a self) -> ConnFuture<'async_trait, ()>
where
'a: 'async_trait,
Self: 'async_trait,
{
Box::pin(async { Ok(()) })
}
fn as_any(&self) -> &(dyn std::any::Any + Send + Sync) {
self
pub async fn send_to(&self, buffer: &[u8], target: SocketAddr) -> Result<usize, Error> {
self.socket.send_to(buffer, target).await
}
}
@@ -357,11 +248,11 @@ mod tests {
.unwrap();
let mut request = [0; 16];
let (length, address) = Conn::recv_from(&ice, &mut request).await.unwrap();
let (length, address) = ice.recv_from(&mut request).await.unwrap();
assert_eq!(&request[..length], b"request");
assert_eq!(address, client.local_addr().unwrap());
Conn::send_to(&ice, b"response", client.local_addr().unwrap())
ice.send_to(b"response", client.local_addr().unwrap())
.await
.unwrap();
let mut response = [0; 16];