mod icmp;
mod udp;
use std::net::Ipv4Addr;
use tokio::sync::mpsc;
use tokio_util::sync::CancellationToken;
use arcbox_fakeip::dns_log::DnsResolutionLog;
use arcbox_fakeip::proxy_detect::ProxyEnvironment;
use arcbox_packet::ethernet::ETH_HEADER_LEN;
use icmp::IcmpProxy;
use udp::UdpProxy;
pub struct HostEgress {
icmp: IcmpProxy,
udp: UdpProxy,
inbound: super::inbound_relay::InboundRelay,
reply_tx: mpsc::Sender<Vec<u8>>,
cancel: CancellationToken,
}
impl HostEgress {
#[must_use]
pub fn new(
gateway_ip: Ipv4Addr,
gateway_mac: [u8; 6],
guest_ip: Ipv4Addr,
reply_tx: mpsc::Sender<Vec<u8>>,
cancel: CancellationToken,
mtu: usize,
) -> Self {
let inbound = super::inbound_relay::InboundRelay::new(
reply_tx.clone(),
gateway_mac,
gateway_ip,
guest_ip,
mtu,
);
Self {
icmp: IcmpProxy::new(reply_tx.clone(), gateway_mac),
udp: UdpProxy::new(reply_tx.clone(), gateway_mac, gateway_ip, mtu),
inbound,
reply_tx,
cancel,
}
}
#[must_use]
pub fn reply_sender(&self) -> mpsc::Sender<Vec<u8>> {
self.reply_tx.clone()
}
pub fn set_proxy_awareness(&mut self, dns_log: DnsResolutionLog, proxy_env: ProxyEnvironment) {
self.udp.dns_log = Some(dns_log);
self.udp.proxy_env = Some(proxy_env);
}
pub fn handle_outbound(&mut self, frame: &[u8], guest_mac: [u8; 6]) {
if frame.len() < ETH_HEADER_LEN + 20 {
return;
}
if self.inbound.try_handle_reply(frame, guest_mac) {
return;
}
let protocol = frame[ETH_HEADER_LEN + 9];
let cancel = self.cancel.clone();
match protocol {
1 => self.icmp.proxy_icmp(frame, guest_mac, cancel),
17 => self.udp.proxy_udp(frame, guest_mac, cancel),
_ => {
tracing::trace!("Host egress: dropping protocol {}", protocol);
}
}
}
pub fn handle_inbound_command(
&mut self,
cmd: super::inbound_relay::InboundCommand,
guest_mac: [u8; 6],
) {
use super::inbound_relay::InboundCommand;
if let InboundCommand::UdpReceived {
host_port,
data,
reply_tx,
..
} = cmd
{
self.inbound
.inject_udp(host_port, &data, reply_tx, guest_mac);
}
}
pub fn maintenance(&mut self) {
self.udp.cleanup_stale_flows();
self.inbound.cleanup();
}
pub fn expire_flow(&mut self, _addr: std::net::SocketAddr) {
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_host_egress_creation() {
let (tx, _rx) = mpsc::channel(16);
let gw_ip = Ipv4Addr::new(192, 168, 64, 1);
let gw_mac = [0x02, 0xAB, 0xCD, 0x00, 0x00, 0x01];
let guest_ip = Ipv4Addr::new(192, 168, 64, 2);
let _egress = HostEgress::new(gw_ip, gw_mac, guest_ip, tx, CancellationToken::new(), 1500);
}
#[test]
fn test_host_egress_drops_unknown_protocol() {
let (tx, _rx) = mpsc::channel(16);
let gw_ip = Ipv4Addr::new(192, 168, 64, 1);
let gw_mac = [0x02, 0xAB, 0xCD, 0x00, 0x00, 0x01];
let guest_ip = Ipv4Addr::new(192, 168, 64, 2);
let mut egress =
HostEgress::new(gw_ip, gw_mac, guest_ip, tx, CancellationToken::new(), 1500);
let mut frame = vec![0u8; ETH_HEADER_LEN + 20];
frame[12..14].copy_from_slice(&0x0800u16.to_be_bytes());
frame[ETH_HEADER_LEN] = 0x45; frame[ETH_HEADER_LEN + 9] = 50;
let guest_mac = [0x02, 0x00, 0x00, 0x00, 0x00, 0x99];
egress.handle_outbound(&frame, guest_mac);
}
#[test]
fn test_host_egress_ignores_short_frames() {
let (tx, _rx) = mpsc::channel(16);
let gw_ip = Ipv4Addr::new(192, 168, 64, 1);
let gw_mac = [0x02, 0xAB, 0xCD, 0x00, 0x00, 0x01];
let guest_ip = Ipv4Addr::new(192, 168, 64, 2);
let mut egress =
HostEgress::new(gw_ip, gw_mac, guest_ip, tx, CancellationToken::new(), 1500);
let guest_mac = [0x02, 0x00, 0x00, 0x00, 0x00, 0x99];
egress.handle_outbound(&[0u8; 10], guest_mac);
}
#[tokio::test]
async fn test_reply_channel_flow() {
let (tx, mut rx) = mpsc::channel(16);
let gw_ip = Ipv4Addr::new(192, 168, 64, 1);
let gw_mac = [0x02, 0xAB, 0xCD, 0x00, 0x00, 0x01];
let test_frame = vec![0xDE, 0xAD, 0xBE, 0xEF];
tx.send(test_frame.clone()).await.unwrap();
let received = rx.recv().await.unwrap();
assert_eq!(received, test_frame);
let guest_ip = Ipv4Addr::new(192, 168, 64, 2);
let _egress = HostEgress::new(gw_ip, gw_mac, guest_ip, tx, CancellationToken::new(), 1500);
}
}