闽公网安备 35020302035485号
3.计算时延
sudo chmod 666 /dev/bpf*首先看看程序运行的效果:

use std::net::{Ipv4Addr,Ipv6Addr};
use std::ops::Sub;
use std::time::{Duration, UNIX_EPOCH};
use chrono::{DateTime, Local};
use macaddr::MacAddr;
use pcap;
use pdu::*;
use libc;
fn main() {
// 这个用来记录flow已经它被捕获的时间
let mut map = std::collections::HashMap::new();
// 在Mac上,使用en1网卡
let mut cap = pcap::Capture::from_device("en1")
.unwrap()
.immediate_mode(true)
.open()
.unwrap();
// 你可以设置filter,这里我们简化不进行设置了
// cap.filter("host 127.0.0.1", true).unwrap();
while let Ok(packet) = cap.next_packet() {
// 得到捕获的包信息
......
}
}
目前我们只能得到捕获的包信息,包括 pcap 增加的头信息(捕获时间、包长度等)和包的数据。我们需要解析包的数据,得到 TCP 包,然后解析 TCP 选项中的时间戳。目前 pcap 不能帮助我们了。我们在那个 while 循环中一步一步补充省略的代码: let ethernet = EthernetPdu::new(&packet.data).unwrap();
// 堆代码 duidaima.com
// 实现代码,输出源和目的MAC地址,转换成MacAddr类型
let _src_mac = MacAddr::from(ethernet.source_address());
let _dst_mac = MacAddr::from(ethernet.destination_address());
// println!("ethernet: src_mac={}, dst_mac={}", src_mac, dst_mac);
let ei = ethernet.inner();
let (src_ip,dst_ip, tcp) = match ei {
Ok(Ethernet::Ipv4(ref ip)) => {
let src_ip = Ipv4Addr::from(ip.source_address()).to_string();
let dst_ip = Ipv4Addr::from(ip.destination_address()).to_string();
let tcp = match ip.inner() {
Ok(Ipv4::Tcp(tcp)) => Some(tcp),
_ => None
};
(src_ip,dst_ip,tcp)
}
Ok(Ethernet::Ipv6(ref ip)) => {
let src_ip = Ipv6Addr::from(ip.source_address()).to_string();
let dst_ip = Ipv6Addr::from(ip.destination_address()).to_string();
let tcp = match ip.inner() {
Ok(Ipv6::Tcp(tcp)) => Some(tcp),
_ => None
};
(src_ip,dst_ip,tcp)
}
_ => (String::new(),String::new(),None)
};
......
首先解析出ethernet层,和 gopacket 调用方法不同,但是一样很简洁。ethernet中包含源目的 Mac 地址,如果你需要,你可以调用相应的方法获取它们。本程序不需要这两个信息,忽略即可。 let ei = ethernet.inner();
let (src_ip,dst_ip, tcp) = match ei {
Ok(Ethernet::Ipv4(ref ip)) => {
let src_ip = Ipv4Addr::from(ip.source_address()).to_string();
let dst_ip = Ipv4Addr::from(ip.destination_address()).to_string();
let tcp = match ip.inner() {
Ok(Ipv4::Tcp(tcp)) => Some(tcp),
_ => None
};
(src_ip,dst_ip,tcp)
}
Ok(Ethernet::Ipv6(ref ip)) => {
let src_ip = Ipv6Addr::from(ip.source_address()).to_string();
let dst_ip = Ipv6Addr::from(ip.destination_address()).to_string();
let tcp = match ip.inner() {
Ok(Ipv6::Tcp(tcp)) => Some(tcp),
_ => None
};
(src_ip,dst_ip,tcp)
}
_ => (String::new(),String::new(),None)
};
if tcp.is_none() {
continue;
}
let tcp = tcp.unwrap();
调用inner方法就可以得到IP层的信息,我们处理 ipv4 和 ipv6 两种情况,分别获取源目的 IP 地址和 TCP 层这三个数据。因为一开始我们没有设置 filter,所以这里捕获的包很多,比如 UDP 的包、ARP 的包,我们在这里检查包是否是 TCP 包,如果不是,我们忽略这个包。当然最好是一开始就设置 filter,性能会更好。 let ts = tcp.options().find_map(|option| {
match option {
TcpOption::Timestamp{val,ecr} => {
Some((val, ecr))
}
_ => None
}
});
if ts.is_none() {
continue;
}
if ts.unwrap().1 == 0 && !tcp.syn(){
continue;
}
pdu库的好处是方便解析 TCP 以及它的选项。TCP 的选项可能有好几个,我们只 match 时间戳的那个,得到时间戳的值和 echo reply 的值。接下来我们处理数据。首先根据五元组和tval为 key,将这个 flow 的信息存储到 map 中: let key = format!("{}:{}->{}:{}-{}", src_ip, tcp.source_port(),dst_ip,tcp.destination_port(),ts.unwrap().0);
if !map.contains_key(key.as_str()) {
map.insert(key, packet.header.ts);
}
然后我们找反向的 key,如果存在,就说明有去向,当前处理的是回向,我们计算两个捕获的值的差,就是时延: let reverse_key = format!("{}:{}->{}:{}-{}", dst_ip, tcp.destination_port(),src_ip,tcp.source_port(),ts.unwrap().1);
if map.contains_key(reverse_key.as_str()) {
map.get(reverse_key.as_str()).map(|ts| {
let rtt = timeval_diff_str(ts,&packet.header.ts);
println!("{} {} {}:{}->{}:{}", timeval_to_current_time_str(&packet.header.ts), rtt,dst_ip, tcp.destination_port(),src_ip,tcp.source_port());
});
}
当然为了避免map中的数据越积越多,我们可以定期清理一下,这里我们根据 map 中的元素的数量决定要不要清理: if map.len() > 10_000 {
map.retain(|_,v| {
let now = std::time::SystemTime::now();
let duration = now.duration_since(UNIX_EPOCH).unwrap();
let ts = Duration::new(v.tv_sec as u64, v.tv_usec as u32 * 1000);
duration.sub(ts).as_secs() < 60
});
}
然后补充两个计算时间的辅助程序,这就是这个程序的全部代码了:fn timeval_to_current_time_str(tv: &libc::timeval) -> String {
let secs = tv.tv_sec as u64;
let nsecs = (tv.tv_usec as u32 * 1000) as u64;
let duration = UNIX_EPOCH + std::time::Duration::new(secs, nsecs as u32);
let datetime = DateTime::<Local>::from(duration);
datetime.format("%H:%M:%S").to_string()
}
fn timeval_diff_str(start: &libc::timeval, end: &libc::timeval) -> String {
let secs = end.tv_sec as i64 - start.tv_sec as i64;
let usecs = end.tv_usec as i64 - start.tv_usec as i64;
let (secs, usecs) = if usecs < 0 {
(secs - 1, usecs + 1_000_000)
} else {
(secs, usecs)
};
format_duration(secs, usecs as u32)
}
fn format_duration(secs: i64, usecs: u32) -> String {
let duration = secs * 1_000_000 + usecs as i64;
match duration {
0..=999_999 => format!("{:.3}ms", duration as f64 / 1_000.0),
_ => format!("{:.6}s", duration as f64 / 1_000_000.0),
}
}
你对 Rust 实现的 pping 有什么看法,欢迎在评论区留下你宝贵的意见。