Compare commits
2 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
18f5a3338e | |
|
|
0c4327dd71 |
|
|
@ -1,7 +1,10 @@
|
|||
use crate::dhcp_snooper::message_matches_bootp_client;
|
||||
use crate::proxy::flows::{FlowDirection, FlowMatch};
|
||||
use crate::proxy::udp_packet_helper::UdpPacketHelper;
|
||||
use crate::proxy::{Direction, PolicyDecision, Proxy};
|
||||
use anyhow::{Context, Result};
|
||||
use dhcproto::Decodable;
|
||||
use dhcproto::v4::Opcode;
|
||||
use smoltcp::phy::ChecksumCapabilities;
|
||||
use smoltcp::wire::{EthernetFrame, EthernetProtocol, Ipv4Packet, Ipv4Repr, UdpPacket};
|
||||
|
||||
|
|
@ -141,8 +144,75 @@ impl Proxy<'_> {
|
|||
return false;
|
||||
}
|
||||
|
||||
UdpPacket::new_checked(ipv4_pkt.payload())
|
||||
.map(|udp_pkt| udp_pkt.is_dhcp_response())
|
||||
.unwrap_or(false)
|
||||
let Ok(udp_pkt) = UdpPacket::new_checked(ipv4_pkt.payload()) else {
|
||||
return false;
|
||||
};
|
||||
|
||||
// Require the standard DHCP server and client ports
|
||||
if !udp_pkt.is_dhcp_response() {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Require the BOOTP client hardware address to match this VM
|
||||
// (symmetric with is_allowed_dhcp_request / #191 on the VM→host path)
|
||||
let mut decoder = dhcproto::v4::Decoder::new(udp_pkt.payload());
|
||||
let Ok(message) = dhcproto::v4::Message::decode(&mut decoder) else {
|
||||
return false;
|
||||
};
|
||||
|
||||
message_matches_bootp_client(&message, Opcode::BootReply, self.vm_mac_address.0)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::dhcp_snooper::message_matches_bootp_client;
|
||||
use dhcproto::Decodable;
|
||||
use dhcproto::v4::{DhcpOption, Message, MessageType, Opcode};
|
||||
use dhcproto::{Encodable, Encoder};
|
||||
use smoltcp::wire::Ipv4Address;
|
||||
|
||||
const VM_MAC: [u8; 6] = [0x02, 0x00, 0x00, 0x00, 0x00, 0x01];
|
||||
const OTHER_MAC: [u8; 6] = [0x02, 0x00, 0x00, 0x00, 0x00, 0x02];
|
||||
|
||||
#[test]
|
||||
fn dhcp_boot_reply_chaddr_must_match_vm() {
|
||||
let own = encode_boot_reply(VM_MAC);
|
||||
let foreign = encode_boot_reply(OTHER_MAC);
|
||||
|
||||
let mut dec = dhcproto::v4::Decoder::new(&own);
|
||||
let own_msg = Message::decode(&mut dec).unwrap();
|
||||
let mut dec = dhcproto::v4::Decoder::new(&foreign);
|
||||
let foreign_msg = Message::decode(&mut dec).unwrap();
|
||||
|
||||
assert!(message_matches_bootp_client(
|
||||
&own_msg,
|
||||
Opcode::BootReply,
|
||||
VM_MAC
|
||||
));
|
||||
assert!(!message_matches_bootp_client(
|
||||
&foreign_msg,
|
||||
Opcode::BootReply,
|
||||
VM_MAC
|
||||
));
|
||||
}
|
||||
|
||||
fn encode_boot_reply(chaddr: [u8; 6]) -> Vec<u8> {
|
||||
let mut message = Message::new(
|
||||
Ipv4Address::UNSPECIFIED,
|
||||
Ipv4Address::new(192, 168, 64, 2),
|
||||
Ipv4Address::UNSPECIFIED,
|
||||
Ipv4Address::UNSPECIFIED,
|
||||
&chaddr,
|
||||
);
|
||||
message.set_opcode(Opcode::BootReply);
|
||||
message
|
||||
.opts_mut()
|
||||
.insert(DhcpOption::MessageType(MessageType::Ack));
|
||||
message.opts_mut().insert(DhcpOption::AddressLeaseTime(600));
|
||||
|
||||
let mut encoded = Vec::new();
|
||||
message.encode(&mut Encoder::new(&mut encoded)).unwrap();
|
||||
encoded
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -108,6 +108,11 @@ impl Proxy<'_> {
|
|||
loop {
|
||||
let (vm_readable, host_readable, interrupt) = self.poller.wait()?;
|
||||
|
||||
// kqueue does not report peer disconnects for Unix datagram sockets.
|
||||
if !self.vm.is_connected()? {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Update coarse time for DHCP snooping and flows
|
||||
coarsetime::Instant::update();
|
||||
|
||||
|
|
|
|||
68
lib/vm.rs
68
lib/vm.rs
|
|
@ -26,6 +26,14 @@ impl VM {
|
|||
pub fn read(&self, buf: &mut [u8]) -> std::io::Result<usize> {
|
||||
self.sock.recv(buf)
|
||||
}
|
||||
|
||||
pub fn is_connected(&self) -> io::Result<bool> {
|
||||
match self.sock.peer_addr() {
|
||||
Ok(_) => Ok(true),
|
||||
Err(error) if error.kind() == io::ErrorKind::NotConnected => Ok(false),
|
||||
Err(error) => Err(error),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn duplicate_vm_fd(vm_fd: RawFd) -> Result<RawFd> {
|
||||
|
|
@ -115,10 +123,12 @@ impl AsRawFd for VM {
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::VM;
|
||||
use polling::{Event, Events, PollMode, Poller};
|
||||
use std::fs::File;
|
||||
use std::net::UdpSocket;
|
||||
use std::os::fd::AsRawFd;
|
||||
use std::os::unix::net::{UnixDatagram, UnixStream};
|
||||
use std::time::Duration;
|
||||
|
||||
#[test]
|
||||
fn test_new_rejects_negative_fd() {
|
||||
|
|
@ -187,4 +197,62 @@ mod tests {
|
|||
|
||||
assert!(socket_fd_is_open);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_connected_socket_has_peer() {
|
||||
let (socket, _peer) = UnixDatagram::pair().unwrap();
|
||||
let vm = VM::new(socket.as_raw_fd()).unwrap();
|
||||
|
||||
assert!(vm.is_connected().unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_disconnected_peer_is_detected_without_kqueue_event() {
|
||||
let (socket, peer) = UnixDatagram::pair().unwrap();
|
||||
let vm = VM::new(socket.as_raw_fd()).unwrap();
|
||||
let poller = Poller::new().unwrap();
|
||||
let mut events = Events::new();
|
||||
|
||||
unsafe {
|
||||
poller
|
||||
.add_with_mode(vm.as_raw_fd(), Event::readable(0), PollMode::Edge)
|
||||
.unwrap();
|
||||
}
|
||||
drop(peer);
|
||||
|
||||
poller
|
||||
.wait(&mut events, Some(Duration::from_millis(20)))
|
||||
.unwrap();
|
||||
|
||||
assert!(events.is_empty());
|
||||
assert!(!vm.is_connected().unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_disconnected_peer_is_detected_when_another_socket_wakes_kqueue() {
|
||||
let (socket, peer) = UnixDatagram::pair().unwrap();
|
||||
let (host, host_peer) = UnixDatagram::pair().unwrap();
|
||||
let vm = VM::new(socket.as_raw_fd()).unwrap();
|
||||
let poller = Poller::new().unwrap();
|
||||
let mut events = Events::new();
|
||||
|
||||
unsafe {
|
||||
poller
|
||||
.add_with_mode(vm.as_raw_fd(), Event::readable(0), PollMode::Edge)
|
||||
.unwrap();
|
||||
poller
|
||||
.add_with_mode(host.as_raw_fd(), Event::readable(1), PollMode::Edge)
|
||||
.unwrap();
|
||||
}
|
||||
drop(peer);
|
||||
host_peer.send(&[1]).unwrap();
|
||||
|
||||
poller
|
||||
.wait(&mut events, Some(Duration::from_millis(20)))
|
||||
.unwrap();
|
||||
|
||||
assert!(events.iter().any(|event| event.key == 1));
|
||||
assert!(!events.iter().any(|event| event.key == 0));
|
||||
assert!(!vm.is_connected().unwrap());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue