1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
// Copyright 2019 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

//! Ephemeral port allocation provider.
//!
//! Defines [`PortAllocImpl`] trait and [`simple_randomized_port_alloc`], used
//! for ephemeral port allocations in transport protocols.

use core::hash::Hash;
use core::marker::PhantomData;
use core::ops::RangeInclusive;

use rand::Rng;

/// A port number.
// NB: `PortNumber` could be a trait, but given the expected use of the
// PortAlloc algorithm is to allocate `u16` ports, it's just defined as a type
// alias for simplicity.
type PortNumber = u16;

/// Trait that configures the behavior of port allocation.
///
/// `PortAllocImpl` provides the types, custom behaviors, and port availability
/// checks necessary to operate the port allocation algorithm.
pub trait PortAllocImpl {
    /// The range of ports that can be allocated.
    ///
    /// Local ports used in transport protocols are called [Ephemeral Ports].
    /// Different transport protocols may define different ranges for the issued
    /// ports. Port allocation algorithms should guarantee to return a port in
    /// this range.
    ///
    /// [Ephemeral Ports]: https://tools.ietf.org/html/rfc6056#section-2
    const EPHEMERAL_RANGE: RangeInclusive<PortNumber>;
    /// The "flow" identifier used to allocate port Ids.
    ///
    /// The `Id` is typically the 3 elements other other than the local port in
    /// the 4-tuple (local IP:port, remote IP:port) that is used to uniquely
    /// identify the flow information of a connection.
    type Id: Hash;

    /// An extra argument passed to `is_port_available`.
    type PortAvailableArg;

    /// Returns a random ephemeral port in `EPHEMERAL_RANGE`
    fn rand_ephemeral<R: Rng>(rng: &mut R) -> EphemeralPort<Self> {
        EphemeralPort::new_random(rng)
    }

    /// Checks if `port` is available to be used for the flow `id`.
    ///
    /// Implementers return `true` if the provided `port` is available to be
    /// used for a given flow `id`. An available port is a port that would not
    /// conflict for the given `id` *plus* ideally the port is not in LISTEN or
    /// CLOSED states for a given protocol (see [RFC 6056]).
    ///
    /// Note: Callers must guarantee that the given port being checked is within
    /// the `EPHEMERAL_RANGE`.
    ///
    /// [RFC 6056]: https://tools.ietf.org/html/rfc6056#section-2.2
    fn is_port_available(
        &self,
        id: &Self::Id,
        port: PortNumber,
        arg: &Self::PortAvailableArg,
    ) -> bool;
}

/// A witness type for a port within some ephemeral port range.
///
/// `EphemeralPort` is always guaranteed to contain a port that is within
/// `I::EPHEMERAL_RANGE`.
pub struct EphemeralPort<I: PortAllocImpl + ?Sized> {
    port: PortNumber,
    _marker: PhantomData<I>,
}

impl<I: PortAllocImpl + ?Sized> EphemeralPort<I> {
    /// Creates a new `EphemeralPort` with a port chosen randomly in `range`.
    pub fn new_random<R: Rng>(rng: &mut R) -> Self {
        let port = rng.gen_range(I::EPHEMERAL_RANGE);
        Self { port, _marker: PhantomData }
    }

    /// Increments the current [`PortNumber`] to the next value in the contained
    /// range, wrapping around to the start of the range.
    pub fn next(&mut self) {
        if self.port == *I::EPHEMERAL_RANGE.end() {
            self.port = *I::EPHEMERAL_RANGE.start();
        } else {
            self.port += 1;
        }
    }

    /// Gets the `PortNumber` value.
    pub fn get(&self) -> PortNumber {
        self.port
    }
}

/// Implements the [algorithm 1] as described in RFC 6056.
///
/// [algorithm 1]: https://datatracker.ietf.org/doc/html/rfc6056#section-3.3.1
pub fn simple_randomized_port_alloc<I: PortAllocImpl + ?Sized, R: Rng>(
    rng: &mut R,
    id: &I::Id,
    state: &I,
    arg: &I::PortAvailableArg,
) -> Option<PortNumber> {
    let num_ephemeral = u32::from(I::EPHEMERAL_RANGE.end() - I::EPHEMERAL_RANGE.start()) + 1;
    let mut candidate = EphemeralPort::<I>::new_random(rng);
    for _ in 0..num_ephemeral {
        if state.is_port_available(id, candidate.get(), arg) {
            return Some(candidate.get());
        }
        candidate.next();
    }
    None
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::testutil::FakeCryptoRng;

    /// A fake flow identifier.
    #[derive(Hash)]
    struct FakeId(usize);

    /// Number of different RNG seeds used in tests in this mod.
    const RNG_ROUNDS: u128 = 128;

    /// Hard-coded fake of available port filter.
    enum FakeAvailable {
        /// Only a single port is available.
        AllowSingle(PortNumber),
        /// No ports are available.
        DenyAll,
        /// Only even-numbered ports are available.
        AllowEvens,
    }

    /// Fake implementation of [`PortAllocImpl`].
    ///
    /// The `available` field will dictate the return of
    /// [`PortAllocImpl::is_port_available`] and can be set to get the expected
    /// testing behavior.
    struct FakeImpl {
        available: FakeAvailable,
    }

    impl PortAllocImpl for FakeImpl {
        const EPHEMERAL_RANGE: RangeInclusive<u16> = 100..=200;
        type Id = FakeId;
        type PortAvailableArg = ();

        fn is_port_available(&self, _id: &Self::Id, port: u16, (): &()) -> bool {
            match self.available {
                FakeAvailable::AllowEvens => (port & 1) == 0,
                FakeAvailable::DenyAll => false,
                FakeAvailable::AllowSingle(p) => port == p,
            }
        }
    }

    /// Helper fn to test that if only a single port is available, we will
    /// eventually get that
    fn test_allow_single(single: u16) {
        FakeCryptoRng::with_fake_rngs(RNG_ROUNDS, |mut rng| {
            let fake = FakeImpl { available: FakeAvailable::AllowSingle(single) };
            let port = simple_randomized_port_alloc(&mut rng, &FakeId(0), &fake, &());
            assert_eq!(port.unwrap(), single);
        });
    }

    #[test]
    fn test_single_range_start() {
        // Test boundary condition for first ephemeral port.
        test_allow_single(FakeImpl::EPHEMERAL_RANGE.start().clone())
    }

    #[test]
    fn test_single_range_end() {
        // Test boundary condition for last ephemeral port.
        test_allow_single(FakeImpl::EPHEMERAL_RANGE.end().clone())
    }

    #[test]
    fn test_single_range_mid() {
        // Test some other ephemeral port.
        test_allow_single((FakeImpl::EPHEMERAL_RANGE.end() + FakeImpl::EPHEMERAL_RANGE.start()) / 2)
    }

    #[test]
    fn test_allow_none() {
        // Test that if no ports are available, try_alloc must return none.
        FakeCryptoRng::with_fake_rngs(RNG_ROUNDS, |mut rng| {
            let fake = FakeImpl { available: FakeAvailable::DenyAll };
            let port = simple_randomized_port_alloc(&mut rng, &FakeId(0), &fake, &());
            assert_eq!(port, None);
        });
    }

    #[test]
    fn test_allow_evens() {
        // Test that if we only allow even ports, we will always get ports in
        // the specified range, and they'll always be even.
        FakeCryptoRng::with_fake_rngs(RNG_ROUNDS, |mut rng| {
            let fake = FakeImpl { available: FakeAvailable::AllowEvens };
            let port = simple_randomized_port_alloc(&mut rng, &FakeId(0), &fake, &()).unwrap();
            assert!(FakeImpl::EPHEMERAL_RANGE.contains(&port));
            assert_eq!(port & 1, 0);
        });
    }

    #[test]
    fn test_ephemeral_port_random() {
        // Test that random ephemeral ports are always in range.
        let mut rng = FakeCryptoRng::new_xorshift(0);
        for _ in 0..1000 {
            let rnd_port = EphemeralPort::<FakeImpl>::new_random(&mut rng);
            assert!(FakeImpl::EPHEMERAL_RANGE.contains(&rnd_port.port));
        }
    }

    #[test]
    fn test_ephemeral_port_next() {
        let mut port = EphemeralPort::<FakeImpl> {
            port: *FakeImpl::EPHEMERAL_RANGE.start(),
            _marker: PhantomData,
        };
        // Loop over all the range twice so we see the wrap-around.
        for _ in 0..=1 {
            for x in FakeImpl::EPHEMERAL_RANGE {
                assert_eq!(port.port, x);
                port.next();
            }
        }
    }
}