Flow 1.0.0
Flow project: Full implementation reference.
port_space.hpp
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
19#pragma once
20
24#include "flow/log/log.hpp"
25#include "flow/util/random.hpp"
26#include <boost/dynamic_bitset.hpp>
27#include <boost/utility.hpp>
28#include <boost/random.hpp>
29#include <queue>
30
31namespace flow::net_flow
32{
33
34/**
35 * Internal `net_flow` class that maintains the available Flow-protocol port space, somewhat similarly to the
36 * classic TCP or UDP port scheme. The class's main use case it to be stored in a Node, though it
37 * could be used in other scenarios of desired. `net_flow` users should not use this class directly.
38 *
39 * The port scheme is similar to TCP or UDP's but not identical. net_flow::flow_port_t maximum value
40 * defines the total number of ports available. Port #S_PORT_ANY is a special value and cannot be
41 * used as a port. The remaining port space is divided into 2 parts: #S_NUM_SERVICE_PORTS "service
42 * ports" and #S_NUM_EPHEMERAL_PORTS "ephemeral ports." Thus the port ranges are
43 * [`S_FIRST_SERVICE_PORT`, `S_FIRST_SERVICE_PORT + S_NUM_SERVICE_PORTS - 1`] and
44 * [`S_FIRST_EPHEMERAL_PORT`, `S_FIRST_EPHEMERAL_PORT + S_NUM_EPHEMERAL_PORTS - 1`], respectively. Both
45 * types of ports are on the same number line so that Flow-protocol socket addressing can be symmetrical
46 * between client and server once a connection has been set up.
47 *
48 * At construction all ports are available. A port can then be "reserved" by the class user. That
49 * port can then no longer be reserved until it is "returned" via return_port().
50 *
51 * One can either reserve to a specified service port (reserve_port()) or a random available
52 * ephemeral port (reserve_ephemeral_port()). Unlike with TCP and UDP ports, it's not possible to
53 * reserve a user-supplied ephemeral port (reserve_port() will fail in this case). Therefore,
54 * service ports and ephemeral ports are entirely separate (it is not merely a convention as in TCP
55 * and UDP ports).
56 *
57 * While this is transparent to the class user, this implementation avoids reserving ephemeral ports
58 * that have been recently returned, to avoid confusion in the socket implementation. It does so by
59 * keeping a queue (of some reasonably large length up to a maximum size) of such ports. Ports
60 * within this queue are reserved only if there is absolutely no ephemeral port space left other
61 * than the ports on the queue.
62 *
63 * Main (all?) differences from TCP/UDP ports: different allocation of ephemeral vs. non-ephemeral
64 * port space; no privileged ports; enforced inability to reserve a specific ephemeral port; no
65 * service name -> port number mapping (yet?).
66 *
67 * ### Thread safety ###
68 * Separate objects: All functionality safe for simultaneous execution from multiple
69 * threads. Same object: Not safe to execute a non-const operation simultaneously with any other
70 * operation; otherwise safe. Rationale: Node only deals with its Port_space in thread W.
71 *
72 * ### Implementation notes ###
73 * `bit_set` objects are used to represent the set of reserved vs. available ports of a
74 * given type (0 means taken, 1 means available). This is not only quick and memory-compact but
75 * also allows a convenient search for a random available port (random bit -> `find_next()` if not
76 * open -> `find_first()` if still not found -> no ports available if even still not found).
77 *
78 * @todo While the ephemeral port reuse avoidance works well within a single Port_space lifetime,
79 * it does not carry over across different Port_spaces. I.e., if Port_space is destructed and then
80 * constructed, the container of recently used ports starts out empty, so a port may (with some low
81 * but sizable probability) be reused. This could be avoided by persisting (e.g., via
82 * boost.serialization) those structures to disk (perhaps at a user-supplied
83 * `boost::filesystem::path`) in `~Port_space()`. This wouldn't work if Port_space crashed, however. To
84 * solve that, we could easily start a thread in Port_space() (and join it in `~Port_space()`) that
85 * would save that state every N seconds. If that's not enough, we can instead have that thread run
86 * a boost.asio `io_service::run()` event loop and `io_service::post()` the save operation onto it (from
87 * arbitrary threads) each time a new ephemeral port is reserved (with some kind of protection
88 * against incessant disk writing built into this worker thread; e.g., don't save if saved in the
89 * last N msec).
90 */
92 public log::Log_context,
93 private boost::noncopyable
94{
95public:
96 // Constants.
97
98 /// Total number of ports in the port space, including #S_PORT_ANY.
99 static const size_t S_NUM_PORTS;
100
101 /// Total number of "service" ports (ones that can be reserved by number with reserve_port()).
102 static const size_t S_NUM_SERVICE_PORTS;
103
104 /// Total number of "ephemeral" ports (ones reserved at random with reserve_ephemeral_port()).
105 static const size_t S_NUM_EPHEMERAL_PORTS;
106
107 /// The port number of the lowest service port.
109
110 /// The port number of the lowest ephemeral port.
112
113 // Constructors/destructor.
114
115 /**
116 * Constructs the Port_space with all ports available.
117 *
118 * @param logger
119 * The Logger implementation to use subsequently.
120 */
121 explicit Port_space(log::Logger* logger);
122
123 // Methods.
124
125 /**
126 * Reserve the specified service port, or reserve_ephemeral_port() if the specified port is
127 * #S_PORT_ANY.
128 *
129 * @param port
130 * A valid and still available service port number, or #S_PORT_ANY.
131 * @param err_code
132 * See flow::Error_code docs for error reporting semantics. error::Code generated:
133 * error::Code::S_INVALID_SERVICE_PORT_NUMBER, error::Code::S_PORT_TAKEN, or
134 * whatever set by reserve_ephemeral_port() if `port == S_PORT_ANY`.
135 * @return On success, the reserved port; on failure, #S_PORT_ANY.
136 */
138
139 /**
140 * Reserve a randomly chosen available ephemeral port.
141 *
142 * @param err_code
143 * See flow::Error_code docs for error reporting semantics. error::Code generated:
144 * error::Code::S_OUT_OF_PORTS.
145 * @return On success, the reserved port; on failure, #S_PORT_ANY.
146 */
148
149 /**
150 * Return a previously reserved port (of any type).
151 *
152 * @param port
153 * A previously reserved port.
154 * @param err_code
155 * See flow::Error_code docs for error reporting semantics. error::Code generated:
156 * error::Code::S_PORT_TAKEN.
157 */
158 void return_port(flow_port_t port, Error_code* err_code);
159
160private:
161 // Types.
162
163 /// Short-hand for bit set of arbitary length, representing a port set (each bit is a port; 1 open, 0 reserved).
164 using Bit_set = boost::dynamic_bitset<>;
165
166 /// Random number generator.
168
169 /// A type same as #flow_port_t but larger, useful when doing arithmetic that might hit overflow in corner cases.
171
172 // Type checks.
173 static_assert((std::numeric_limits<flow_port_sans_overflow_t>::is_integer // Mostly silly sanity checks....
174 == std::numeric_limits<flow_port_t>::is_integer)
175 && (std::numeric_limits<flow_port_sans_overflow_t>::is_signed
176 == std::numeric_limits<flow_port_t>::is_signed)
177 && (sizeof(flow_port_sans_overflow_t) > sizeof(flow_port_t)), // ...but main point is here.
178 "flow_port_sans_overflow_t must be similar to flow_port_t but with larger max value.");
179
180 // Methods.
181
182 /**
183 * Returns `true` if and only if the given port is a service port (as opposed to ephemeral or
184 * #S_PORT_ANY).
185 *
186 * @param port
187 * Port to check.
188 * @return See above.
189 */
190 static bool is_service_port(flow_port_t port);
191
192 /**
193 * Helper method that, given a reference to a bit set representing available ports (1 available, 0
194 * reserved) finds a random available (1) port in that bit set.
195 *
196 * @param ports
197 * A bit set representing port availability (e.g., #m_ephemeral_ports).
198 * @return The bit index of a random available port, or `Bit_set::npos` if all are taken.
199 */
200 size_t find_available_port_bit_idx(const Bit_set& ports);
201
202 // Constants.
203
204 /// The maximum size of #m_recent_ephemeral_ports.
205 static const size_t S_MAX_RECENT_EPHEMERAL_PORTS;
206
207 // Data.
208
209 /**
210 * Current service port set. Bit 0 is port #S_FIRST_SERVICE_PORT, bit 1 is `S_FIRST_SERVICE_PORT + 1`,
211 * etc. In particular, this starts off as all 1s (all ports open).
212 */
214
215 /**
216 * Current ephemeral port set; indexing analogous to #m_service_ports but starting at
217 * #S_FIRST_EPHEMERAL_PORT.
218 */
220
221 /**
222 * Set representing the union of the set of current reserved ephemeral ports and the set of the
223 * last up-to-#S_MAX_RECENT_EPHEMERAL_PORTS returned and available ephemeral ports. In other
224 * words, the union of the ports represented by #m_ephemeral_ports and #m_recent_ephemeral_ports.
225 * In yet other words, a 1 bit represents an available and not-recently-used port. This is kept
226 * for performance, so that when choosing a random port one can find a random 1 bit to find a
227 * not-recently-used and open port.
228 *
229 * Invariants:
230 * - Port `P` is in #m_recent_ephemeral_ports => bit `(P - S_FIRST_EPHEMERAL_PORT)` is 0 in this.
231 * - Bit `N` is 0 in #m_ephemeral_ports => bit `N` is 0 in this.
232 * - All other bits are 1.
233 */
235
236 /**
237 * A FIFO of recently used but currently available ephemeral ports. Algorithm: push port onto
238 * back of queue when it's returned; pop front of queue if #m_recent_ephemeral_ports is beyond
239 * #S_MAX_RECENT_EPHEMERAL_PORTS long. If port is needed and no more
240 * non-#m_ephemeral_and_recent_ephemeral_ports ports are available, pop front of queue (i.e.,
241 * oldest recently used port) and use that. If emptied, there are simply no more ports left.
242 */
243 std::queue<flow_port_t> m_recent_ephemeral_ports;
244
245 /// Random number generator for picking ports.
247}; // class Port_space
248
249} // namespace flow::net_flow
Convenience class that simply stores a Logger and/or Component passed into a constructor; and returns...
Definition: log.hpp:1619
Interface that the user should implement, passing the implementing Logger into logging classes (Flow'...
Definition: log.hpp:1291
Internal net_flow class that maintains the available Flow-protocol port space, somewhat similarly to ...
Definition: port_space.hpp:94
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
boost::random::mt19937 Random_generator
The random generator engine; it is public for the reason explained in Usability section of the Rnd_ge...
Definition: random.hpp:41
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).
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