Flow 2.0.0
Flow project: Full implementation reference.
port_space.cpp
Go to the documentation of this file.
1/* Flow
2 * Copyright 2023 Akamai Technologies, Inc.
3 *
4 * Licensed under the Apache License, Version 2.0 (the
5 * "License"); you may not use this file except in
6 * compliance with the License. You may obtain a copy
7 * of the License at
8 *
9 * https://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in
12 * writing, software distributed under the License is
13 * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
14 * CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing
16 * permissions and limitations under the License. */
17
18/// @file
20#include "flow/util/util.hpp"
21#include "flow/error/error.hpp"
22#include <boost/random.hpp>
23#include <boost/random/random_device.hpp>
24#include <limits>
25
26namespace flow::net_flow
27{
28
29// Type checks.
30static_assert(sizeof(size_t) > sizeof(flow_port_t),
31 "@todo Investigate why this is required. As of this writing I do not recall off-hand.");
32
33// Static initializations.
34
35const flow_port_t S_PORT_ANY = 0; // Not in Port_space, but the numbering must fit within Port_space's.
36// Avoid overflow via above static assertion.
37const size_t Port_space::S_NUM_PORTS = size_t(std::numeric_limits<flow_port_t>::max()) + 1;
38const size_t Port_space::S_NUM_SERVICE_PORTS = S_NUM_PORTS / 2 - 1;
39// -1 for S_PORT_ANY. @todo I now have no idea what that comment means. Figure it out....
40const size_t Port_space::S_NUM_EPHEMERAL_PORTS = S_NUM_PORTS - S_NUM_SERVICE_PORTS - 1;
41const size_t Port_space::S_MAX_RECENT_EPHEMERAL_PORTS = S_NUM_EPHEMERAL_PORTS / 32;
43const flow_port_t Port_space::S_FIRST_EPHEMERAL_PORT = S_FIRST_SERVICE_PORT + S_NUM_SERVICE_PORTS;
44
45// Implementations.
46
48 log::Log_context(logger_ptr, Flow_log_component::S_NET_FLOW),
49 // Set the bit fields to their permanent widths.
50 m_service_ports(S_NUM_SERVICE_PORTS),
51 m_ephemeral_ports(S_NUM_EPHEMERAL_PORTS),
52 m_ephemeral_and_recent_ephemeral_ports(S_NUM_EPHEMERAL_PORTS)
53{
54 // All 1s = all ports available.
55 m_service_ports.set();
58
59 FLOW_LOG_INFO("Port_space [" << this << "] created; "
60 "port PORT_ANY [" << S_PORT_ANY << "]; service ports [" << S_FIRST_SERVICE_PORT << ", "
63 "ephemeral ports [" << S_FIRST_EPHEMERAL_PORT << ", "
66} // Port_space::Port_space()
67
69{
74}
75
77{
78 if (port == S_PORT_ANY)
79 {
80 return reserve_ephemeral_port(err_code);
81 }
82 // else
83
84 // Explicitly disallow reserving specific ephemeral ports.
85 if (!is_service_port(port))
86 {
88 FLOW_LOG_TRACE("Port was [" << port << "].");
89 return S_PORT_ANY;
90 }
91 // else
92
93 // Bits are indexed from 0, service ports from S_FIRST_SERVICE_PORT.
94 const size_t port_bit_idx = port - S_FIRST_SERVICE_PORT;
95
96 // Ensure not trying to reserve an already reserved service port.
97 if (!m_service_ports.test(port_bit_idx))
98 {
100 FLOW_LOG_TRACE("Port was [" << port << "].");
101 return S_PORT_ANY;
102 }
103 // else
104
105 // Record as reserved.
106 m_service_ports.set(port_bit_idx, false);
107
108 err_code->clear();
109 FLOW_LOG_INFO("Flow service port [" << port << "] reserved.");
110 return port;
111} // Port_space::reserve_port()
112
114{
115 flow_port_t port = S_PORT_ANY; // Failure result.
116
117 // Find a random available and not recently used ephemeral port.
119 if (port_bit_idx != Bit_set::npos)
120 {
121 // Found (typical). Just reserve that port then.
122
123 // Note: This shouldn't overflow.
125 // Subtlety: The following exceeds domain of flow_port_t but not of flow_port_sans_overflow_t.
127 port = port_bit_idx + S_FIRST_EPHEMERAL_PORT;
128 m_ephemeral_ports.set(port_bit_idx, false);
129
130 // Port not being returned => no effect on m_recent_ephemeral_ports.
131
132 /* Maintain invariant: 0 in m_ephemeral_ports => 0 in m_ephemeral_and_recent_ephemeral_ports.
133 * No effect on m_recent_ephemeral_ports => no effect on other invariant. */
134 m_ephemeral_and_recent_ephemeral_ports.set(port_bit_idx, false);
135 }
136 else // if (port_bit_idx == Bit_set::npos) &&
137 if (!m_recent_ephemeral_ports.empty())
138 {
139 // No not-recently-used available ports... but there are still some recently-used availables ones left.
140
141 // Use the top of the queue: the port least recently added to the FIFO.
142 port = m_recent_ephemeral_ports.front();
144 FLOW_LOG_INFO("Ran out of non-recently used and available NetFlow ports; "
145 "reserving oldest recently used available NetFlow port [" << port << "].");
146
147 /* Port not being returned => no effect on m_recent_ephemeral_ports.
148 * Port changing from recently-used-but available to reserved => still should be 0 to maintain invariant. */
149
150 // Reserve port (and sanity-check some stuff).
151 port_bit_idx = port - S_FIRST_EPHEMERAL_PORT;
152 // If it was in the FIFO, it should be 0 in this guy (invariant).
153 assert(!m_ephemeral_and_recent_ephemeral_ports.test(port_bit_idx));
154 // If it was in the FIFO, it was available, so should not be 0 in this guy.
155 assert(m_ephemeral_ports.test(port_bit_idx));
156 // Reserve.
157 m_ephemeral_ports.set(port_bit_idx, false);
158 }
159 // else simply no ephemeral ports available.
160
161 if (port == S_PORT_ANY)
162 {
164 }
165 else
166 {
167 err_code->clear();
168 FLOW_LOG_INFO("NetFlow ephemeral port [" << port << "] reserved.");
169 }
170
171 FLOW_LOG_TRACE("Available ephemeral port count [" << m_ephemeral_ports.count() << "]; "
172 "recently used ephemeral port count [" << m_recent_ephemeral_ports.size() << "].");
173 return port;
174} // Port_space::reserve_ephemeral_port()
175
177{
178 // Check if it's a service port.
179 if (is_service_port(port))
180 {
181 // Straightforward; just return it if it's really reserved.
182 assert(port >= S_FIRST_SERVICE_PORT);
183 const size_t port_bit_idx = port - S_FIRST_SERVICE_PORT;
184
185 // Check for "double-return."
186 if (m_service_ports.test(port_bit_idx))
187 {
189 FLOW_LOG_TRACE("Port was [" << port << "].");
190 return;
191 }
192 // else
193 m_service_ports.set(port_bit_idx, true);
194
195 err_code->clear();
196 FLOW_LOG_INFO("NetFlow service port [" << port << "] returned.");
197 return;
198 } // if (service port)
199 // else
200
201 // Check if it's an ephemeral port.
204 // Subtlety: The following would probably overflow without the conversion.
207 {
208 assert(port >= S_FIRST_EPHEMERAL_PORT);
209 const size_t port_bit_idx = port - S_FIRST_EPHEMERAL_PORT;
210
211 // Check for "double-return."
212 if (m_ephemeral_ports.test(port_bit_idx))
213 {
215 FLOW_LOG_TRACE("Port was [" << port << "].");
216 return;
217 }
218 // else
219
220 // Sanity-check invariant (0 in m_ephemeral_ports => 0 in m_ephemeral_and_recent_ephemeral_ports).
221 assert(!m_ephemeral_and_recent_ephemeral_ports.test(port_bit_idx));
222
223 // Unreserve.
224 m_ephemeral_ports.set(port_bit_idx, true);
225
226 // As promised, any newly returned port goes to the back of the queue (most recent).
227 m_recent_ephemeral_ports.push(port);
228
229 // If we've thus exceeded max size of queue, free the least recent "recent" port of "recent" status.
231 {
232 // Front of queue = oldest.
233 const flow_port_t port = m_recent_ephemeral_ports.front();
234 assert(port >= S_FIRST_EPHEMERAL_PORT);
235 const size_t port_bit_idx = port - S_FIRST_EPHEMERAL_PORT;
237
238 // Sanity-check we always clip the queue right after the maximum is exceeded.
240 // Sanity-check: recently used port we're evicting was indeed not reserved.
241 assert(m_ephemeral_ports.test(port_bit_idx));
242 // Sanity-check invariant: port was in m_recent_ephemeral_ports => bit is 0 in this structure.
243 assert(!m_ephemeral_and_recent_ephemeral_ports.test(port_bit_idx));
244
245 // Maintain invariant: all other ports are 1 in m_ephemeral_and_recent_ephemeral_ports.
246 m_ephemeral_and_recent_ephemeral_ports.set(port_bit_idx, true);
247 }
248
249 err_code->clear();
250 FLOW_LOG_INFO("NetFlow ephemeral port [" << port << "] returned.");
251 FLOW_LOG_TRACE("Available ephemeral port count [" << m_ephemeral_ports.count() << "]; "
252 "recently used ephemeral port count [" << m_recent_ephemeral_ports.size() << "].");
253 return;
254 } // else if (ephemeral port)
255 // else
256
257 // Invalid port number => it was never reserved. Yeah. Am I wrong? I am not.
259 FLOW_LOG_TRACE("Port was [" << port << "].");
260} // Port_space::return_port()
261
263{
264 using boost::random::uniform_int_distribution;
265 using boost::random::random_device;
266
267 /* Pick a random bit in bit field. Use a CSPRNG (not the general-purpose mt19937-based
268 * Random_generator in flow::util) because ephemeral port selection must be unpredictable to off-path
269 * attackers attempting to guess the tuple of an established connection (cf. RFC 6056). */
270 random_device rnd_dev; // Setting this up, in Linux at least, is ~microseconds per new endpoint. No prob.
271 uniform_int_distribution<size_t> range{0, ports.size() - 1};
272 size_t port_bit_idx = range(rnd_dev);
273
274 // If that bit is 0, go right until you find a 1.
275 if (!ports.test(port_bit_idx))
276 {
277 port_bit_idx = ports.find_next(port_bit_idx);
278 // If no 1s to the right, wrap around at the beginning and find the first 1.
279 if (port_bit_idx == Bit_set::npos)
280 {
281 port_bit_idx = ports.find_first();
282 // And if still no 1s found, then so be it (return npos).
283 }
284 }
285 return port_bit_idx;
286} // Port_space::find_available_port_bit_idx
287
288} // namespace flow::net_flow
Interface that the user should implement, passing the implementing Logger into logging classes (Flow'...
Definition: log.hpp:1286
static const size_t S_NUM_EPHEMERAL_PORTS
Total number of "ephemeral" ports (ones reserved at random with reserve_ephemeral_port()).
Definition: port_space.hpp:103
static const size_t S_MAX_RECENT_EPHEMERAL_PORTS
The maximum size of m_recent_ephemeral_ports.
Definition: port_space.hpp:209
flow_port_t reserve_port(flow_port_t port, Error_code *err_code)
Reserve the specified service port, or reserve_ephemeral_port() if the specified port is S_PORT_ANY.
Definition: port_space.cpp:76
Bit_set m_service_ports
Current service port set.
Definition: port_space.hpp:217
flow_port_t reserve_ephemeral_port(Error_code *err_code)
Reserve a randomly chosen available ephemeral port.
Definition: port_space.cpp:113
static const flow_port_t S_FIRST_SERVICE_PORT
The port number of the lowest service port.
Definition: port_space.hpp:106
boost::dynamic_bitset<> Bit_set
Short-hand for bit set of arbitary length, representing a port set (each bit is a port; 1 open,...
Definition: port_space.hpp:171
void return_port(flow_port_t port, Error_code *err_code)
Return a previously reserved port (of any type).
Definition: port_space.cpp:176
static const flow_port_t S_FIRST_EPHEMERAL_PORT
The port number of the lowest ephemeral port.
Definition: port_space.hpp:109
Bit_set m_ephemeral_and_recent_ephemeral_ports
Set representing the union of the set of current reserved ephemeral ports and the set of the last up-...
Definition: port_space.hpp:238
static bool is_service_port(flow_port_t port)
Returns true if and only if the given port is a service port (as opposed to ephemeral or S_PORT_ANY).
Definition: port_space.cpp:68
Port_space(log::Logger *logger)
Constructs the Port_space with all ports available.
Definition: port_space.cpp:47
size_t find_available_port_bit_idx(const Bit_set &ports)
Helper method that, given a reference to a bit set representing available ports (1 available,...
Definition: port_space.cpp:262
static const size_t S_NUM_SERVICE_PORTS
Total number of "service" ports (ones that can be reserved by number with reserve_port()).
Definition: port_space.hpp:100
std::queue< flow_port_t > m_recent_ephemeral_ports
A FIFO of recently used but currently available ephemeral ports.
Definition: port_space.hpp:247
Bit_set m_ephemeral_ports
Current ephemeral port set; indexing analogous to m_service_ports but starting at S_FIRST_EPHEMERAL_P...
Definition: port_space.hpp:223
static const size_t S_NUM_PORTS
Total number of ports in the port space, including S_PORT_ANY.
Definition: port_space.hpp:97
uint32_t flow_port_sans_overflow_t
A type same as flow_port_t but larger, useful when doing arithmetic that might hit overflow in corner...
Definition: port_space.hpp:174
#define FLOW_ERROR_EMIT_ERROR(ARG_val)
Sets *err_code to ARG_val and logs a warning about the error using FLOW_LOG_WARNING().
Definition: error.hpp:202
#define FLOW_LOG_INFO(ARG_stream_fragment)
Logs an INFO message into flow::log::Logger *get_logger() with flow::log::Component get_log_component...
Definition: log.hpp:197
#define FLOW_LOG_TRACE(ARG_stream_fragment)
Logs a TRACE message into flow::log::Logger *get_logger() with flow::log::Component get_log_component...
Definition: log.hpp:227
@ S_INTERNAL_ERROR_PORT_NOT_TAKEN
Internal error: Tried to return Flow port which had not been reserved.
@ S_OUT_OF_PORTS
No more ephemeral Flow ports available.
@ S_PORT_TAKEN
Flow port already reserved.
@ S_INVALID_SERVICE_PORT_NUMBER
Flow port number is not in the valid service port number range.
Flow module containing the API and implementation of the Flow network protocol, a TCP-inspired stream...
Definition: node.cpp:25
uint16_t flow_port_t
Logical Flow port type (analogous to a UDP/TCP port in spirit but in no way relevant to UDP/TCP).
const flow_port_t S_PORT_ANY
Special Flow port value used to indicate "invalid port" or "please pick a random available ephemeral ...
Definition: port_space.cpp:35
bool in_closed_open_range(T const &min_val, T const &val, T const &max_val)
Returns true if and only if the given value is within the given range, given as a [low,...
Definition: util.hpp:296
boost::system::error_code Error_code
Short-hand for a boost.system error code (which basically encapsulates an integer/enum error code and...
Definition: common.hpp:497
Flow_log_component
The flow::log::Component payload enumeration comprising various log components used by Flow's own int...
Definition: common.hpp:627