|
| 1 | +use crate::dhcp_snooper::message_matches_bootp_client; |
1 | 2 | use crate::proxy::flows::{FlowDirection, FlowMatch}; |
2 | 3 | use crate::proxy::udp_packet_helper::UdpPacketHelper; |
3 | 4 | use crate::proxy::{Direction, PolicyDecision, Proxy}; |
4 | 5 | use anyhow::{Context, Result}; |
| 6 | +use dhcproto::Decodable; |
| 7 | +use dhcproto::v4::Opcode; |
5 | 8 | use smoltcp::phy::ChecksumCapabilities; |
6 | 9 | use smoltcp::wire::{EthernetFrame, EthernetProtocol, Ipv4Packet, Ipv4Repr, UdpPacket}; |
7 | 10 |
|
@@ -141,8 +144,75 @@ impl Proxy<'_> { |
141 | 144 | return false; |
142 | 145 | } |
143 | 146 |
|
144 | | - UdpPacket::new_checked(ipv4_pkt.payload()) |
145 | | - .map(|udp_pkt| udp_pkt.is_dhcp_response()) |
146 | | - .unwrap_or(false) |
| 147 | + let Ok(udp_pkt) = UdpPacket::new_checked(ipv4_pkt.payload()) else { |
| 148 | + return false; |
| 149 | + }; |
| 150 | + |
| 151 | + // Require the standard DHCP server and client ports |
| 152 | + if !udp_pkt.is_dhcp_response() { |
| 153 | + return false; |
| 154 | + } |
| 155 | + |
| 156 | + // Require the BOOTP client hardware address to match this VM |
| 157 | + // (symmetric with is_allowed_dhcp_request / #191 on the VM→host path) |
| 158 | + let mut decoder = dhcproto::v4::Decoder::new(udp_pkt.payload()); |
| 159 | + let Ok(message) = dhcproto::v4::Message::decode(&mut decoder) else { |
| 160 | + return false; |
| 161 | + }; |
| 162 | + |
| 163 | + message_matches_bootp_client(&message, Opcode::BootReply, self.vm_mac_address.0) |
| 164 | + } |
| 165 | +} |
| 166 | + |
| 167 | +#[cfg(test)] |
| 168 | +mod tests { |
| 169 | + use crate::dhcp_snooper::message_matches_bootp_client; |
| 170 | + use dhcproto::Decodable; |
| 171 | + use dhcproto::v4::{DhcpOption, Message, MessageType, Opcode}; |
| 172 | + use dhcproto::{Encodable, Encoder}; |
| 173 | + use smoltcp::wire::Ipv4Address; |
| 174 | + |
| 175 | + const VM_MAC: [u8; 6] = [0x02, 0x00, 0x00, 0x00, 0x00, 0x01]; |
| 176 | + const OTHER_MAC: [u8; 6] = [0x02, 0x00, 0x00, 0x00, 0x00, 0x02]; |
| 177 | + |
| 178 | + #[test] |
| 179 | + fn dhcp_boot_reply_chaddr_must_match_vm() { |
| 180 | + let own = encode_boot_reply(VM_MAC); |
| 181 | + let foreign = encode_boot_reply(OTHER_MAC); |
| 182 | + |
| 183 | + let mut dec = dhcproto::v4::Decoder::new(&own); |
| 184 | + let own_msg = Message::decode(&mut dec).unwrap(); |
| 185 | + let mut dec = dhcproto::v4::Decoder::new(&foreign); |
| 186 | + let foreign_msg = Message::decode(&mut dec).unwrap(); |
| 187 | + |
| 188 | + assert!(message_matches_bootp_client( |
| 189 | + &own_msg, |
| 190 | + Opcode::BootReply, |
| 191 | + VM_MAC |
| 192 | + )); |
| 193 | + assert!(!message_matches_bootp_client( |
| 194 | + &foreign_msg, |
| 195 | + Opcode::BootReply, |
| 196 | + VM_MAC |
| 197 | + )); |
| 198 | + } |
| 199 | + |
| 200 | + fn encode_boot_reply(chaddr: [u8; 6]) -> Vec<u8> { |
| 201 | + let mut message = Message::new( |
| 202 | + Ipv4Address::UNSPECIFIED, |
| 203 | + Ipv4Address::new(192, 168, 64, 2), |
| 204 | + Ipv4Address::UNSPECIFIED, |
| 205 | + Ipv4Address::UNSPECIFIED, |
| 206 | + &chaddr, |
| 207 | + ); |
| 208 | + message.set_opcode(Opcode::BootReply); |
| 209 | + message |
| 210 | + .opts_mut() |
| 211 | + .insert(DhcpOption::MessageType(MessageType::Ack)); |
| 212 | + message.opts_mut().insert(DhcpOption::AddressLeaseTime(600)); |
| 213 | + |
| 214 | + let mut encoded = Vec::new(); |
| 215 | + message.encode(&mut Encoder::new(&mut encoded)).unwrap(); |
| 216 | + encoded |
147 | 217 | } |
148 | 218 | } |
0 commit comments