132 lines
3.9 KiB
Rust
132 lines
3.9 KiB
Rust
use super::{
|
|
Conntrack, ConntrackResult, Direction, Flow, FlowKey, FlowState, Initiator, PendingFlow,
|
|
initiator, oriented_transport_key,
|
|
};
|
|
use coarsetime::{Duration, Instant};
|
|
use smoltcp::wire::{Ipv4Packet, UdpPacket};
|
|
|
|
const UNREPLIED_TIMEOUT: Duration = Duration::from_secs(30);
|
|
const REPLIED_TIMEOUT: Duration = Duration::from_secs(3 * 60);
|
|
|
|
#[derive(Clone, Copy, Debug)]
|
|
pub(super) struct State {
|
|
replied: bool,
|
|
}
|
|
|
|
impl State {
|
|
pub(super) fn timeout(self) -> Duration {
|
|
if self.replied {
|
|
REPLIED_TIMEOUT
|
|
} else {
|
|
UNREPLIED_TIMEOUT
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Conntrack {
|
|
pub(super) fn inspect_udp(
|
|
&mut self,
|
|
packet: &Ipv4Packet<&[u8]>,
|
|
direction: Direction,
|
|
now: Instant,
|
|
) -> ConntrackResult {
|
|
let Ok(udp) = UdpPacket::new_checked(packet.payload()) else {
|
|
return ConntrackResult::Denied;
|
|
};
|
|
if udp.src_port() == 0 || udp.dst_port() == 0 {
|
|
return ConntrackResult::Denied;
|
|
}
|
|
|
|
let key = oriented_transport_key(
|
|
packet,
|
|
direction,
|
|
|host_addr, host_port, vm_addr, vm_port| FlowKey::Udp {
|
|
host_addr,
|
|
host_port,
|
|
vm_addr,
|
|
vm_port,
|
|
},
|
|
udp.src_port(),
|
|
udp.dst_port(),
|
|
);
|
|
|
|
if let Some(flow) = self.flows.get_mut(&key) {
|
|
let FlowState::Udp(state) = &mut flow.state else {
|
|
return ConntrackResult::Denied;
|
|
};
|
|
|
|
let is_reply = matches!(
|
|
(flow.initiator, direction),
|
|
(Initiator::Host, Direction::FromVm) | (Initiator::Vm, Direction::FromHost)
|
|
);
|
|
state.replied |= is_reply;
|
|
flow.last_seen = now;
|
|
return ConntrackResult::Allowed;
|
|
}
|
|
|
|
ConntrackResult::New(PendingFlow {
|
|
key,
|
|
flow: Flow {
|
|
initiator: initiator(direction),
|
|
state: FlowState::Udp(State { replied: false }),
|
|
last_seen: now,
|
|
},
|
|
})
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::super::test_support::{HOST, VM, inspect_from_host, inspect_from_vm, udp_packet};
|
|
use super::super::{Conntrack, ConntrackResult};
|
|
|
|
#[test]
|
|
fn udp_reply_requires_an_exact_host_request() {
|
|
let mut tracker = Conntrack::new();
|
|
|
|
let vm_datagram = udp_packet(VM, 5353, HOST, 50000);
|
|
assert!(matches!(
|
|
inspect_from_vm(&mut tracker, &vm_datagram),
|
|
ConntrackResult::New(_)
|
|
));
|
|
|
|
let host_datagram = udp_packet(HOST, 50000, VM, 5353);
|
|
let ConntrackResult::New(pending) = inspect_from_host(&mut tracker, &host_datagram) else {
|
|
panic!("expected a new flow");
|
|
};
|
|
assert!(tracker.commit(pending));
|
|
assert!(matches!(
|
|
inspect_from_vm(&mut tracker, &vm_datagram),
|
|
ConntrackResult::Allowed
|
|
));
|
|
|
|
let wrong_vm_datagram = udp_packet(VM, 5353, HOST, 50001);
|
|
assert!(matches!(
|
|
inspect_from_vm(&mut tracker, &wrong_vm_datagram),
|
|
ConntrackResult::New(_)
|
|
));
|
|
}
|
|
|
|
#[test]
|
|
fn explicitly_allowed_vm_udp_gets_only_its_reply() {
|
|
let mut tracker = Conntrack::new();
|
|
let vm_dns = udp_packet(VM, 53000, HOST, 53);
|
|
let host_dns = udp_packet(HOST, 53, VM, 53000);
|
|
|
|
let ConntrackResult::New(pending) = inspect_from_vm(&mut tracker, &vm_dns) else {
|
|
panic!("expected a new flow");
|
|
};
|
|
assert!(tracker.commit(pending));
|
|
assert!(matches!(
|
|
inspect_from_host(&mut tracker, &host_dns),
|
|
ConntrackResult::Allowed
|
|
));
|
|
|
|
let unsolicited_host_udp = udp_packet(HOST, 53, VM, 53001);
|
|
assert!(matches!(
|
|
inspect_from_host(&mut tracker, &unsolicited_host_udp),
|
|
ConntrackResult::New(_)
|
|
));
|
|
}
|
|
}
|