shadow_rs/host/network/
namespace.rs

1use std::cell::{Cell, RefCell};
2use std::net::{Ipv4Addr, SocketAddrV4};
3use std::ops::{Deref, DerefMut};
4use std::sync::Arc;
5
6use atomic_refcell::AtomicRefCell;
7
8use crate::core::configuration::QDiscMode;
9use crate::core::worker::Worker;
10use crate::host::descriptor::socket::abstract_unix_ns::AbstractUnixNamespace;
11use crate::host::descriptor::socket::inet::InetSocket;
12use crate::host::network::interface::{NetworkInterface, PcapOptions};
13use crate::network::packet::IanaProtocol;
14
15// The start of our random port range in host order, used if application doesn't
16// specify the port it wants to bind to, and for client connections.
17const MIN_RANDOM_PORT: u16 = 10000;
18
19/// Represents a network namespace.
20///
21/// Can be thought of as roughly equivalent to a Linux `struct net`. Shadow doesn't support multiple
22/// network namespaces, but this `NetworkNamespace` allows us to consolidate the host's networking
23/// objects, and hopefully might make it easier to support multiple network namespaces if we want to
24/// in the future.
25pub struct NetworkNamespace {
26    // map abstract socket addresses to unix sockets
27    pub unix: Arc<AtomicRefCell<AbstractUnixNamespace>>,
28
29    pub localhost: RefCell<NetworkInterface>,
30    pub internet: RefCell<NetworkInterface>,
31
32    pub default_ip: Ipv4Addr,
33
34    // used for debugging to make sure we've cleaned up before being dropped
35    has_run_cleanup: Cell<bool>,
36}
37
38impl NetworkNamespace {
39    pub fn new(public_ip: Ipv4Addr, pcap: Option<PcapOptions>, qdisc: QDiscMode) -> Self {
40        let localhost = NetworkInterface::new("lo", Ipv4Addr::LOCALHOST, pcap.clone(), qdisc);
41
42        let internet = NetworkInterface::new("eth0", public_ip, pcap, qdisc);
43
44        Self {
45            unix: Arc::new(AtomicRefCell::new(AbstractUnixNamespace::new())),
46            localhost: RefCell::new(localhost),
47            internet: RefCell::new(internet),
48            default_ip: public_ip,
49            has_run_cleanup: Cell::new(false),
50        }
51    }
52
53    /// Clean up the network namespace. This should be called while `Worker` has the active host
54    /// set.
55    pub fn cleanup(&self) {
56        assert!(!self.has_run_cleanup.get());
57
58        // we need to unref all sockets and free them before we drop the host, otherwise they'll try
59        // to access the global host and panic since there is no host
60        self.localhost.borrow().remove_all_sockets();
61        self.internet.borrow().remove_all_sockets();
62
63        self.has_run_cleanup.set(true);
64    }
65
66    /// Returns `None` if there is no such interface.
67    #[track_caller]
68    pub fn interface_borrow(
69        &self,
70        addr: Ipv4Addr,
71    ) -> Option<impl Deref<Target = NetworkInterface> + '_> {
72        // Notes:
73        // - The `is_loopback` matches all loopback addresses, but shadow will only work correctly
74        //   with 127.0.0.1. Using any other loopback address will lead to problems.
75        // - If the address is 0.0.0.0, we return the `internet` interface. This is not ideal if a
76        //   socket bound to 0.0.0.0 is trying to send a localhost packet and uses this method to
77        //   get the network interface, since the packet will be sent on the internet interface
78        //   instead of loopback. It's not clear if this will lead to bugs.
79        if addr.is_loopback() {
80            Some(self.localhost.borrow())
81        } else if addr == self.default_ip || addr.is_unspecified() {
82            Some(self.internet.borrow())
83        } else {
84            None
85        }
86    }
87
88    /// Returns `None` if there is no such interface.
89    #[track_caller]
90    pub fn interface_borrow_mut(
91        &self,
92        addr: Ipv4Addr,
93    ) -> Option<impl DerefMut<Target = NetworkInterface> + '_> {
94        // Notes:
95        // - The `is_loopback` matches all loopback addresses, but shadow will only work correctly
96        //   with 127.0.0.1. Using any other loopback address will lead to problems.
97        // - If the address is 0.0.0.0, we return the `internet` interface. This is not ideal if a
98        //   socket bound to 0.0.0.0 is trying to send a localhost packet and uses this method to
99        //   get the network interface, since the packet will be sent on the internet interface
100        //   instead of loopback. It's not clear if this will lead to bugs.
101        if addr.is_loopback() {
102            Some(self.localhost.borrow_mut())
103        } else if addr == self.default_ip || addr.is_unspecified() {
104            Some(self.internet.borrow_mut())
105        } else {
106            None
107        }
108    }
109
110    pub fn is_addr_in_use(
111        &self,
112        protocol_type: IanaProtocol,
113        src: SocketAddrV4,
114        dst: SocketAddrV4,
115    ) -> Result<bool, NoInterface> {
116        if src.ip().is_unspecified() {
117            Ok(self
118                .localhost
119                .borrow()
120                .is_addr_in_use(protocol_type, src.port(), dst)
121                || self
122                    .internet
123                    .borrow()
124                    .is_addr_in_use(protocol_type, src.port(), dst))
125        } else {
126            match self.interface_borrow(*src.ip()) {
127                Some(i) => Ok(i.is_addr_in_use(protocol_type, src.port(), dst)),
128                None => Err(NoInterface),
129            }
130        }
131    }
132
133    /// Returns a random port in host byte order.
134    pub fn get_random_free_port(
135        &self,
136        protocol_type: IanaProtocol,
137        interface_ip: Ipv4Addr,
138        peer: SocketAddrV4,
139        mut rng: impl rand::Rng,
140    ) -> Option<u16> {
141        // we need a random port that is free everywhere we need it to be.
142        // we have two modes here: first we just try grabbing a random port until we
143        // get a free one. if we cannot find one fast enough, then as a fallback we
144        // do an inefficient linear search that is guaranteed to succeed or fail.
145
146        // if choosing randomly doesn't succeed within 10 tries, then we have already
147        // allocated a lot of ports (>90% on average). then we fall back to linear search.
148        for _ in 0..10 {
149            let random_port = rng.random_range(MIN_RANDOM_PORT..=u16::MAX);
150
151            // `is_addr_in_use` will check all interfaces in the case of INADDR_ANY
152            let specific_in_use = self
153                .is_addr_in_use(
154                    protocol_type,
155                    SocketAddrV4::new(interface_ip, random_port),
156                    peer,
157                )
158                .unwrap_or(true);
159            let generic_in_use = self
160                .is_addr_in_use(
161                    protocol_type,
162                    SocketAddrV4::new(interface_ip, random_port),
163                    SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, 0),
164                )
165                .unwrap_or(true);
166            if !specific_in_use && !generic_in_use {
167                return Some(random_port);
168            }
169        }
170
171        // now if we tried too many times and still don't have a port, fall back
172        // to a linear search to make sure we get a free port if we have one.
173        // but start from a random port instead of the min.
174        let start = rng.random_range(MIN_RANDOM_PORT..=u16::MAX);
175        for port in (start..=u16::MAX).chain(MIN_RANDOM_PORT..start) {
176            let specific_in_use = self
177                .is_addr_in_use(protocol_type, SocketAddrV4::new(interface_ip, port), peer)
178                .unwrap_or(true);
179            let generic_in_use = self
180                .is_addr_in_use(
181                    protocol_type,
182                    SocketAddrV4::new(interface_ip, port),
183                    SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, 0),
184                )
185                .unwrap_or(true);
186            if !specific_in_use && !generic_in_use {
187                return Some(port);
188            }
189        }
190
191        log::warn!("unable to find free ephemeral port for {protocol_type:?} peer {peer}");
192        None
193    }
194
195    /// Associate the socket with any applicable network interfaces. The socket will be
196    /// automatically disassociated when the returned handle is dropped.
197    ///
198    /// # Safety
199    ///
200    /// Pointer args must be safely dereferenceable.
201    pub unsafe fn associate_interface(
202        &self,
203        socket: &InetSocket,
204        protocol: IanaProtocol,
205        bind_addr: SocketAddrV4,
206        peer_addr: SocketAddrV4,
207    ) -> AssociationHandle {
208        if bind_addr.ip().is_unspecified() {
209            // need to associate all interfaces
210            self.localhost
211                .borrow()
212                .associate(socket, protocol, bind_addr.port(), peer_addr);
213            self.internet
214                .borrow()
215                .associate(socket, protocol, bind_addr.port(), peer_addr);
216        } else {
217            // TODO: return error if interface does not exist
218            if let Some(iface) = self.interface_borrow(*bind_addr.ip()) {
219                iface.associate(socket, protocol, bind_addr.port(), peer_addr);
220            }
221        }
222
223        AssociationHandle {
224            protocol,
225            local_addr: bind_addr,
226            remote_addr: peer_addr,
227        }
228    }
229
230    /// Disassociate the socket associated using the local and remote addresses from all network
231    /// interfaces.
232    ///
233    /// Is only public so that it can be called from `host_disassociateInterface`. Normally this
234    /// should only be called from the [`AssociationHandle`].
235    pub fn disassociate_interface(
236        &self,
237        protocol: IanaProtocol,
238        bind_addr: SocketAddrV4,
239        peer_addr: SocketAddrV4,
240    ) {
241        if bind_addr.ip().is_unspecified() {
242            // need to disassociate all interfaces
243            self.localhost
244                .borrow()
245                .disassociate(protocol, bind_addr.port(), peer_addr);
246
247            self.internet
248                .borrow()
249                .disassociate(protocol, bind_addr.port(), peer_addr);
250        } else {
251            // TODO: return error if interface does not exist
252            if let Some(iface) = self.interface_borrow(*bind_addr.ip()) {
253                iface.disassociate(protocol, bind_addr.port(), peer_addr);
254            }
255        }
256    }
257}
258
259impl std::ops::Drop for NetworkNamespace {
260    fn drop(&mut self) {
261        if !self.has_run_cleanup.get() && !std::thread::panicking() {
262            debug_panic!("Dropped the network namespace before it has been cleaned up");
263        }
264    }
265}
266
267#[derive(Copy, Clone, Debug, PartialEq, Eq)]
268pub struct NoInterface;
269
270impl std::fmt::Display for NoInterface {
271    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
272        write!(f, "No interface available")
273    }
274}
275
276impl std::error::Error for NoInterface {}
277
278/// A handle for a socket association with a network interface(s).
279///
280/// The network association will be dissolved when this handle is dropped (similar to
281/// [`callback_queue::Handle`](crate::utility::callback_queue::Handle)).
282#[derive(Debug)]
283pub struct AssociationHandle {
284    protocol: IanaProtocol,
285    local_addr: SocketAddrV4,
286    remote_addr: SocketAddrV4,
287}
288
289impl AssociationHandle {
290    pub fn local_addr(&self) -> SocketAddrV4 {
291        self.local_addr
292    }
293
294    pub fn remote_addr(&self) -> SocketAddrV4 {
295        self.remote_addr
296    }
297}
298
299impl std::ops::Drop for AssociationHandle {
300    fn drop(&mut self) {
301        Worker::with_active_host(|host| {
302            host.network_namespace_borrow().disassociate_interface(
303                self.protocol,
304                self.local_addr,
305                self.remote_addr,
306            );
307        })
308        .unwrap();
309    }
310}