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