capsules_extra/net/udp/
udp_port_table.rs

1// Licensed under the Apache License, Version 2.0 or the MIT License.
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3// Copyright Tock Contributors 2022.
4
5//! In-kernel structure for tracking UDP ports bound by capsules.
6//!
7//! When kernel capsules wish to send or receive UDP packets, the UDP sending / receiving
8//! capsules will only allow this if the capsule has bound to the port it wishes to
9//! send from / receive on. Binding to a port is accomplished via calls on the
10//! `UdpPortManager` struct defined in this file. Calls to bind on this table enforce that only
11//! one capsule can be bound to a given port at any time. Once capsules successfully bind
12//! using this table, they receive back binding structures (`UdpPortBindingTx`/`UdpPortBindingRx`)
13//! that act as proof that the holder
14//! is bound to that port. These structures can only be created within this file, and calls
15//! to unbind must consume these structures, enforcing this invariant.
16//! The UDP tx/rx capsules require these bindings be passed in order to send/receive on a given
17//! port. Separate bindings are used for sending and receiving because the UdpReceiver must
18//! hold onto the binding for as long as a capsule wishes to receive packets on a port, so
19//! a separate binding must be available to enable sending packets on a port while
20//! listening on the same port.
21//!
22//! To reduce the size of data structures required for this task, a fixed size
23//! array is used to store bindings in the kernel. This means that a limited
24//! number of bindings can be stored at any point in time. Reserving a slot
25//! in this table is done by requesting a socket, which represents a reserved slot.
26//! These sockets are then used to request bindings on a particular port.
27//!
28//! This file only stores information about which ports are bound by capsules.
29//! The files `udp_send.rs` and `udp_recv.rs` enforce that only capsules possessing
30//! the correct bindings can actually send / recv on a given port.
31//!
32//! Userspace port bindings are managed separately by the userspace UDP driver
33//! (`capsules/src/net/udp/driver.rs`), because apps can be dynamically added or
34//! removed. Bindings for userspace apps are stored in the grant regions of each app,
35//! such that removing an app automatically unbinds it. This file is able to query the
36//! userspace UDP driver to check which ports are bound, and vice-versa, such that
37//! exclusive access to ports between userspace apps and capsules is still enforced.
38
39use crate::net::network_capabilities::{NetworkCapability, UdpVisibilityCapability};
40
41use core::fmt;
42
43use kernel::capabilities::{CreatePortTableCapability, UdpDriverCapability};
44use kernel::utilities::cells::{OptionalCell, TakeCell};
45use kernel::ErrorCode;
46
47// Sets the maximum number of UDP ports that can be bound by capsules. Reducing this number
48// can save a small amount of memory, and slightly reduces the overhead of iterating through the
49// table to check whether a port is already bound. Note: if this numberis changed,
50// port_table_test2 in udp_lowpan_test.rs will fail since it tests the capacity of
51// the port table -- therefore that test should be modified when MAX_NUM_BOUND_PORTS
52// is.
53pub const MAX_NUM_BOUND_PORTS: usize = 16;
54
55/// Conveys what port is bound at the given index.
56///
57/// If no port is bound, the value stored at that location in the
58/// table is Unbound.
59#[derive(Clone, Copy, PartialEq)]
60pub enum SocketBindingEntry {
61    Port(u16),
62    Unbound,
63}
64
65/// The PortQuery trait enables the UdpPortManager to query the userspace bound
66/// ports in the UDP driver. The UDP driver struct implements this trait.
67pub trait PortQuery {
68    fn is_bound(&self, port: u16) -> bool;
69}
70
71/// Provides a handle into the bound port table.
72///
73/// When binding to a port, the socket is consumed and Udp{Sender,
74/// Receiver}Binding structs are returned. When undbinding, the socket
75/// is returned and can be used to bind to other ports.
76#[derive(Debug)]
77pub struct UdpSocket {
78    idx: usize,
79    port_table: &'static UdpPortManager,
80}
81
82/// Maps bound ports to userspace port bindings.
83pub struct UdpPortManager {
84    port_array: TakeCell<'static, [Option<SocketBindingEntry>]>,
85    user_ports: OptionalCell<&'static dyn PortQuery>,
86    udp_vis: &'static UdpVisibilityCapability,
87}
88
89impl fmt::Debug for UdpPortManager {
90    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
91        write!(f, "[Port Table]")
92    }
93}
94
95impl UdpSocket {
96    // important that this function is not public. If it were, capsules could
97    // obtain access to ports bound by other capsules
98    fn new(idx: usize, pt: &'static UdpPortManager) -> UdpSocket {
99        UdpSocket {
100            idx,
101            port_table: pt,
102        }
103    }
104}
105
106impl Drop for UdpSocket {
107    fn drop(&mut self) {
108        self.port_table.destroy_socket(self);
109    }
110}
111
112/// An opaque descriptor that allows the holder to obtain a binding on a port
113/// for receiving UDP packets.
114#[derive(Debug)]
115pub struct UdpPortBindingRx {
116    idx: usize,
117    port: u16,
118}
119
120/// An opaque descriptor that allows the holder to obtain a binding on a port
121/// for sending UDP packets.
122#[derive(Debug)]
123pub struct UdpPortBindingTx {
124    idx: usize,
125    port: u16,
126}
127
128impl UdpPortBindingTx {
129    fn new(idx: usize, port: u16) -> UdpPortBindingTx {
130        UdpPortBindingTx { idx, port }
131    }
132
133    pub fn get_port(&self) -> u16 {
134        self.port
135    }
136}
137
138impl UdpPortBindingRx {
139    fn new(idx: usize, port: u16) -> UdpPortBindingRx {
140        UdpPortBindingRx { idx, port }
141    }
142
143    pub fn get_port(&self) -> u16 {
144        self.port
145    }
146}
147
148impl UdpPortManager {
149    // Require capability so that the port table is only created by kernel
150    pub fn new(
151        _cap: &dyn CreatePortTableCapability,
152        used_kernel_ports: &'static mut [Option<SocketBindingEntry>],
153        udp_vis: &'static UdpVisibilityCapability,
154    ) -> UdpPortManager {
155        UdpPortManager {
156            port_array: TakeCell::new(used_kernel_ports),
157            user_ports: OptionalCell::empty(),
158            udp_vis,
159        }
160    }
161
162    // This function is called to set a reference to the UDP driver, so that the ports
163    // bound by applications can be queried from within this file.
164    pub fn set_user_ports(
165        &self,
166        user_ports_ref: &'static dyn PortQuery,
167        _driver_cap: &dyn UdpDriverCapability,
168    ) {
169        self.user_ports.replace(user_ports_ref);
170    }
171
172    /// Called by capsules that would like to eventually be able to bind to a
173    /// UDP port. This call will succeed unless MAX_NUM_BOUND_PORTS capsules
174    /// have already bound to a port.
175    pub fn create_socket(&'static self) -> Result<UdpSocket, Result<(), ErrorCode>> {
176        self.port_array
177            .map_or(Err(Err(ErrorCode::NOSUPPORT)), |table| {
178                let mut result: Result<UdpSocket, Result<(), ErrorCode>> =
179                    Err(Err(ErrorCode::FAIL));
180                for i in 0..MAX_NUM_BOUND_PORTS {
181                    match table[i] {
182                        None => {
183                            result = Ok(UdpSocket::new(i, self));
184                            table[i] = Some(SocketBindingEntry::Unbound);
185                            break;
186                        }
187                        _ => (),
188                    }
189                }
190                result
191            })
192    }
193
194    /// Called when sockets are dropped to free their slots in the table.
195    /// The slot in the table is only freed if the socket that is dropped is
196    /// unbound. If the slot is bound, the socket is being dropped after a call to
197    /// bind(), and the slot in the table should remain reserved.
198    fn destroy_socket(&self, socket: &UdpSocket) {
199        self.port_array.map(|table| match table[socket.idx] {
200            Some(entry) => {
201                if entry == SocketBindingEntry::Unbound {
202                    table[socket.idx] = None;
203                }
204            }
205            _ => {}
206        });
207    }
208
209    /// Check if a given port is already bound, by either an app or capsule.
210    pub fn is_bound(&self, port: u16) -> Result<bool, ()> {
211        // First, check the user bindings.
212        if self.user_ports.is_none() {
213            return Err(());
214        }
215        let user_bound = self
216            .user_ports
217            .map_or(true, |port_query| port_query.is_bound(port));
218        if user_bound {
219            return Ok(true);
220        }
221        let ret = self
222            .port_array
223            .map(|table| {
224                let mut port_exists = false;
225                for i in 0..MAX_NUM_BOUND_PORTS {
226                    match table[i] {
227                        Some(SocketBindingEntry::Port(p)) => {
228                            if p == port {
229                                port_exists = true;
230                                break;
231                            }
232                        }
233                        _ => (),
234                    }
235                }
236                port_exists
237            })
238            .unwrap();
239        Ok(ret)
240    }
241
242    /// Called by capsules that have already reserved a socket to attempt to bind to
243    /// a UDP port. The socket is passed by value.
244    /// On success, bindings is returned. On failure, the same
245    /// UdpSocket is returned.
246    pub fn bind(
247        &self,
248        socket: UdpSocket,
249        port: u16,
250        net_cap: &'static NetworkCapability,
251    ) -> Result<(UdpPortBindingTx, UdpPortBindingRx), UdpSocket> {
252        if net_cap.local_port_valid(port, self.udp_vis) {
253            match self.is_bound(port) {
254                Ok(bound) => {
255                    if bound {
256                        Err(socket)
257                    } else {
258                        self.port_array
259                            .map(|table| {
260                                table[socket.idx] = Some(SocketBindingEntry::Port(port));
261                                let binding_pair = (
262                                    UdpPortBindingTx::new(socket.idx, port),
263                                    UdpPortBindingRx::new(socket.idx, port),
264                                );
265                                // Add socket to the linked list.
266                                Ok(binding_pair)
267                            })
268                            .unwrap()
269                    }
270                }
271                Err(()) => Err(socket),
272            }
273        } else {
274            Err(socket)
275        }
276    }
277
278    /// Disassociate the port from the given binding. Return the socket associated
279    /// with the passed bindings. On Err, return the passed bindings.
280    pub fn unbind(
281        &'static self,
282        sender_binding: UdpPortBindingTx,
283        receiver_binding: UdpPortBindingRx,
284    ) -> Result<UdpSocket, (UdpPortBindingTx, UdpPortBindingRx)> {
285        // Verify that the indices match up
286        if sender_binding.idx != receiver_binding.idx {
287            return Err((sender_binding, receiver_binding));
288        }
289        let idx = sender_binding.idx;
290        self.port_array.map(|table| {
291            table[idx] = Some(SocketBindingEntry::Unbound);
292        });
293        // Search the list and return the appropriate socket
294        Ok(UdpSocket::new(idx, self))
295    }
296}