Rust 自练手小项目 - IP端口嗅探器
序:我的第一个不知道咋写的博客
小白看着 rust
挺有趣,小白学习学习,小白练练手,小白第一次写博客,小白很随意
1. 什么是 IP 端口嗅探器
IP
端口嗅探器,通常也被简称为嗅探器或协议分析器,是一种能够监视和分析网络数据运行的软件或硬件设备,特别关注于IP
端口的数据传输。
2. 本小项目要实现的功能
通过输入命令,来实现嗅探某个IP
的主机的端口是否开放。
3. 实现过程与方法
3.1 rust 获取环境参数
let args: Vec<String> = env::args().collect();
for arg in args {
println!("arg = {}", arg);
}
cargo run -- -h // 控制台输入
arg = target\debug\ip_sniffer.exe
arg = -h
输出两个参数,一个是程序本身,一个是第一个参数。小项目通过输入命令参数,来实现IP
端口嗅探的功能。
cargo run -- -j 1000 127.0.0.1 // 示例命令
上述命令包含 3 个参数,-j表示需要启动多线程的flag
,1000为线程数,127.0.0.1为 IP 地址。因此可以设计参数的结构体如下:
struct Arguments {
flag: String,
threads: u16,
ipaddr: IpAddr,
}
我们在输入命令时,将数据放入到结构体中,因此我们为结构体实现一个关联函数。
impl Arguments {
fn new(args: &Vec<String>) -> Result<Arguments, &'static str> {
}
}
传入的参数是一个字符串vec to ref
,返回一个Result
枚举,Ok
的话将Argument
放入,否则将错误消息&str
放入 Err
中 。
3.2 让我们来实现这个关联函数吧
if args.len() < 2 {
return Err("not enough arguments");
} else if args.len() > 4 {
return Err("too many arguments");
}
我们的 IP 端口嗅探器只允许最多输入4
个参数,最少输入2
个参数,不在这个范围内直接返回错误消息。
let f = args[1].clone();
if let Ok(ipaddr) = IpAddr::from_str(&f) {
return Ok(Arguments {
flag: String::from(""),
ipaddr,
threads: 4,
});
} else {
// other logic
}
如果第一个参数就是IP
,我们为其生成一个默认的命令格式的Arguments
,类似于如下命令
cargo run -- 4 127.0.0.1
如果第一个参数不是IP
,我们来检查是不是-h,或者-help,如果是我们则给出一些提示,当然仅此而已。当然我们不允许此时有更多的参数了。
let flag = args[1].clone();
if flag.contains("-h") || flag.contains("-help") && args.len() == 2 {
println!(
"Usage: -j to select how many threads you want
\r\n -h or -help to show this help message"
);
return Err("help");
} else if flag.contains("-h") || flag.contains("-help") {
return Err("too many arguments");
} else if /*other condition*/ {
// other logic
}
接下来我们看其它条件
else if flag.contains("-j") {
let ipaddr = match IpAddr::from_str(&args[3]) {
Ok(s) => s,
Err(_) => return Err("not a valid IPADDR: must be IPv4 or IPv6"),
};
let threads = match args[2].parse::<u16>() {
Ok(s) => s,
Err(_) => return Err("failed to parse thread number"),
};
return Ok(Arguments {
threads,
flag,
ipaddr,
});
} else {
return Err("invalid syntsx");
}
如果flag
是-j,说明我们接下来的一个参数是要启动的线程数量,最后一个参数仍然是IP
地址。同理,我们将获取到的线程数和IP
地址放入Arguments
中。
3.3 接下来我们看main函数的实现
/// ip_sniffer/main
let args: Vec<String> = env::args().collect();
let program = args[0].clone();
let arguments = Arguments::new(&args).unwrap_or_else(|err| {
if err.contains("help") {
process::exit(0);
} else {
eprintln!("{} problem parsing arguments: {}", program, err);
process::exit(0);
}
});
我们获取环境参数args
,第0
个参数就是程序本身,通过命令输入的参数来创建Arguments
,
let num_threads = arguments.threads;
let ip_addr = arguments.ipaddr;
let (tx, rx) = channel();
for i in 0..num_threads {
let tx = tx.clone();
thread::spawn(move || {
scan(tx, i, ip_addr, num_threads);
});
}
我们得到线程数量num_threads
和IP
地址ip_addr
;然后创建channel
返回发送端和接收端,启动线程向 ip:port
发送数据。
3.4 让我们看看scan的实现
const MAX = 65535;
fn scan(tx: Sender<u16>, start_port: u16, addr: IpAddr, num_threads: u16) {
let mut port: u16 = start_port + 1;
loop {
match TcpStream::connect((addr, port)) {
Ok(_) => {
print!(".");
io::stdout().flush().unwrap();
tx.send(port).unwrap();
},
Err(_) => {},
};
if (MAX - port) <= num_threads {
break;
}
port += num_threads;
}
}
端口号从1
开始,我们执行loop
循环,向ip:port
发送TCP
请求,如果请求成功,将缓冲区数据写出到目标,tx
并将此port
通讯给rx
。
3.5 接下来我们返回到main函数中看看剩余的部分
let mut out = vec![];
drop(tx);
for p in rx {
out.push(p);
}
println!("");
out.sort();
for v in out {
println!("{} is open", v);
}
将rx
接收到的消息放入到vec
中,并打印出来。
4. 代码输出效果
其结果如下
....................
135 is open
445 is open
5040 is open
7680 is open
8588 is open
8590 is open
8680 is open
11066 is open
13013 is open
15796 is open
18444 is open
18460 is open
18464 is open
19156 is open
49664 is open
49665 is open
49666 is open
49667 is open
49668 is open
49693 is open