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}