netstack3_base/port_alloc.rs
1// Copyright 2019 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5//! Ephemeral port allocation provider.
6//!
7//! Defines [`PortAllocImpl`] trait and [`simple_randomized_port_alloc`], used
8//! for ephemeral port allocations in transport protocols.
9
10use core::hash::Hash;
11use core::marker::PhantomData;
12use core::ops::RangeInclusive;
13
14use rand::Rng;
15
16/// A port number.
17// NB: `PortNumber` could be a trait, but given the expected use of the
18// PortAlloc algorithm is to allocate `u16` ports, it's just defined as a type
19// alias for simplicity.
20type PortNumber = u16;
21
22/// Trait that configures the behavior of port allocation.
23///
24/// `PortAllocImpl` provides the types, custom behaviors, and port availability
25/// checks necessary to operate the port allocation algorithm.
26pub trait PortAllocImpl {
27 /// The range of ports that can be allocated.
28 ///
29 /// Local ports used in transport protocols are called [Ephemeral Ports].
30 /// Different transport protocols may define different ranges for the issued
31 /// ports. Port allocation algorithms should guarantee to return a port in
32 /// this range.
33 ///
34 /// [Ephemeral Ports]: https://tools.ietf.org/html/rfc6056#section-2
35 const EPHEMERAL_RANGE: RangeInclusive<PortNumber>;
36 /// The "flow" identifier used to allocate port Ids.
37 ///
38 /// The `Id` is typically the 3 elements other other than the local port in
39 /// the 4-tuple (local IP:port, remote IP:port) that is used to uniquely
40 /// identify the flow information of a connection.
41 type Id: Hash;
42
43 /// An extra argument passed to `is_port_available`.
44 type PortAvailableArg;
45
46 /// Returns a random ephemeral port in `EPHEMERAL_RANGE`
47 fn rand_ephemeral<R: Rng>(rng: &mut R) -> EphemeralPort<Self> {
48 EphemeralPort::new_random(rng)
49 }
50
51 /// Checks if `port` is available to be used for the flow `id`.
52 ///
53 /// Implementers return `true` if the provided `port` is available to be
54 /// used for a given flow `id`. An available port is a port that would not
55 /// conflict for the given `id` *plus* ideally the port is not in LISTEN or
56 /// CLOSED states for a given protocol (see [RFC 6056]).
57 ///
58 /// Note: Callers must guarantee that the given port being checked is within
59 /// the `EPHEMERAL_RANGE`.
60 ///
61 /// [RFC 6056]: https://tools.ietf.org/html/rfc6056#section-2.2
62 fn is_port_available(
63 &self,
64 id: &Self::Id,
65 port: PortNumber,
66 arg: &Self::PortAvailableArg,
67 ) -> bool;
68}
69
70/// A witness type for a port within some ephemeral port range.
71///
72/// `EphemeralPort` is always guaranteed to contain a port that is within
73/// `I::EPHEMERAL_RANGE`.
74pub struct EphemeralPort<I: PortAllocImpl + ?Sized> {
75 port: PortNumber,
76 _marker: PhantomData<I>,
77}
78
79impl<I: PortAllocImpl + ?Sized> EphemeralPort<I> {
80 /// Creates a new `EphemeralPort` with a port chosen randomly in `range`.
81 pub fn new_random<R: Rng>(rng: &mut R) -> Self {
82 let port = rng.gen_range(I::EPHEMERAL_RANGE);
83 Self { port, _marker: PhantomData }
84 }
85
86 /// Increments the current [`PortNumber`] to the next value in the contained
87 /// range, wrapping around to the start of the range.
88 pub fn next(&mut self) {
89 if self.port == *I::EPHEMERAL_RANGE.end() {
90 self.port = *I::EPHEMERAL_RANGE.start();
91 } else {
92 self.port += 1;
93 }
94 }
95
96 /// Gets the `PortNumber` value.
97 pub fn get(&self) -> PortNumber {
98 self.port
99 }
100}
101
102/// Implements the [algorithm 1] as described in RFC 6056.
103///
104/// [algorithm 1]: https://datatracker.ietf.org/doc/html/rfc6056#section-3.3.1
105pub fn simple_randomized_port_alloc<I: PortAllocImpl + ?Sized, R: Rng>(
106 rng: &mut R,
107 id: &I::Id,
108 state: &I,
109 arg: &I::PortAvailableArg,
110) -> Option<PortNumber> {
111 let num_ephemeral = u32::from(I::EPHEMERAL_RANGE.end() - I::EPHEMERAL_RANGE.start()) + 1;
112 let mut candidate = EphemeralPort::<I>::new_random(rng);
113 for _ in 0..num_ephemeral {
114 if state.is_port_available(id, candidate.get(), arg) {
115 return Some(candidate.get());
116 }
117 candidate.next();
118 }
119 None
120}
121
122#[cfg(test)]
123mod tests {
124 use super::*;
125 use crate::testutil::FakeCryptoRng;
126
127 /// A fake flow identifier.
128 #[derive(Hash)]
129 struct FakeId(usize);
130
131 /// Number of different RNG seeds used in tests in this mod.
132 const RNG_ROUNDS: u128 = 128;
133
134 /// Hard-coded fake of available port filter.
135 enum FakeAvailable {
136 /// Only a single port is available.
137 AllowSingle(PortNumber),
138 /// No ports are available.
139 DenyAll,
140 /// Only even-numbered ports are available.
141 AllowEvens,
142 }
143
144 /// Fake implementation of [`PortAllocImpl`].
145 ///
146 /// The `available` field will dictate the return of
147 /// [`PortAllocImpl::is_port_available`] and can be set to get the expected
148 /// testing behavior.
149 struct FakeImpl {
150 available: FakeAvailable,
151 }
152
153 impl PortAllocImpl for FakeImpl {
154 const EPHEMERAL_RANGE: RangeInclusive<u16> = 100..=200;
155 type Id = FakeId;
156 type PortAvailableArg = ();
157
158 fn is_port_available(&self, _id: &Self::Id, port: u16, (): &()) -> bool {
159 match self.available {
160 FakeAvailable::AllowEvens => (port & 1) == 0,
161 FakeAvailable::DenyAll => false,
162 FakeAvailable::AllowSingle(p) => port == p,
163 }
164 }
165 }
166
167 /// Helper fn to test that if only a single port is available, we will
168 /// eventually get that
169 fn test_allow_single(single: u16) {
170 FakeCryptoRng::with_fake_rngs(RNG_ROUNDS, |mut rng| {
171 let fake = FakeImpl { available: FakeAvailable::AllowSingle(single) };
172 let port = simple_randomized_port_alloc(&mut rng, &FakeId(0), &fake, &());
173 assert_eq!(port.unwrap(), single);
174 });
175 }
176
177 #[test]
178 fn test_single_range_start() {
179 // Test boundary condition for first ephemeral port.
180 test_allow_single(FakeImpl::EPHEMERAL_RANGE.start().clone())
181 }
182
183 #[test]
184 fn test_single_range_end() {
185 // Test boundary condition for last ephemeral port.
186 test_allow_single(FakeImpl::EPHEMERAL_RANGE.end().clone())
187 }
188
189 #[test]
190 fn test_single_range_mid() {
191 // Test some other ephemeral port.
192 test_allow_single((FakeImpl::EPHEMERAL_RANGE.end() + FakeImpl::EPHEMERAL_RANGE.start()) / 2)
193 }
194
195 #[test]
196 fn test_allow_none() {
197 // Test that if no ports are available, try_alloc must return none.
198 FakeCryptoRng::with_fake_rngs(RNG_ROUNDS, |mut rng| {
199 let fake = FakeImpl { available: FakeAvailable::DenyAll };
200 let port = simple_randomized_port_alloc(&mut rng, &FakeId(0), &fake, &());
201 assert_eq!(port, None);
202 });
203 }
204
205 #[test]
206 fn test_allow_evens() {
207 // Test that if we only allow even ports, we will always get ports in
208 // the specified range, and they'll always be even.
209 FakeCryptoRng::with_fake_rngs(RNG_ROUNDS, |mut rng| {
210 let fake = FakeImpl { available: FakeAvailable::AllowEvens };
211 let port = simple_randomized_port_alloc(&mut rng, &FakeId(0), &fake, &()).unwrap();
212 assert!(FakeImpl::EPHEMERAL_RANGE.contains(&port));
213 assert_eq!(port & 1, 0);
214 });
215 }
216
217 #[test]
218 fn test_ephemeral_port_random() {
219 // Test that random ephemeral ports are always in range.
220 let mut rng = FakeCryptoRng::new_xorshift(0);
221 for _ in 0..1000 {
222 let rnd_port = EphemeralPort::<FakeImpl>::new_random(&mut rng);
223 assert!(FakeImpl::EPHEMERAL_RANGE.contains(&rnd_port.port));
224 }
225 }
226
227 #[test]
228 fn test_ephemeral_port_next() {
229 let mut port = EphemeralPort::<FakeImpl> {
230 port: *FakeImpl::EPHEMERAL_RANGE.start(),
231 _marker: PhantomData,
232 };
233 // Loop over all the range twice so we see the wrap-around.
234 for _ in 0..=1 {
235 for x in FakeImpl::EPHEMERAL_RANGE {
236 assert_eq!(port.port, x);
237 port.next();
238 }
239 }
240 }
241}