突然ですが皆様は「Rust」使ってますか?
私は普段Pythonをメインとしているのですが、高速・安定なパケットキャプチャが欲しくなり
ついでにWindows環境にてRustで作ってみようと思いました
年末年始の時間で概ね動くようになったので、ここに記録します
Cargo.toml 抜粋
dependenciesには下記3つのパッケージを加えてください(2024/08/25現在のVersion)
[dependencies]
netdev = "0.30.0"
pnet = "0.35.0"
pnet_datalink = "0.35.0"
netdev(旧 default-net) は普段メインで利用しているdefault interface情報を取得してくれる便利な奴です
(これが無いとHyper-VとかVMwareで複数のinterfaceが付いている時は取得するinterfaceを手動指定する必要があった...)
libpnet(pnet / pnet_datalink)は interfaceからパケットキャプチャするためのパッケージです
なお、Windowsでは別途Npcapが必要となります(後述)
Rustに実装してみた
実際にコードを書いてみるとこんな感じになります
ここではIPv4 + TCPセグメント のデータだけ欲しいので大分端折ってます
IPv6やICMP, UDPも必要といった場合は下記libpnetのサンプルコードを元に拡張して下さい
https://github.com/libpnet/libpnet/blob/master/examples/packetdump.rs
extern crate netdev;
extern crate pnet;
extern crate pnet_datalink;
use std::{net::IpAddr, thread};
use pnet::packet::{
ethernet::{EtherTypes, EthernetPacket},
ip::{IpNextHeaderProtocol, IpNextHeaderProtocols},
ipv4::Ipv4Packet,
tcp, Packet,
};
use pnet_datalink::{Channel::Ethernet, DataLinkReceiver, DataLinkSender, NetworkInterface};
pub fn ignition() {
let mut _best_device_name: String = "\\Device\\NPF_".to_string();
match netdev::get_default_interface() {
Ok(_best_interface) => {
println!("Best Interface Name: {}", _best_interface.name);
_best_device_name += &_best_interface.name;
println!(
"Friendly Name: {:?}",
_best_interface.friendly_name.unwrap()
);
}
Err(ex) => {
panic!("Error while find the best interface. {}", ex);
}
}
// すべてのネットワークインターフェースを取得
let _all_interfaces: Vec<NetworkInterface> = pnet_datalink::interfaces();
let _default_interface: Option<NetworkInterface> = _all_interfaces
.iter()
.find(|iface| iface.is_loopback() == false && iface.name == _best_device_name)
.cloned();
if let Some(interface) = _default_interface {
println!("Found default interface with [{}].", interface.name);
let builder = thread::Builder::new().name("ro-indexer-packet-caputure".into());
let handle = builder
.spawn(move || {
let (_, mut rx): (Box<dyn DataLinkSender>, Box<dyn DataLinkReceiver>) =
match pnet_datalink::channel(&interface, Default::default()) {
Ok(Ethernet(tx, rx)) => (tx, rx),
Ok(_) => panic!("Unhandled channel type"),
Err(ex) => panic!("Unable to create channel: {}", ex),
};
loop {
match rx.next() {
Ok(_packet) => match EthernetPacket::new(_packet) {
Some(frame) => {
handle_ethernet_frame(&frame);
}
_ => {}
},
Err(ex) => panic!("Unable to recive frame: {}", ex),
}
}
})
.unwrap();
match handle.join() {
Ok(_) => {
println!("")
}
Err(ex) => {
println!("Error: {:?}", ex)
}
}
} else {
panic!("Error while finding the default interface.");
}
}
fn handle_ethernet_frame(ethernet: &EthernetPacket) {
match ethernet.get_ethertype() {
EtherTypes::Ipv4 => handle_ipv4_packet(ðernet),
_ => (),
}
}
fn handle_ipv4_packet(ethernet: &EthernetPacket) {
let ip_packet: Option<Ipv4Packet> = Ipv4Packet::new(ethernet.payload());
match ip_packet {
Some(ip_packet) => {
handle_transport_protocol(
IpAddr::V4(ip_packet.get_source()),
IpAddr::V4(ip_packet.get_destination()),
ip_packet.get_next_level_protocol(),
ip_packet.payload(),
);
}
_ => (),
}
}
fn handle_transport_protocol(
source: IpAddr,
destination: IpAddr,
protocol: IpNextHeaderProtocol,
packet: &[u8],
) {
match protocol {
IpNextHeaderProtocols::Tcp => {
handle_tcp_segment(source, destination, packet);
}
_ => (),
}
}
fn handle_tcp_segment(source: IpAddr, destination: IpAddr, packet: &[u8]) {
let segment: Option<tcp::TcpPacket> = tcp::TcpPacket::new(packet);
match segment {
Some(segment) => {
println!(
"{}:{} -> {}:{}",
source,
segment.get_source(),
destination,
segment.get_destination()
);
//ここでTCPパケットの送信元IPv4アドレス&ポート + 宛先IPv4アドレス&ポートが見える
}
_ => (),
}
}
あとはmain.rsを作成し、上記のpacket_capture.rsを読み込み実行すれば動く(はず)
mod packet_capture;
fn main() {
packet_capture::ignition();
}
コンパイルする際に下記のPowerShell実行すると良い
rustup default stable-x86_64-pc-windows-gnu
cargo clean
$env:RUSTFLAGS = "-L .\lib\npcap\x64"
cargo build --release
こんな感じで送信元IPv4アドレス&ポート + 宛先IPv4アドレス&ポートの出力が流れたら成功
ハマりポイント
-
Npcapは「Install Npcap WinPcap API-compatible Mode」にチェックを入れてインストールすること
-
libpnetの exsampleが間違ってる
pnet::datalinkが無い...って悩んでたらpnet_datalinkに改名されてた -
Rustの仕様がまだ分かってないのである!
これから精進していきます!
2024年になって再度触ってみたら動かなかったので、更新しました