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
|
||||
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 query_server;
|
||||
pub mod query_server;
|
||||
pub mod wiring;
|
||||
|
||||
// Reexports
|
||||
|
@ -13,6 +13,10 @@ use tracing::{debug, info, trace};
|
||||
// NOTE: Debug logging is disabled by default for this module - to enable it add
|
||||
// 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 bind_addr: SocketAddr,
|
||||
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 {
|
||||
const VELOREN_HEADER: [u8; 7] = [0x56, 0x45, 0x4C, 0x4F, 0x52, 0x45, 0x4E];
|
||||
|
||||
if len < 8 {
|
||||
trace!("Ignoring packet - too short");
|
||||
false
|
||||
@ -86,46 +88,12 @@ impl QueryServer {
|
||||
QueryServer::send_response(remote_addr, &QueryServerPacketKind::Pong(Pong {}))
|
||||
.await?
|
||||
},
|
||||
QueryServerPacketKind::ServerInfoQuery(ref _query) => {
|
||||
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 {
|
||||
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::ServerInfoQuery(query) => {
|
||||
self.handle_server_info_query(remote_addr, query)?
|
||||
},
|
||||
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");
|
||||
},
|
||||
}
|
||||
@ -152,6 +120,53 @@ impl QueryServer {
|
||||
|
||||
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)]
|
||||
@ -159,6 +174,7 @@ enum QueryError {
|
||||
NetworkError(io::Error),
|
||||
ProtocolError(protocol::Error),
|
||||
ChannelError(String),
|
||||
ValidationError(String),
|
||||
}
|
||||
|
||||
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) }
|
||||
}
|
||||
|
||||
#[derive(protocol::Protocol, Clone, Debug, PartialEq)]
|
||||
#[derive(Protocol, Clone, Debug, PartialEq)]
|
||||
pub struct Ping;
|
||||
|
||||
#[derive(protocol::Protocol, Clone, Debug, PartialEq)]
|
||||
#[derive(Protocol, Clone, Debug, PartialEq)]
|
||||
pub struct Pong;
|
||||
|
||||
#[derive(protocol::Protocol, Clone, Debug, PartialEq)]
|
||||
pub struct ServerInfoQuery;
|
||||
#[derive(Protocol, Clone, Debug, PartialEq)]
|
||||
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(discriminator(u8))]
|
||||
pub enum QueryServerPacketKind {
|
||||
|
Loading…
Reference in New Issue
Block a user