Skip to main content

mc_gate/
lib.rs

1pub mod mc;
2
3use dashmap::DashMap;
4use futures::future::BoxFuture;
5use std::sync::Arc;
6use std::sync::atomic::{AtomicBool, Ordering};
7use std::time::{Duration, Instant};
8use t_port::{Protocol, identify, tunnel};
9use tokio::net::{TcpListener, TcpStream};
10use tokio::time::timeout;
11
12pub type WakeupCallback = Arc<dyn Fn() -> BoxFuture<'static, ()> + Send + Sync>;
13
14#[derive(Clone, Copy, Debug, PartialEq)]
15pub enum WakeupCondition {
16    Motd,
17    Join,
18    Disabled,
19}
20
21pub struct Config {
22    pub listen: String,
23    pub web: Option<String>,
24    pub mc: String,
25    pub wakeup_on: WakeupCondition,
26    pub debug: bool,
27    pub on_wakeup: Option<WakeupCallback>,
28    pub is_waking: AtomicBool,
29
30    pub msg_motd: String,
31    pub msg_starting: String,
32    pub msg_waitlist: String,
33    pub msg_online: String,
34    pub msg_timeout: String,
35}
36
37pub struct UserHistory {
38    pub attempts: u32,
39    pub last_seen: Instant,
40}
41
42pub async fn run(cfg: Arc<Config>) -> Result<(), Box<dyn std::error::Error>> {
43    let history: Arc<DashMap<String, UserHistory>> = Arc::new(DashMap::new());
44    let listener = TcpListener::bind(&cfg.listen).await?;
45    println!("Proxy active on {}", cfg.listen);
46
47    let history_gc = Arc::clone(&history);
48    tokio::spawn(async move {
49        let mut interval = tokio::time::interval(Duration::from_secs(60));
50        loop {
51            interval.tick().await;
52            history_gc.retain(|_, v| v.last_seen.elapsed() < Duration::from_secs(300));
53        }
54    });
55
56    loop {
57        let (socket, addr) = listener.accept().await?;
58        let cfg = Arc::clone(&cfg);
59        let history = Arc::clone(&history);
60        let ip = addr.ip().to_string();
61
62        tokio::spawn(async move {
63            let _ = process(socket, cfg, history, ip).await;
64        });
65    }
66}
67
68async fn process(
69    mut socket: TcpStream,
70    cfg: Arc<Config>,
71    history: Arc<DashMap<String, UserHistory>>,
72    ip: String,
73) -> tokio::io::Result<()> {
74    let mut head = [0u8; 8];
75    let n = timeout(Duration::from_secs(2), socket.peek(&mut head[..]))
76        .await
77        .map_err(|_| std::io::Error::new(std::io::ErrorKind::TimedOut, "peek timeout"))??;
78
79    match identify(&head[..n]) {
80        Protocol::Http => {
81            if let Some(web_target) = &cfg.web {
82                tunnel(socket, web_target.clone()).await
83            } else {
84                if cfg.debug {
85                    println!("HTTP request received but no web target configured. Closing.");
86                }
87                Ok(())
88            }
89        }
90        Protocol::Binary => {
91            if let Ok(Ok(mut target)) =
92                timeout(Duration::from_secs(1), TcpStream::connect(&cfg.mc)).await
93            {
94                cfg.is_waking.store(false, Ordering::SeqCst);
95                tokio::io::copy_bidirectional(&mut socket, &mut target).await?;
96                return Ok(());
97            }
98
99            if cfg.on_wakeup.is_some() {
100                let state = mc::inspect_handshake(&socket).await;
101                mc::handler::McHandler::send_fallback(&mut socket, state, cfg, history, ip).await
102            } else {
103                if cfg.debug {
104                    println!(
105                        "Connection to {} failed and no on-wakeup command set. Closing.",
106                        cfg.mc
107                    );
108                }
109                Ok(())
110            }
111        }
112    }
113}