Skip to main content

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;