ys_sniffer/lib.rs
1mod sniffer;
2mod cryptography;
3mod utils;
4
5use std::fmt::{Debug, Display, Formatter};
6use std::thread;
7use serde::{Deserialize, Serialize};
8
9use anyhow::Result;
10use crossbeam_channel::Sender;
11use log::{error, trace};
12
13#[cfg(feature = "tokio")]
14use tokio::sync::mpsc::UnboundedSender as AsyncSender;
15#[cfg(feature = "tokio")]
16use tokio::runtime::Handle;
17
18struct CrossbeamSender {
19 sender: Sender<GamePacket>
20}
21
22impl PacketSender for CrossbeamSender {
23 fn send(&self, data: GamePacket) {
24 self.sender.send(data).unwrap()
25 }
26}
27
28#[cfg(feature = "tokio")]
29struct TokioSender {
30 sender: AsyncSender<GamePacket>
31}
32
33#[cfg(feature = "tokio")]
34impl PacketSender for TokioSender {
35 fn send(&self, data: GamePacket) {
36 let sender = self.sender.clone();
37 _ = Handle::current().spawn(async move {
38 sender.send(data)
39 });
40 }
41}
42
43/// Sniffs game packets from the network using `pcap`.
44///
45/// If an error occurs while configuring the packet sniffer,
46/// it will be thrown in the `Result`.
47///
48/// # Examples
49///
50/// ```rust,no_run
51/// use ys_sniffer::Config;
52///
53/// fn main() -> anyhow::Result<()> {
54/// let (tx, rx) = crossbeam_channel::unbounded();
55/// let shutdown_hook = ys_sniffer::sniff(Config::default(), tx)?;
56///
57/// // To stop the sniffer, send a message to the shutdown hook.
58/// shutdown_hook.send(())?;
59///
60/// Ok(())
61/// }
62/// ```
63pub fn sniff(
64 config: Config,
65 consumer: Sender<GamePacket>
66) -> Result<Sender<()>> {
67 trace!("Configuration to be used: {:#?}", config);
68
69 // Create shutdown hook.
70 let (tx, rx) = crossbeam_channel::bounded(1);
71
72 // Create the packet sender.
73 let consumer = CrossbeamSender { sender: consumer };
74
75 // Run the packet sniffer.
76 thread::spawn(|| {
77 if let Err(error) = sniffer::run(config, rx, consumer) {
78 error!("Failed to run the sniffer: {:#?}", error);
79 }
80 });
81
82 Ok(tx)
83}
84
85/// Sniffs game packets from the network using `pcap`.
86///
87/// If an error occurs while configuring the packet sniffer,
88/// it will be thrown in the `Result`.
89///
90/// This requires a Tokio MPSC unbounded channel.
91///
92/// # Examples
93///
94/// ```rust,no_run
95/// use ys_sniffer::Config;
96///
97/// #[tokio::main]
98/// async fn main() -> anyhow::Result<()> {
99/// let (tx, rx) = tokio::sync::mpsc::unbounded_channel();
100/// let shutdown_hook = ys_sniffer::sniff_async(Config::default(), tx).await?;
101///
102/// // To stop the sniffer, send a message to the shutdown hook.
103/// shutdown_hook.send(())?;
104///
105/// Ok(())
106/// }
107/// ```
108#[cfg(feature = "tokio")]
109pub fn sniff_async(
110 config: Config,
111 consumer: AsyncSender<GamePacket>
112) -> Result<Sender<()>> {
113 trace!("Configuration to be used: {:#?}", config);
114
115 // Create shutdown hook.
116 let (tx, rx) = crossbeam_channel::bounded(1);
117
118 // Create the packet sender.
119 let consumer = TokioSender { sender: consumer };
120
121 // Run the packet sniffer.
122 tokio::spawn(async move {
123 if let Err(error) = sniffer::run(config, rx, consumer) {
124 error!("Failed to run the sniffer: {:#?}", error);
125 }
126 });
127
128 Ok(tx)
129}
130
131/// Represents a processed game packet.
132#[derive(Clone, Debug)]
133pub struct GamePacket {
134 pub id: u16,
135 pub header: Vec<u8>,
136 pub data: Vec<u8>,
137 pub source: PacketSource
138}
139
140impl Default for GamePacket {
141 fn default() -> Self {
142 GamePacket {
143 id: 0,
144 header: vec![],
145 data: vec![],
146 source: PacketSource::Server
147 }
148 }
149}
150
151/// Represents the source of a packet.
152#[derive(Serialize, Deserialize, Copy, Clone, PartialEq)]
153pub enum PacketSource {
154 /// This packet was sent by the client.
155 ///
156 /// Common names include: `<name>Req`, `<name>Notify`, `<name>C2S<side>`
157 Client,
158
159 /// This packet was sent by the server.
160 ///
161 /// Common names include: `<name>Rsp`, `<name>Notify`, `<name>S2C<side>`
162 Server
163}
164
165impl PacketSource {
166 /// Determines the packet source based on the configuration and port.
167 ///
168 /// # Example
169 ///
170 /// ```rust,no_run
171 /// use ys_sniffer::{PacketSource, Config};
172 ///
173 /// let config = Config {
174 /// server_port: vec![22101, 22102],
175 /// ..Default::default()
176 /// };
177 ///
178 /// let result = PacketSource::from(&config, 22101);
179 /// assert_eq!(result, PacketSource::Server);
180 /// ```
181 pub fn from(config: &Config, port: u16) -> PacketSource {
182 if config.server_port.contains(&port) {
183 PacketSource::Server
184 } else {
185 PacketSource::Client
186 }
187 }
188
189 /// Converts the packet source to a string.
190 pub fn to_string(&self) -> &'static str {
191 match self {
192 PacketSource::Client => "Client",
193 PacketSource::Server => "Server"
194 }
195 }
196
197 /// Simple utility method to determine if the packet is from the client.
198 pub fn is_client(&self) -> bool {
199 self.eq(&PacketSource::Client)
200 }
201
202 /// Simple utility method to determine if the packet is from the server.
203 pub fn is_server(&self) -> bool {
204 self.eq(&PacketSource::Server)
205 }
206}
207
208impl Display for PacketSource {
209 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
210 write!(f, "{}", self.to_string())
211 }
212}
213
214impl Debug for PacketSource {
215 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
216 write!(f, "{}", self.to_string())
217 }
218}
219
220impl Default for PacketSource {
221 fn default() -> Self {
222 PacketSource::Server
223 }
224}
225
226/// Configuration used for the sniffer.
227///
228/// This does not include any programmable logic.
229#[derive(Deserialize, Serialize, Clone, Debug)]
230pub struct Config {
231 /// This is the name of the capturing device to use.
232 /// The name can be found with `pcap_findalldevs`.
233 ///
234 /// When left blank, the first active device is used.
235 pub device_name: Option<String>,
236
237 /// This is the filter to apply to the capture device.
238 /// A blank filter can result in the program receiving more traffic than necessary.
239 ///
240 /// When left blank, no filter is applied.
241 ///
242 /// # Default
243 ///
244 /// `udp portrange 22101-22102`
245 pub filter: Option<String>,
246
247 /// A list of ports which the server listens for traffic on.
248 /// This is used for determining if a packet is incoming or outgoing.
249 ///
250 /// This cannot be left blank.
251 ///
252 /// # Default
253 ///
254 /// `[22101, 22102]`
255 pub server_port: Vec<u16>,
256
257 /// The path to a file containing known seeds.
258 /// The specific path needs to be readable and writable.
259 ///
260 /// This cannot be left blank.
261 ///
262 /// # Default
263 ///
264 /// `known_seeds.txt`
265 pub known_seeds: String
266}
267
268impl Default for Config {
269 fn default() -> Self {
270 Config {
271 device_name: None,
272 filter: Some("udp portrange 22101-22102".to_string()),
273 server_port: vec![22101, 22102],
274 known_seeds: "known_seeds.txt".to_string()
275 }
276 }
277}
278
279// If the feature is enabled, include the `processor` module.
280#[cfg(feature = "processor")]
281pub use sniffer::Processor;
282use crate::sniffer::PacketSender;