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}