initial commit

This commit is contained in:
2025-06-26 23:29:17 +08:00
commit 6a463cb1d7
4 changed files with 504 additions and 0 deletions

114
src/main.rs Normal file
View File

@ -0,0 +1,114 @@
use clap::Parser;
use std::io;
use std::net::{TcpStream, ToSocketAddrs, UdpSocket};
use std::str::FromStr;
use std::thread;
use std::time::Duration;
#[derive(Debug, Clone)]
enum Protocol {
Tcp,
Udp,
}
#[derive(Debug, Clone)]
struct KnockTarget {
port: u16,
protocol: Protocol,
}
impl FromStr for KnockTarget {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let parts: Vec<&str> = s.split('/').collect();
if parts.len() != 2 {
return Err("Invalid format. Expected PORT/PROTOCOL".to_string());
}
let port = parts[0]
.parse::<u16>()
.map_err(|e| format!("Invalid port number: {}", e))?;
let protocol = match parts[1].to_lowercase().as_str() {
"tcp" => Protocol::Tcp,
"udp" => Protocol::Udp,
_ => return Err("Invalid protocol. Use 'tcp' or 'udp'".to_string()),
};
Ok(KnockTarget { port, protocol })
}
}
#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
struct Cli {
/// The target to connect to after knocking, e.g., 127.0.0.1:22
#[arg(required = true)]
target_address: String,
/// Port knocking sequence, e.g., --knock 1000/tcp --knock 2000/udp
#[arg(long, short, value_parser = clap::value_parser!(KnockTarget), required = true, env = "PORT_KNOCKING_SEQ", value_delimiter = ':', num_args = 1..)]
knock: Vec<KnockTarget>,
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let cli = Cli::parse();
env_logger::builder().target(env_logger::Target::Stderr).init();
let (target_host, _target_port) = {
let parts: Vec<&str> = cli.target_address.split(':').collect();
if parts.len() != 2 {
return Err("Invalid target address format. Expected HOST:PORT".into());
}
(parts[0], parts[1].parse::<u16>()?)
};
log::info!("Starting port knocking sequence...");
for knock_target in &cli.knock {
let addr = format!("{}:{}", target_host, knock_target.port)
.to_socket_addrs()?
.next()
.ok_or("Could not resolve host")?;
match knock_target.protocol {
Protocol::Tcp => {
log::debug!("Knocking on TCP port {}", knock_target.port);
let timeout = Duration::from_secs(1);
if TcpStream::connect_timeout(&addr, timeout).is_err() {
// We expect errors here, like connection refused or timeout.
// The goal is just to hit the port.
}
}
Protocol::Udp => {
log::debug!("Knocking on UDP port {}", knock_target.port);
let socket = UdpSocket::bind("0.0.0.0:0")?;
socket.send_to(&[], addr)?;
}
}
}
log::debug!("Knocking sequence finished. Connecting to {}...", cli.target_address);
let mut stream = TcpStream::connect(&cli.target_address)?;
let mut stream_clone = stream.try_clone()?;
log::info!("Connection established. Proxying data...");
let stdin_thread = thread::spawn(move || {
let mut stdin = io::stdin();
io::copy(&mut stdin, &mut stream_clone).ok();
});
let stdout_thread = thread::spawn(move || {
let mut stdout = io::stdout();
io::copy(&mut stream, &mut stdout).ok();
});
stdin_thread.join().unwrap();
stdout_thread.join().unwrap();
Ok(())
}