mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Added protection against UDP amplification attacks to QueryServerInfo message handling. Added query_client bin to test Query Server
This commit is contained in:
parent
e9bba9999b
commit
bbfcf224c5
@ -71,3 +71,6 @@ refinery = { git = "https://gitlab.com/veloren/refinery.git", rev = "8ecf4b4772d
|
|||||||
|
|
||||||
# Plugins
|
# Plugins
|
||||||
plugin-api = { package = "veloren-plugin-api", path = "../plugin/api"}
|
plugin-api = { package = "veloren-plugin-api", path = "../plugin/api"}
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "query_client"
|
81
server/src/bin/query_client/main.rs
Normal file
81
server/src/bin/query_client/main.rs
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
use std::{
|
||||||
|
error::Error,
|
||||||
|
io::Cursor,
|
||||||
|
net::{IpAddr, Ipv4Addr, SocketAddr, SocketAddrV4},
|
||||||
|
time::{Duration, Instant},
|
||||||
|
};
|
||||||
|
|
||||||
|
use tokio::{net::UdpSocket, time::timeout};
|
||||||
|
use veloren_server::query_server::{Pong, QueryServerPacketKind, VELOREN_HEADER};
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() -> Result<(), Box<dyn Error>> {
|
||||||
|
let dest = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 14006);
|
||||||
|
|
||||||
|
// Ping
|
||||||
|
let (result, elapsed) = send_query(
|
||||||
|
QueryServerPacketKind::Ping(veloren_server::query_server::Ping),
|
||||||
|
dest,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
if matches!(result, QueryServerPacketKind::Pong(Pong)) {
|
||||||
|
println!("Ping response took {}ms", elapsed.as_millis());
|
||||||
|
} else {
|
||||||
|
return Err("Unexpected response to Ping received".into());
|
||||||
|
}
|
||||||
|
|
||||||
|
// ServerInfoQuery
|
||||||
|
let (result, elapsed) = send_query(
|
||||||
|
QueryServerPacketKind::ServerInfoQuery(veloren_server::query_server::ServerInfoQuery {
|
||||||
|
padding: [0xFF; 512],
|
||||||
|
}),
|
||||||
|
dest,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
if let QueryServerPacketKind::ServerInfoResponse(_) = result {
|
||||||
|
println!(
|
||||||
|
"ServerInfoQuery response took {}ms - data: {:?}",
|
||||||
|
elapsed.as_millis(),
|
||||||
|
result
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return Err("Unexpected response to ServerInfoQuery received".into());
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn send_query(
|
||||||
|
query: QueryServerPacketKind,
|
||||||
|
dest: SocketAddr,
|
||||||
|
) -> Result<(QueryServerPacketKind, Duration), Box<dyn Error>> {
|
||||||
|
let socket = UdpSocket::bind(SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, 0)).await?;
|
||||||
|
|
||||||
|
let mut pipeline = protocol::wire::dgram::Pipeline::new(
|
||||||
|
protocol::wire::middleware::pipeline::default(),
|
||||||
|
protocol::Settings::default(),
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut buf = Vec::<u8>::new();
|
||||||
|
// All outgoing datagrams must start with the "VELOREN" header
|
||||||
|
buf.append(&mut VELOREN_HEADER.to_vec());
|
||||||
|
|
||||||
|
let mut cursor = Cursor::new(&mut buf);
|
||||||
|
cursor.set_position(VELOREN_HEADER.len() as u64);
|
||||||
|
pipeline.send_to(&mut cursor, &query)?;
|
||||||
|
|
||||||
|
let query_sent = Instant::now();
|
||||||
|
socket.send_to(buf.as_slice(), dest).await?;
|
||||||
|
|
||||||
|
let mut buf = vec![0; 1500];
|
||||||
|
let _ = timeout(Duration::from_secs(2), socket.recv_from(&mut buf))
|
||||||
|
.await
|
||||||
|
.expect("Socket receive failed")
|
||||||
|
.expect("Socket receive timeout");
|
||||||
|
|
||||||
|
let packet: QueryServerPacketKind = pipeline.receive_from(&mut Cursor::new(&mut buf))?;
|
||||||
|
|
||||||
|
Ok((packet, query_sent.elapsed()))
|
||||||
|
}
|
@ -38,7 +38,7 @@ pub mod terrain_persistence;
|
|||||||
|
|
||||||
mod weather;
|
mod weather;
|
||||||
|
|
||||||
mod query_server;
|
pub mod query_server;
|
||||||
pub mod wiring;
|
pub mod wiring;
|
||||||
|
|
||||||
// Reexports
|
// Reexports
|
||||||
|
@ -13,6 +13,10 @@ use tracing::{debug, info, trace};
|
|||||||
// NOTE: Debug logging is disabled by default for this module - to enable it add
|
// NOTE: Debug logging is disabled by default for this module - to enable it add
|
||||||
// veloren_server::query_server=trace to RUST_LOG
|
// veloren_server::query_server=trace to RUST_LOG
|
||||||
|
|
||||||
|
pub const VELOREN_HEADER: [u8; 7] = [0x56, 0x45, 0x4C, 0x4F, 0x52, 0x45, 0x4E];
|
||||||
|
|
||||||
|
/// Implements the Veloren Query Server, used by external applications to query
|
||||||
|
/// real-time status information from the server.
|
||||||
pub struct QueryServer {
|
pub struct QueryServer {
|
||||||
pub bind_addr: SocketAddr,
|
pub bind_addr: SocketAddr,
|
||||||
pipeline: dgram::Pipeline<QueryServerPacketKind, protocol::wire::middleware::pipeline::Default>,
|
pipeline: dgram::Pipeline<QueryServerPacketKind, protocol::wire::middleware::pipeline::Default>,
|
||||||
@ -54,8 +58,6 @@ impl QueryServer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn validate_datagram(len: usize, data: &mut Vec<u8>) -> bool {
|
fn validate_datagram(len: usize, data: &mut Vec<u8>) -> bool {
|
||||||
const VELOREN_HEADER: [u8; 7] = [0x56, 0x45, 0x4C, 0x4F, 0x52, 0x45, 0x4E];
|
|
||||||
|
|
||||||
if len < 8 {
|
if len < 8 {
|
||||||
trace!("Ignoring packet - too short");
|
trace!("Ignoring packet - too short");
|
||||||
false
|
false
|
||||||
@ -86,46 +88,12 @@ impl QueryServer {
|
|||||||
QueryServer::send_response(remote_addr, &QueryServerPacketKind::Pong(Pong {}))
|
QueryServer::send_response(remote_addr, &QueryServerPacketKind::Pong(Pong {}))
|
||||||
.await?
|
.await?
|
||||||
},
|
},
|
||||||
QueryServerPacketKind::ServerInfoQuery(ref _query) => {
|
QueryServerPacketKind::ServerInfoQuery(query) => {
|
||||||
let (sender, receiver) = tokio::sync::oneshot::channel::<ServerInfoResponse>();
|
self.handle_server_info_query(remote_addr, query)?
|
||||||
let req = ServerInfoRequest {
|
|
||||||
response_sender: sender,
|
|
||||||
};
|
|
||||||
self.server_info_request_sender
|
|
||||||
.send(req)
|
|
||||||
.map_err(|e| QueryError::ChannelError(format!("{}", e)))?;
|
|
||||||
|
|
||||||
tokio::spawn(async move {
|
|
||||||
match timeout(Duration::from_secs(2), async move {
|
|
||||||
match receiver.await {
|
|
||||||
Ok(response) => {
|
|
||||||
trace!(?response, "Sending ServerInfoResponse");
|
|
||||||
QueryServer::send_response(
|
|
||||||
remote_addr,
|
|
||||||
&QueryServerPacketKind::ServerInfoResponse(response),
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.expect("Failed to send response"); // TODO remove expect
|
|
||||||
},
|
|
||||||
Err(_) => {
|
|
||||||
// Oneshot receive error
|
|
||||||
},
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
Ok(_) => {},
|
|
||||||
Err(elapsed) => {
|
|
||||||
debug!(
|
|
||||||
?elapsed,
|
|
||||||
"Timeout expired while waiting for ServerInfoResponse"
|
|
||||||
);
|
|
||||||
},
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
QueryServerPacketKind::Pong(_) | QueryServerPacketKind::ServerInfoResponse(_) => {
|
QueryServerPacketKind::Pong(_) | QueryServerPacketKind::ServerInfoResponse(_) => {
|
||||||
// Ignore any incoming packets
|
// Ignore any incoming datagrams for packets that should only be sent as
|
||||||
|
// responses
|
||||||
debug!(?packet, "Dropping received response packet");
|
debug!(?packet, "Dropping received response packet");
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -152,6 +120,53 @@ impl QueryServer {
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn handle_server_info_query(
|
||||||
|
&mut self,
|
||||||
|
remote_addr: SocketAddr,
|
||||||
|
query: ServerInfoQuery,
|
||||||
|
) -> Result<(), QueryError> {
|
||||||
|
// Ensure that the required padding is present
|
||||||
|
if !query.padding.iter().all(|x| *x == 0xFF) {
|
||||||
|
return Err(QueryError::ValidationError("Missing padding".into()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send a request to the server_info system to retrieve the live server
|
||||||
|
// information. This takes up to the duration of a full tick to process,
|
||||||
|
// so a future is spawned to wait for the result which is then sent as a
|
||||||
|
// UDP response.
|
||||||
|
let (sender, receiver) = tokio::sync::oneshot::channel::<ServerInfoResponse>();
|
||||||
|
let req = ServerInfoRequest {
|
||||||
|
response_sender: sender,
|
||||||
|
};
|
||||||
|
self.server_info_request_sender
|
||||||
|
.send(req)
|
||||||
|
.map_err(|e| QueryError::ChannelError(format!("{}", e)))?;
|
||||||
|
|
||||||
|
tokio::spawn(async move {
|
||||||
|
// Wait to receive the response from the server_info system
|
||||||
|
timeout(Duration::from_secs(2), async move {
|
||||||
|
match receiver.await {
|
||||||
|
Ok(response) => {
|
||||||
|
trace!(?response, "Sending ServerInfoResponse");
|
||||||
|
QueryServer::send_response(
|
||||||
|
remote_addr,
|
||||||
|
&QueryServerPacketKind::ServerInfoResponse(response),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
},
|
||||||
|
Err(_) => {
|
||||||
|
// Oneshot receive error
|
||||||
|
Err(QueryError::ChannelError("Oneshot receive error".to_owned()))
|
||||||
|
},
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.map_err(|_| debug!("Timeout expired while waiting for ServerInfoResponse"))
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@ -159,6 +174,7 @@ enum QueryError {
|
|||||||
NetworkError(io::Error),
|
NetworkError(io::Error),
|
||||||
ProtocolError(protocol::Error),
|
ProtocolError(protocol::Error),
|
||||||
ChannelError(String),
|
ChannelError(String),
|
||||||
|
ValidationError(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<protocol::Error> for QueryError {
|
impl From<protocol::Error> for QueryError {
|
||||||
@ -169,16 +185,19 @@ impl From<io::Error> for QueryError {
|
|||||||
fn from(e: io::Error) -> Self { QueryError::NetworkError(e) }
|
fn from(e: io::Error) -> Self { QueryError::NetworkError(e) }
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(protocol::Protocol, Clone, Debug, PartialEq)]
|
#[derive(Protocol, Clone, Debug, PartialEq)]
|
||||||
pub struct Ping;
|
pub struct Ping;
|
||||||
|
|
||||||
#[derive(protocol::Protocol, Clone, Debug, PartialEq)]
|
#[derive(Protocol, Clone, Debug, PartialEq)]
|
||||||
pub struct Pong;
|
pub struct Pong;
|
||||||
|
|
||||||
#[derive(protocol::Protocol, Clone, Debug, PartialEq)]
|
#[derive(Protocol, Clone, Debug, PartialEq)]
|
||||||
pub struct ServerInfoQuery;
|
pub struct ServerInfoQuery {
|
||||||
|
// Used to avoid UDP amplification attacks by requiring more data to be sent than is received
|
||||||
|
pub padding: [u8; 512],
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(protocol::Protocol, Clone, Debug, PartialEq)]
|
#[derive(Protocol, Clone, Debug, PartialEq)]
|
||||||
#[protocol(discriminant = "integer")]
|
#[protocol(discriminant = "integer")]
|
||||||
#[protocol(discriminator(u8))]
|
#[protocol(discriminator(u8))]
|
||||||
pub enum QueryServerPacketKind {
|
pub enum QueryServerPacketKind {
|
||||||
|
Loading…
Reference in New Issue
Block a user