mirror of
https://github.com/italicsjenga/valence.git
synced 2025-01-27 22:16:32 +11:00
Packet filtering (#67)
* Add `PacketName` * Fix inefficiency in codec.rs * Add regex argument to packet_inspector * Reorder imports * Make the regex arg optional * Update README
This commit is contained in:
parent
7c23d7a086
commit
0a75f103aa
5 changed files with 67 additions and 17 deletions
|
@ -10,3 +10,4 @@ clap = { version = "3.2.8", features = ["derive"] }
|
||||||
tokio = { version = "1", features = ["full"] }
|
tokio = { version = "1", features = ["full"] }
|
||||||
anyhow = "1"
|
anyhow = "1"
|
||||||
chrono = "0.4.19"
|
chrono = "0.4.19"
|
||||||
|
regex = "1.6.0"
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
The packet inspector is a very simple Minecraft proxy for viewing the contents of packets as they are sent/received.
|
The packet inspector is a very simple Minecraft proxy for viewing the contents of packets as they are sent/received.
|
||||||
It uses Valence's protocol facilities to print packet contents.
|
It uses Valence's protocol facilities to print packet contents.
|
||||||
This was made for three purposes:
|
This was made for three purposes:
|
||||||
|
|
||||||
- Check that packets between Valence and client are matching your expectations.
|
- Check that packets between Valence and client are matching your expectations.
|
||||||
- Check that packets between vanilla server and client are parsed correctly by Valence.
|
- Check that packets between vanilla server and client are parsed correctly by Valence.
|
||||||
- Understand how the protocol works between the vanilla server and client.
|
- Understand how the protocol works between the vanilla server and client.
|
||||||
|
@ -14,6 +15,7 @@ First, start a server
|
||||||
```sh
|
```sh
|
||||||
cargo r -r --example conway
|
cargo r -r --example conway
|
||||||
```
|
```
|
||||||
|
|
||||||
In a separate terminal, start the packet inspector.
|
In a separate terminal, start the packet inspector.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
|
@ -22,7 +24,19 @@ cargo r -r -p packet_inspector -- 127.0.0.1:25566 127.0.0.1:25565
|
||||||
|
|
||||||
The client must connect to `localhost:25566`. You should see the packets in `stdout`.
|
The client must connect to `localhost:25566`. You should see the packets in `stdout`.
|
||||||
|
|
||||||
If you only want to see errors, direct `stdout` elsewhere.
|
The third argument to the packet inspector is an optional regular expression compatible with
|
||||||
|
the [regex](https://docs.rs/regex/latest/regex/) crate. Packets with names that match the regex are printed while those
|
||||||
|
that don't are ignored. If the regex is not provided then the empty string is assumed and all packets are considered
|
||||||
|
matching.
|
||||||
|
|
||||||
|
If you're only interested in packets `Foo`, `Bar`, and `Baz`, you can use a regex such as `^(Foo|Bar|Baz)$`.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cargo r -r -p packet_inspector -- 127.0.0.1:25566 127.0.0.1:25565 '^(Foo|Bar|Baz)$'
|
||||||
|
```
|
||||||
|
|
||||||
|
Packets are printed to `stdout` while errors are printed to `stderr`. If you only want to see errors in your terminal,
|
||||||
|
direct `stdout` elsewhere.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
cargo r -r -p packet_inspector -- 127.0.0.1:25566 127.0.0.1:25565 > log.txt
|
cargo r -r -p packet_inspector -- 127.0.0.1:25566 127.0.0.1:25565 > log.txt
|
||||||
|
@ -31,17 +45,20 @@ cargo r -r -p packet_inspector -- 127.0.0.1:25566 127.0.0.1:25565 > log.txt
|
||||||
## Quick start with Vanilla Server via Docker
|
## Quick start with Vanilla Server via Docker
|
||||||
|
|
||||||
Start the server
|
Start the server
|
||||||
```bash
|
|
||||||
|
```sh
|
||||||
docker run -e EULA=TRUE -e ONLINE_MODE=false -d -p 25565:25565 --name mc itzg/minecraft-server
|
docker run -e EULA=TRUE -e ONLINE_MODE=false -d -p 25565:25565 --name mc itzg/minecraft-server
|
||||||
```
|
```
|
||||||
|
|
||||||
View server logs
|
View server logs
|
||||||
```bash
|
|
||||||
|
```sh
|
||||||
docker logs -f mc
|
docker logs -f mc
|
||||||
```
|
```
|
||||||
|
|
||||||
Server Rcon
|
Server Rcon
|
||||||
```bash
|
|
||||||
|
```sh
|
||||||
docker exec -i mc rcon-cli
|
docker exec -i mc rcon-cli
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -54,6 +71,7 @@ cargo r -r -p packet_inspector -- 127.0.0.1:25566 127.0.0.1:25565
|
||||||
Open Minecraft and connect to `localhost:25566`.
|
Open Minecraft and connect to `localhost:25566`.
|
||||||
|
|
||||||
Clean up
|
Clean up
|
||||||
|
|
||||||
```
|
```
|
||||||
docker stop mc
|
docker stop mc
|
||||||
docker rm mc
|
docker rm mc
|
||||||
|
|
|
@ -8,6 +8,7 @@ use std::{fmt, io};
|
||||||
use anyhow::bail;
|
use anyhow::bail;
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
|
use regex::Regex;
|
||||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||||
use tokio::net::tcp::{OwnedReadHalf, OwnedWriteHalf};
|
use tokio::net::tcp::{OwnedReadHalf, OwnedWriteHalf};
|
||||||
use tokio::net::{TcpListener, TcpStream};
|
use tokio::net::{TcpListener, TcpStream};
|
||||||
|
@ -20,7 +21,7 @@ use valence::protocol::packets::c2s::status::{QueryPing, QueryRequest};
|
||||||
use valence::protocol::packets::s2c::login::{LoginSuccess, S2cLoginPacket};
|
use valence::protocol::packets::s2c::login::{LoginSuccess, S2cLoginPacket};
|
||||||
use valence::protocol::packets::s2c::play::S2cPlayPacket;
|
use valence::protocol::packets::s2c::play::S2cPlayPacket;
|
||||||
use valence::protocol::packets::s2c::status::{QueryPong, QueryResponse};
|
use valence::protocol::packets::s2c::status::{QueryPong, QueryResponse};
|
||||||
use valence::protocol::packets::{DecodePacket, EncodePacket};
|
use valence::protocol::packets::{DecodePacket, EncodePacket, PacketName};
|
||||||
use valence::protocol::{Encode, VarInt};
|
use valence::protocol::{Encode, VarInt};
|
||||||
|
|
||||||
#[derive(Parser, Clone, Debug)]
|
#[derive(Parser, Clone, Debug)]
|
||||||
|
@ -29,26 +30,36 @@ struct Cli {
|
||||||
/// The socket address to listen for connections on. This is the address
|
/// The socket address to listen for connections on. This is the address
|
||||||
/// clients should connect to.
|
/// clients should connect to.
|
||||||
client: SocketAddr,
|
client: SocketAddr,
|
||||||
/// The socket address the proxy will connect to.
|
/// The socket address the proxy will connect to. This is the address of the
|
||||||
|
/// server.
|
||||||
server: SocketAddr,
|
server: SocketAddr,
|
||||||
|
/// The optional regular expression to use on packet names. Packet names
|
||||||
|
/// matching the regex are printed while those that don't are ignored.
|
||||||
|
///
|
||||||
|
/// If no regex is provided, all packets are considered matching.
|
||||||
|
regex: Option<Regex>,
|
||||||
/// The maximum number of connections allowed to the proxy. By default,
|
/// The maximum number of connections allowed to the proxy. By default,
|
||||||
/// there is no limit.
|
/// there is no limit.
|
||||||
#[clap(short, long)]
|
#[clap(short, long)]
|
||||||
max_connections: Option<usize>,
|
max_connections: Option<usize>,
|
||||||
|
|
||||||
/// Print a timestamp before each packet.
|
/// Print a timestamp before each packet.
|
||||||
#[clap(short, long)]
|
#[clap(short, long)]
|
||||||
timestamp: bool,
|
timestamp: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Cli {
|
impl Cli {
|
||||||
fn print(&self, d: &impl fmt::Debug) {
|
fn print(&self, p: &(impl fmt::Debug + PacketName)) {
|
||||||
|
if let Some(r) = &self.regex {
|
||||||
|
if !r.is_match(p.packet_name()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if self.timestamp {
|
if self.timestamp {
|
||||||
let now: DateTime<Utc> = Utc::now();
|
let now: DateTime<Utc> = Utc::now();
|
||||||
println!("{now} {d:#?}");
|
println!("{now} {p:#?}");
|
||||||
} else {
|
} else {
|
||||||
println!("{d:#?}");
|
println!("{p:#?}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -200,10 +200,8 @@ impl<R: AsyncRead + Unpin> Decoder<R> {
|
||||||
.context("decoding packet after decompressing")?;
|
.context("decoding packet after decompressing")?;
|
||||||
ensure!(
|
ensure!(
|
||||||
decompressed.is_empty(),
|
decompressed.is_empty(),
|
||||||
format!(
|
"packet contents were not read completely ({} bytes remaining)",
|
||||||
"packet contents were not read completely, {} remaining bytes",
|
decompressed.len()
|
||||||
decompressed.len()
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
packet
|
packet
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -28,12 +28,18 @@ use crate::protocol::{
|
||||||
};
|
};
|
||||||
use crate::text::Text;
|
use crate::text::Text;
|
||||||
|
|
||||||
|
/// Provides the name of a packet for debugging purposes.
|
||||||
|
pub trait PacketName {
|
||||||
|
/// The name of this packet.
|
||||||
|
fn packet_name(&self) -> &'static str;
|
||||||
|
}
|
||||||
|
|
||||||
/// Trait for types that can be written to the Minecraft protocol as a complete
|
/// Trait for types that can be written to the Minecraft protocol as a complete
|
||||||
/// packet.
|
/// packet.
|
||||||
///
|
///
|
||||||
/// A complete packet is one that starts with a `VarInt` packet ID, followed by
|
/// A complete packet is one that starts with a `VarInt` packet ID, followed by
|
||||||
/// the body of the packet.
|
/// the body of the packet.
|
||||||
pub trait EncodePacket: fmt::Debug {
|
pub trait EncodePacket: PacketName + fmt::Debug {
|
||||||
/// Writes a packet to the Minecraft protocol, including its packet ID.
|
/// Writes a packet to the Minecraft protocol, including its packet ID.
|
||||||
fn encode_packet(&self, w: &mut impl Write) -> anyhow::Result<()>;
|
fn encode_packet(&self, w: &mut impl Write) -> anyhow::Result<()>;
|
||||||
}
|
}
|
||||||
|
@ -43,7 +49,7 @@ pub trait EncodePacket: fmt::Debug {
|
||||||
///
|
///
|
||||||
/// A complete packet is one that starts with a `VarInt` packet ID, followed by
|
/// A complete packet is one that starts with a `VarInt` packet ID, followed by
|
||||||
/// the body of the packet.
|
/// the body of the packet.
|
||||||
pub trait DecodePacket: Sized + fmt::Debug {
|
pub trait DecodePacket: Sized + PacketName + fmt::Debug {
|
||||||
/// Reads a packet from the Minecraft protocol, including its packet ID.
|
/// Reads a packet from the Minecraft protocol, including its packet ID.
|
||||||
fn decode_packet(r: &mut &[u8]) -> anyhow::Result<Self>;
|
fn decode_packet(r: &mut &[u8]) -> anyhow::Result<Self>;
|
||||||
}
|
}
|
||||||
|
@ -295,6 +301,12 @@ macro_rules! def_packet_group {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl PacketName for $packet {
|
||||||
|
fn packet_name(&self) -> &'static str {
|
||||||
|
stringify!($packet)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl EncodePacket for $packet {
|
impl EncodePacket for $packet {
|
||||||
fn encode_packet(&self, w: &mut impl Write) -> anyhow::Result<()> {
|
fn encode_packet(&self, w: &mut impl Write) -> anyhow::Result<()> {
|
||||||
VarInt($id).encode(w).context("failed to write packet ID")?;
|
VarInt($id).encode(w).context("failed to write packet ID")?;
|
||||||
|
@ -316,6 +328,16 @@ macro_rules! def_packet_group {
|
||||||
}
|
}
|
||||||
)*
|
)*
|
||||||
|
|
||||||
|
impl PacketName for $group_name {
|
||||||
|
fn packet_name(&self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
$(
|
||||||
|
Self::$packet(pkt) => pkt.packet_name(),
|
||||||
|
)*
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl DecodePacket for $group_name {
|
impl DecodePacket for $group_name {
|
||||||
fn decode_packet(r: &mut &[u8]) -> anyhow::Result<Self> {
|
fn decode_packet(r: &mut &[u8]) -> anyhow::Result<Self> {
|
||||||
let packet_id = VarInt::decode(r)
|
let packet_id = VarInt::decode(r)
|
||||||
|
|
Loading…
Add table
Reference in a new issue