Flow 1.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 <limits>
23
24namespace flow::net_flow
25{
26
27// Type checks.
28static_assert(sizeof(size_t) > sizeof(flow_port_t),
29 "@todo Investigate why this is required. As of this writing I do not recall off-hand.");
30
31// Static initializations.
32
33const flow_port_t S_PORT_ANY = 0; // Not in Port_space, but the numbering must fit within Port_space's.
34// Avoid overflow via above static assertion.
35const size_t Port_space::S_NUM_PORTS = size_t(std::numeric_limits<flow_port_t>::max()) + 1;
36const size_t Port_space::S_NUM_SERVICE_PORTS = S_NUM_PORTS / 2 - 1;
37// -1 for S_PORT_ANY. @todo I now have no idea what that comment means. Figure it out....
38const size_t Port_space::S_NUM_EPHEMERAL_PORTS = S_NUM_PORTS - S_NUM_SERVICE_PORTS - 1;
39const size_t Port_space::S_MAX_RECENT_EPHEMERAL_PORTS = S_NUM_EPHEMERAL_PORTS / 32;
41const flow_port_t Port_space::S_FIRST_EPHEMERAL_PORT = S_FIRST_SERVICE_PORT + S_NUM_SERVICE_PORTS;
42
43// Implementations.
44
46 log::Log_context(logger_ptr, Flow_log_component::S_NET_FLOW),
47 // Set the bit fields to their permanent widths.
48 m_service_ports(S_NUM_SERVICE_PORTS),
49 m_ephemeral_ports(S_NUM_EPHEMERAL_PORTS),
50 m_ephemeral_and_recent_ephemeral_ports(S_NUM_EPHEMERAL_PORTS),
51 m_rnd_generator(Random_generator::result_type(util::time_since_posix_epoch().count()))
52{
53 // All 1s = all ports available.
54 m_service_ports.set();
57
58 FLOW_LOG_INFO("Port_space [" << this << "] created; "
59 "port PORT_ANY [" << S_PORT_ANY << "]; service ports [" << S_FIRST_SERVICE_PORT << ", "
62 "ephemeral ports [" << S_FIRST_EPHEMERAL_PORT << ", "
65} // Port_space::Port_space()
66
68{
73}
74
76{
77 if (port == S_PORT_ANY)
78 {
79 return reserve_ephemeral_port(err_code);
80 }
81 // else
82
83 // Explicitly disallow reserving specific ephemeral ports.
84 if (!is_service_port(port))
85 {
87 FLOW_LOG_TRACE("Port was [" << port << "].");
88 return S_PORT_ANY;
89 }
90 // else
91
92 // Bits are indexed from 0, service ports from S_FIRST_SERVICE_PORT.
93 const size_t port_bit_idx = port - S_FIRST_SERVICE_PORT;
94
95 // Ensure not trying to reserve an already reserved service port.
96 if (!m_service_ports.test(port_bit_idx))
97 {
99 FLOW_LOG_TRACE("Port was [" << port << "].");
100 return S_PORT_ANY;
101 }
102 // else
103
104 // Record as reserved.
105 m_service_ports.set(port_bit_idx, false);
106
107 err_code->clear();
108 FLOW_LOG_INFO("Flow service port [" << port << "] reserved.");
109 return port;
110} // Port_space::reserve_port()
111
113{
114 flow_port_t port = S_PORT_ANY; // Failure result.
115
116 // Find a random available and not recently used ephemeral port.
118 if (port_bit_idx != Bit_set::npos)
119 {
120 // Found (typical). Just reserve that port then.
121
122 // Note: This shouldn't overflow.
124 // Subtlety: The following exceeds domain of flow_port_t but not of flow_port_sans_overflow_t.
126 port = port_bit_idx + S_FIRST_EPHEMERAL_PORT;
127 m_ephemeral_ports.set(port_bit_idx, false);
128
129 // Port not being returned => no effect on m_recent_ephemeral_ports.
130
131 /* Maintain invariant: 0 in m_ephemeral_ports => 0 in m_ephemeral_and_recent_ephemeral_ports.
132 * No effect on m_recent_ephemeral_ports => no effect on other invariant. */
133 m_ephemeral_and_recent_ephemeral_ports.set(port_bit_idx, false);
134 }
135 else // if (port_bit_idx == Bit_set::npos) &&
136 if (!m_recent_ephemeral_ports.empty())
137 {
138 // No not-recently-used available ports... but there are still some recently-used availables ones left.
139
140 // Use the top of the queue: the port least recently added to the FIFO.
141 port = m_recent_ephemeral_ports.front();
143 FLOW_LOG_INFO("Ran out of non-recently used and available NetFlow ports; "
144 "reserving oldest recently used available NetFlow port [" << port << "].");
145
146 /* Port not being returned => no effect on m_recent_ephemeral_ports.
147 * Port changing from recently-used-but available to reserved => still should be 0 to maintain invariant. */
148
149 // Reserve port (and sanity-check some stuff).
150 port_bit_idx = port - S_FIRST_EPHEMERAL_PORT;
151 // If it was in the FIFO, it should be 0 in this guy (invariant).
152 assert(!m_ephemeral_and_recent_ephemeral_ports.test(port_bit_idx));
153 // If it was in the FIFO, it was available, so should not be 0 in this guy.
154 assert(m_ephemeral_ports.test(port_bit_idx));
155 // Reserve.
156 m_ephemeral_ports.set(port_bit_idx, false);
157 }
158 // else simply no ephemeral ports available.
159
160 if (port == S_PORT_ANY)
161 {
163 }
164 else
165 {
166 err_code->clear();
167 FLOW_LOG_INFO("NetFlow ephemeral port [" << port << "] reserved.");
168 }
169
170 FLOW_LOG_TRACE("Available ephemeral port count [" << m_ephemeral_ports.count() << "]; "
171 "recently used ephemeral port count [" << m_recent_ephemeral_ports.size() << "].");
172 return port;
173} // Port_space::reserve_ephemeral_port()
174
176{
177 // Check if it's a service port.
178 if (is_service_port(port))
179 {
180 // Straightforward; just return it if it's really reserved.
181 assert(port >= S_FIRST_SERVICE_PORT);
182 const size_t port_bit_idx = port - S_FIRST_SERVICE_PORT;
183
184 // Check for "double-return."
185 if (m_service_ports.test(port_bit_idx))
186 {
188 FLOW_LOG_TRACE("Port was [" << port << "].");
189 return;
190 }
191 // else
192 m_service_ports.set(port_bit_idx, true);
193
194 err_code->clear();
195 FLOW_LOG_INFO("NetFlow service port [" << port << "] returned.");
196 return;
197 } // if (service port)
198 // else
199
200 // Check if it's an ephemeral port.
203 // Subtlety: The following would probably overflow without the conversion.
206 {
207 assert(port >= S_FIRST_EPHEMERAL_PORT);
208 const size_t port_bit_idx = port - S_FIRST_EPHEMERAL_PORT;
209
210 // Check for "double-return."
211 if (m_ephemeral_ports.test(port_bit_idx))
212 {
214 FLOW_LOG_TRACE("Port was [" << port << "].");
215 return;
216 }
217 // else
218
219 // Sanity-check invariant (0 in m_ephemeral_ports => 0 in m_ephemeral_and_recent_ephemeral_ports).
220 assert(!m_ephemeral_and_recent_ephemeral_ports.test(port_bit_idx));
221
222 // Unreserve.
223 m_ephemeral_ports.set(port_bit_idx, true);
224
225 // As promised, any newly returned port goes to the back of the queue (most recent).
226 m_recent_ephemeral_ports.push(port);
227
228 // If we've thus exceeded max size of queue, free the least recent "recent" port of "recent" status.
230 {
231 // Front of queue = oldest.
232 const flow_port_t port = m_recent_ephemeral_ports.front();
233 assert(port >= S_FIRST_EPHEMERAL_PORT);
234 const size_t port_bit_idx = port - S_FIRST_EPHEMERAL_PORT;
236
237 // Sanity-check we always clip the queue right after the maximum is exceeded.
239 // Sanity-check: recently used port we're evicting was indeed not reserved.
240 assert(m_ephemeral_ports.test(port_bit_idx));
241 // Sanity-check invariant: port was in m_recent_ephemeral_ports => bit is 0 in this structure.
242 assert(!m_ephemeral_and_recent_ephemeral_ports.test(port_bit_idx));
243
244 // Maintain invariant: all other ports are 1 in m_ephemeral_and_recent_ephemeral_ports.
245 m_ephemeral_and_recent_ephemeral_ports.set(port_bit_idx, true);
246 }
247
248 err_code->clear();
249 FLOW_LOG_INFO("NetFlow ephemeral port [" << port << "] returned.");
250 FLOW_LOG_TRACE("Available ephemeral port count [" << m_ephemeral_ports.count() << "]; "
251 "recently used ephemeral port count [" << m_recent_ephemeral_ports.size() << "].");
252 return;
253 } // else if (ephemeral port)
254 // else
255
256 // Invalid port number => it was never reserved. Yeah. Am I wrong? I am not.
258 FLOW_LOG_TRACE("Port was [" << port << "].");
259} // Port_space::return_port()
260
262{
263 using boost::random::uniform_int_distribution;
264
265 // Pick a random bit in bit field.
266 uniform_int_distribution<size_t> range(0, ports.size() - 1);
267 size_t port_bit_idx = range(m_rnd_generator);
268
269 // If that bit is 0, go right until you find a 1.
270 if (!ports.test(port_bit_idx))
271 {
272 port_bit_idx = ports.find_next(port_bit_idx);
273 // If no 1s to the right, wrap around at the beginning and find the first 1.
274 if (port_bit_idx == Bit_set::npos)
275 {
276 port_bit_idx = ports.find_first();
277 // And if still no 1s found, then so be it (return npos).
278 }
279 }
280 return port_bit_idx;
281} // Port_space::find_available_port_bit_idx
282
283} // namespace flow::net_flow
Interface that the user should implement, passing the implementing Logger into logging classes (Flow'...
Definition: log.hpp:1291
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:105
static const size_t S_MAX_RECENT_EPHEMERAL_PORTS
The maximum size of m_recent_ephemeral_ports.
Definition: port_space.hpp:205
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:75
Bit_set m_service_ports
Current service port set.
Definition: port_space.hpp:213
flow_port_t reserve_ephemeral_port(Error_code *err_code)
Reserve a randomly chosen available ephemeral port.
Definition: port_space.cpp:112
static const flow_port_t S_FIRST_SERVICE_PORT
The port number of the lowest service port.
Definition: port_space.hpp:108
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:164
void return_port(flow_port_t port, Error_code *err_code)
Return a previously reserved port (of any type).
Definition: port_space.cpp:175
static const flow_port_t S_FIRST_EPHEMERAL_PORT
The port number of the lowest ephemeral port.
Definition: port_space.hpp:111
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:234
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:67
Port_space(log::Logger *logger)
Constructs the Port_space with all ports available.
Definition: port_space.cpp:45
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:261
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:102
std::queue< flow_port_t > m_recent_ephemeral_ports
A FIFO of recently used but currently available ephemeral ports.
Definition: port_space.hpp:243
Random_generator m_rnd_generator
Random number generator for picking ports.
Definition: port_space.hpp:246
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:219
static const size_t S_NUM_PORTS
Total number of ports in the port space, including S_PORT_ANY.
Definition: port_space.hpp:99
util::Rnd_gen_uniform_range_base::Random_generator Random_generator
Random number generator.
Definition: port_space.hpp:167
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:170
#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:33
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:262
boost::chrono::microseconds time_since_posix_epoch()
Get the current POSIX (Unix) time as a duration from the Epoch time point.
Definition: util.cpp:147
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:502
Flow_log_component
The flow::log::Component payload enumeration comprising various log components used by Flow's own int...
Definition: common.hpp:632