1use std::cell::RefCell;
2use std::collections::HashMap;
3use std::collections::hash_map::Entry;
4use std::fs::File;
5use std::io::BufWriter;
6use std::net::{Ipv4Addr, SocketAddrV4};
7use std::path::PathBuf;
89use crate::core::configuration::QDiscMode;
10use crate::core::worker::Worker;
11use crate::host::descriptor::socket::inet::InetSocket;
12use crate::host::network::queuing::{NetworkQueue, NetworkQueueKind};
13use crate::network::PacketDevice;
14use crate::network::packet::{IanaProtocol, PacketRc, PacketStatus};
15use crate::utility::ObjectCounter;
16use crate::utility::callback_queue::CallbackQueue;
17use crate::utility::pcap_writer::{PacketDisplay, PcapWriter};
1819/// The priority used by the fifo qdisc to choose the next socket to send a packet from.
20pub type FifoPacketPriority = u64;
2122#[derive(Debug, Clone)]
23pub struct PcapOptions {
24pub path: PathBuf,
25pub capture_size_bytes: u32,
26}
2728#[derive(Clone, Debug, Eq, Hash, PartialEq)]
29struct AssociatedSocketKey {
30 protocol: IanaProtocol,
31 local: SocketAddrV4,
32 remote: SocketAddrV4,
33}
3435impl AssociatedSocketKey {
36fn new(protocol: IanaProtocol, local: SocketAddrV4, remote: SocketAddrV4) -> Self {
37Self {
38 protocol,
39 local,
40 remote,
41 }
42 }
43}
4445fn setup_pcap_writer(
46 name: &str,
47 options: &PcapOptions,
48) -> std::io::Result<PcapWriter<BufWriter<File>>> {
49let file = File::create(options.path.join(format!("{name}.pcap")))?;
50 PcapWriter::new(BufWriter::new(file), options.capture_size_bytes)
51}
5253/// Represents a network device that can send and receive packets.
54// TODO: remove the ref cells below since the `NetworkNamespace` already stores this interface
55// in a `RefCell`. We should remove the `RefCell`s to simplify the code and fix any circular
56// code paths that exist.
57pub struct NetworkInterface {
58 addr: Ipv4Addr,
59/// The sockets from which we will pull out packets so that we can send them over the network.
60send_sockets: RefCell<NetworkQueue<InetSocket>>,
61/// The sockets to which we will push incoming packets so they can be received by the network
62 /// stack and their payloads read by the managed process.
63recv_sockets: RefCell<HashMap<AssociatedSocketKey, InetSocket>>,
64/// If configured, assists us in writing out pcap files of our packet flows.
65pcap: RefCell<Option<PcapWriter<BufWriter<File>>>>,
66/// Used to prevent recursion during cleanup.
67// TODO: remove when the legacy stack is removed.
68cleanup_in_progress: RefCell<bool>,
69// Declared last so we only count deallocation as successful after the above are dropped.
70_counter: ObjectCounter,
71}
7273impl NetworkInterface {
74/// Create a new network interface for the assigned `addr`. The configured `name` will be used
75 /// to construct a filesystem path for the pcap file (if enabled), so take care in choosing a
76 /// filesystem-appropriate static string.
77pub fn new(
78 name: &str,
79 addr: Ipv4Addr,
80 pcap_options: Option<PcapOptions>,
81 qdisc: QDiscMode,
82 ) -> Self {
83// Try to set up the pcap writer if configured.
84let pcap = pcap_options.and_then(|opt| match setup_pcap_writer(name, &opt) {
85Ok(writer) => Some(writer),
86Err(e) => {
87log::warn!("Unable to set up the configured pcap writer for '{name}': {e}");
88None
89}
90 });
9192log::debug!("Bringing up network interface '{name}' at '{addr}' using {qdisc:?}");
9394let queue_kind = match qdisc {
95// A packet fifo is realized using a min-heap over monitonically increasing priority
96 // values, which encodes the sequence in which the packets became ready to be sent.
97 // A socket's priority is that of its next sendable packet. This is equivalent to a
98 // pfifo, and close to the default Linux qdisc.
99 // https://tldp.org/HOWTO/Traffic-Control-HOWTO/classless-qdiscs.html
100QDiscMode::Fifo => NetworkQueueKind::MinPriority,
101// We use a round-robin policy to select the next socket, send a packet from that
102 // socket, and repeat. We realize this using a fifo queue of sockets that we repeatedly
103 // push() and pop(). A better name for this qdisc is probably 'StochasticFairQueuing':
104 // https://tldp.org/HOWTO/Traffic-Control-HOWTO/classless-qdiscs.html
105QDiscMode::RoundRobin => NetworkQueueKind::FirstInFirstOut,
106 };
107108Self {
109 addr,
110 send_sockets: RefCell::new(NetworkQueue::new(queue_kind)),
111 recv_sockets: RefCell::new(HashMap::new()),
112 pcap: RefCell::new(pcap),
113 cleanup_in_progress: RefCell::new(false),
114 _counter: ObjectCounter::new("NetworkInterface"),
115 }
116 }
117118pub fn associate(
119&self,
120 socket: &InetSocket,
121 protocol: IanaProtocol,
122 port: u16,
123 peer: SocketAddrV4,
124 ) {
125let local = SocketAddrV4::new(self.addr, port);
126let key = AssociatedSocketKey::new(protocol, local, peer);
127log::trace!("Associating socket key {key:?}");
128129if let Entry::Vacant(entry) = self.recv_sockets.borrow_mut().entry(key) {
130 entry.insert(socket.clone());
131 } else {
132// TODO: Return an error if the association fails.
133debug_panic!("Entry is unexpectedly occupied");
134 }
135 }
136137pub fn disassociate(&self, protocol: IanaProtocol, port: u16, peer: SocketAddrV4) {
138if *self.cleanup_in_progress.borrow() {
139return;
140 }
141142let local = SocketAddrV4::new(self.addr, port);
143let key = AssociatedSocketKey::new(protocol, local, peer);
144log::trace!("Disassociating socket key {key:?}");
145146// TODO: Return an error if the disassociation fails. Generally the calling code should only
147 // try to disassociate a socket if it thinks that the socket is actually associated with
148 // this interface, and if it's not, then it's probably an error. But TCP sockets will
149 // disassociate all sockets (including ones that have never been associated) and will try to
150 // disassociate the same socket multiple times, so we can't just add an assert here.
151if self.recv_sockets.borrow_mut().remove(&key).is_none() {
152// Since this always occurs with our legacy TCP stack and is not really a bug, we log at
153 // trace instead of warn level for now until the legacy TCP stack is removed.
154log::trace!("Attempted to disassociate a vacant socket key");
155 }
156 }
157158pub fn is_addr_in_use(&self, protocol: IanaProtocol, port: u16, peer: SocketAddrV4) -> bool {
159let local = SocketAddrV4::new(self.addr, port);
160let key = AssociatedSocketKey::new(protocol, local, peer);
161self.recv_sockets.borrow().contains_key(&key)
162 }
163164// Add the socket to the list of sockets that have data ready for us to send out to the network.
165pub fn add_data_source(&self, socket: &InetSocket) {
166assert!(socket.borrow().has_data_to_send());
167168if !self.send_sockets.borrow().contains(socket) {
169self.send_sockets
170 .borrow_mut()
171 .push(socket.clone(), socket.borrow().peek_next_packet_priority());
172 } else {
173log::trace!(
174"We attemped to add a socket as a packet source but it is already in our queue of \
175 sending sockets. Ignoring."
176);
177 }
178 }
179180/// Disassociate all bound sockets and remove sockets from the sending queue. This should be
181 /// called as part of the host's cleanup procedure. We don't think we need this function for
182 /// Rust sockets, but we think we need it for the legacy TCP stack which will not otherwise drop
183 /// due to circular references.
184pub fn remove_all_sockets(&self) {
185// The legacy TCP stack also calls disassociate on drop, so we need to prevent recursion.
186*self.cleanup_in_progress.borrow_mut() = true;
187self.recv_sockets.borrow_mut().clear();
188self.send_sockets.borrow_mut().clear();
189*self.cleanup_in_progress.borrow_mut() = false;
190 }
191192fn capture_if_configured(&self, packet: &PacketRc) {
193// Avoid double mutable borrow of pcap.
194let mut pcap_borrowed = self.pcap.borrow_mut();
195196if let Some(pcap) = pcap_borrowed.as_mut() {
197let now = Worker::current_time().unwrap().to_abs_simtime();
198199let ts_sec: u32 = now.as_secs().try_into().unwrap_or(u32::MAX);
200let ts_usec: u32 = now.subsec_micros();
201let packet_len: u32 = packet.len().try_into().unwrap_or(u32::MAX);
202203if let Err(e) = pcap.write_packet_fmt(ts_sec, ts_usec, packet_len, |writer| {
204 packet.display_bytes(writer)
205 }) {
206// There was a non-recoverable error.
207log::warn!("Unable to write packet to pcap output: {e}");
208log::warn!(
209"Fatal pcap logging error; stopping pcap logging for interface '{}'.",
210self.addr
211 );
212 pcap_borrowed.take();
213 }
214 }
215 }
216}
217218impl PacketDevice for NetworkInterface {
219fn get_address(&self) -> Ipv4Addr {
220self.addr
221 }
222223// Pops a packet from the interface to send over the simulated network.
224fn pop(&self) -> Option<PacketRc> {
225loop {
226// Choose the next socket that will send a packet.
227let Some(socket) = self.send_sockets.borrow_mut().pop() else {
228log::trace!(
229"Interface {} is now idle with no sockets containing sendable packets.",
230self.addr
231 );
232return None;
233 };
234235// The socket was in our sendable queue, so it _should_ have a packet.
236let Some(packet) = CallbackQueue::queue_and_run_with_legacy(|cb_queue| {
237 socket.borrow_mut().pull_out_packet(cb_queue)
238 }) else {
239// It is possible that the socket changed state since it was added to our queue, so
240 // we tolerate the case that it no longer has a sendable packet.
241continue;
242 };
243244// If socket has more packets, keep tracking it for future sends. Note that it is
245 // possible that the socket was already re-added to the send queue above during the call
246 // to `pull_out_packet()`.
247if socket.borrow().has_data_to_send() {
248self.add_data_source(&socket);
249 }
250251 packet.add_status(PacketStatus::SndInterfaceSent);
252self.capture_if_configured(&packet);
253254return Some(packet);
255 }
256 }
257258// Pushes a packet from the simulated network into the interface.
259fn push(&self, packet: PacketRc) {
260// The packet is successfully received by this interface.
261packet.add_status(PacketStatus::RcvInterfaceReceived);
262263// Record the packet before we process it, otherwise we may send more packets before we
264 // record this one and the order will be incorrect.
265self.capture_if_configured(&packet);
266267// Find the socket that should process the packet.
268let protocol = packet.iana_protocol();
269let local = SocketAddrV4::new(self.addr, packet.dst_ipv4_address().port());
270let peer = packet.src_ipv4_address();
271let key = AssociatedSocketKey::new(protocol, local, peer);
272273// First check for a socket with the specific association.
274log::trace!("Looking for socket associated with specific key {key:?}");
275let maybe_socket = {
276let associated = self.recv_sockets.borrow();
277 associated
278 .get(&key)
279 .or_else(|| {
280// Then fall back to checking for the wildcard association.
281let wildcard = SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, 0);
282let key = AssociatedSocketKey::new(protocol, local, wildcard);
283log::trace!("Looking for socket associated with general key {key:?}");
284 associated.get(&key)
285 })
286// Pushing a packet to the socket may cause the socket to be disassociated, so we
287 // can't hold on to the borrow of `recv_sockets` when we call `push_in_packet`. We
288 // need to clone the socket instead so that we can drop the `recv_sockets` borrow.
289.cloned()
290 };
291292if let Some(socket) = maybe_socket {
293let recv_time = Worker::current_time().unwrap();
294 CallbackQueue::queue_and_run_with_legacy(|cb_queue| {
295 socket
296 .borrow_mut()
297 .push_in_packet(packet, cb_queue, recv_time);
298 });
299 } else {
300 packet.add_status(PacketStatus::RcvInterfaceDropped);
301 }
302 }
303}