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:
Ryan Johnson 2022-09-16 05:31:37 -07:00 committed by GitHub
parent 7c23d7a086
commit 0a75f103aa
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 67 additions and 17 deletions

View file

@ -10,3 +10,4 @@ clap = { version = "3.2.8", features = ["derive"] }
tokio = { version = "1", features = ["full"] }
anyhow = "1"
chrono = "0.4.19"
regex = "1.6.0"

View file

@ -3,6 +3,7 @@
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.
This was made for three purposes:
- Check that packets between Valence and client are matching your expectations.
- Check that packets between vanilla server and client are parsed correctly by Valence.
- Understand how the protocol works between the vanilla server and client.
@ -14,6 +15,7 @@ First, start a server
```sh
cargo r -r --example conway
```
In a separate terminal, start the packet inspector.
```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`.
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
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
Start the server
```bash
```sh
docker run -e EULA=TRUE -e ONLINE_MODE=false -d -p 25565:25565 --name mc itzg/minecraft-server
```
View server logs
```bash
```sh
docker logs -f mc
```
Server Rcon
```bash
```sh
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`.
Clean up
```
docker stop mc
docker rm mc

View file

@ -8,6 +8,7 @@ use std::{fmt, io};
use anyhow::bail;
use chrono::{DateTime, Utc};
use clap::Parser;
use regex::Regex;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::tcp::{OwnedReadHalf, OwnedWriteHalf};
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::play::S2cPlayPacket;
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};
#[derive(Parser, Clone, Debug)]
@ -29,26 +30,36 @@ struct Cli {
/// The socket address to listen for connections on. This is the address
/// clients should connect to.
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,
/// 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,
/// there is no limit.
#[clap(short, long)]
max_connections: Option<usize>,
/// Print a timestamp before each packet.
#[clap(short, long)]
timestamp: bool,
}
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 {
let now: DateTime<Utc> = Utc::now();
println!("{now} {d:#?}");
println!("{now} {p:#?}");
} else {
println!("{d:#?}");
println!("{p:#?}");
}
}

View file

@ -200,10 +200,8 @@ impl<R: AsyncRead + Unpin> Decoder<R> {
.context("decoding packet after decompressing")?;
ensure!(
decompressed.is_empty(),
format!(
"packet contents were not read completely, {} remaining bytes",
decompressed.len()
)
"packet contents were not read completely ({} bytes remaining)",
decompressed.len()
);
packet
} else {

View file

@ -28,12 +28,18 @@ use crate::protocol::{
};
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
/// packet.
///
/// A complete packet is one that starts with a `VarInt` packet ID, followed by
/// 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.
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
/// 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.
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 {
fn encode_packet(&self, w: &mut impl Write) -> anyhow::Result<()> {
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 {
fn decode_packet(r: &mut &[u8]) -> anyhow::Result<Self> {
let packet_id = VarInt::decode(r)