Flow-IPC 1.0.2
Flow-IPC project: Full implementation reference.
asio_local_stream_socket.hpp
Go to the documentation of this file.
1/* Flow-IPC: Core
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
23#include <flow/common.hpp>
24#include <stdexcept>
25
26// See asio_local_stream_socket_fwd.hpp for doc header (intro) to this namespace.
28{
29
30// Types.
31
32#ifndef FLOW_OS_LINUX
33static_assert(false, "Flow-IPC must define Opt_peer_process_credentials w/ Linux SO_PEERCRED semantics. "
34 "Build in Linux only.");
35#endif
36
37/**
38 * Gettable (read-only) socket option for use with asio_local_stream_socket::Peer_socket `.get_option()` in order to
39 * get the connected opposing peer process's credentials (PID/UID/GID/etc.). Note accessors and data are
40 * in non-polymorphic base util::Process_credentials.
41 *
42 * If one calls `X.get_option(Opt_peer_process_credentials& o)` on #Peer_socket `X`, and `X` is connected to opposing
43 * peer socket in process P, then `o.*()` credential accessors (such as `o.process_id()`) shall return values that were
44 * accurate about process P, at the time P executed `Peer_socket::connect()` or `local_ns::connect_pair()` yielding
45 * the connection to "local" peer `X`.
46 *
47 * @see boost.asio docs: `GettableSocketOption` in boost.asio docs: implemented concept.
48 *
49 * @internal
50 * This is the Linux `getsockopt()` option with level `AF_LOCAL` (a/k/a `AF_UNIX`), name `SO_PEERCRED`.
51 */
54{
55public:
56 // Constructors/destructor.
57
58 /// Default ctor: each value is initialized to zero or equivalent.
60
61 /**
62 * Boring copy ctor.
63 * @param src
64 * Source object.
65 */
67
68 // Methods.
69
70 /**
71 * Boring copy assignment.
72 * @param src
73 * Source object.
74 * @return `*this`.
75 */
77
78 /**
79 * For internal boost.asio use, to enable `Peer_socket::get_option(Opt_peer_process_credentials&)` to work.
80 *
81 * @see boost.asio docs: `GettableSocketOption::level()` in boost.asio docs: implemented concept.
82 *
83 * @tparam Protocol
84 * See concept API.
85 * @param proto
86 * See concept API.
87 * @return See concept API.
88 *
89 * @internal
90 * It's `AF_LOCAL` a/k/a `AF_UNIX`.
91 */
92 template<typename Protocol>
93 int level(const Protocol& proto) const;
94
95 /**
96 * For internal boost.asio use, to enable `Peer_socket::get_option(Opt_peer_process_credentials&)` to work.
97 *
98 * @see boost.asio docs: `GettableSocketOption::name()` in boost.asio docs: implemented concept.
99 *
100 * @tparam Protocol
101 * See concept API.
102 * @param proto
103 * See concept API.
104 * @return See concept API.
105 *
106 * @internal
107 * It's `SO_PEERCRED`.
108 */
109 template<typename Protocol>
110 int name(const Protocol& proto) const;
111
112 /**
113 * For internal boost.asio use, to enable `Peer_socket::get_option(Opt_peer_process_credentials&)` to work.
114 *
115 * @see boost.asio docs: `GettableSocketOption::data()` in boost.asio docs: implemented concept.
116 *
117 * @tparam Protocol
118 * See concept API.
119 * @param proto
120 * See concept API.
121 * @return See concept API.
122 *
123 * @internal
124 * It's `SO_PEERCRED`.
125 */
126 template<typename Protocol>
127 void* data(const Protocol& proto);
128
129 /**
130 * For internal boost.asio use, to enable `Peer_socket::get_option(Opt_peer_process_credentials&)` to work.
131 *
132 * @see boost.asio docs: `GettableSocketOption::size()` in boost.asio docs: implemented concept.
133 *
134 * @tparam Protocol
135 * See concept API.
136 * @param proto
137 * See concept API.
138 * @return See concept API.
139 *
140 * @internal
141 * It's `sizeof(ucred)`.
142 */
143 template<typename Protocol>
144 size_t size(const Protocol& proto) const;
145
146 /**
147 * For internal boost.asio use, to enable `Peer_socket::get_option(Opt_peer_process_credentials&)` to work.
148 *
149 * @see boost.asio docs: `GettableSocketOption::resize()` in boost.asio docs: implemented concept.
150 *
151 * @tparam Protocol
152 * See concept API.
153 * @param proto
154 * See concept API.
155 * @param new_size_but_really_must_equal_current
156 * See concept API.
157 *
158 * @internal
159 * Resizing is not allowed for this option, so really it's either a no-op, or -- if
160 * `new_size_but_really_must_equal_current != size()` -- throws exception.
161 */
162 template<typename Protocol>
163 void resize(const Protocol& proto, size_t new_size_but_really_must_equal_current) const;
164}; // class Opt_peer_process_credentials
165
166// Free functions: in *_fwd.hpp.
167
168// Template implementations.
169
170template<typename Task_err>
171void async_write_with_native_handle(flow::log::Logger* logger_ptr,
172 Peer_socket* peer_socket_ptr,
173 Native_handle payload_hndl,
174 const util::Blob_const& payload_blob,
175 Task_err&& on_sent_or_error)
176{
177 using util::blob_data;
178 using flow::util::buffers_dump_string;
179
180 assert(peer_socket_ptr);
181 assert((!payload_hndl.null()) && (payload_blob.size() != 0));
182
183 auto& peer_socket = *peer_socket_ptr;
184
185 FLOW_LOG_SET_CONTEXT(logger_ptr, Log_component::S_TRANSPORT);
186
187 FLOW_LOG_TRACE("Starting: Via connected local peer socket, will send handle [" << payload_hndl << "] with "
188 "blob of size [" << payload_blob.size() << "] "
189 "located @ [" << payload_blob.data() << "].");
190
191 // Verbose and slow (100% skipped unless log filter passes).
192 FLOW_LOG_DATA("Starting: Blob contents are "
193 "[" << buffers_dump_string(payload_blob, " ") << "].");
194
195 /* OK, now our task is to asynchronously send (1) payload_hndl (native handle) and (2) payload_blob (a buffer)
196 * over peer_socket (a connected local socket). As explicitly documented in boost.asio docs: it does not provide an
197 * API for the former (fairly hairy and Linux-specific) handle-transmitting feature, but it is doable by using
198 * sendmsg() natively via peer_socket.native_handle() which gets the native handle (a/k/a FD). That can only
199 * be called when the socket is actually writable; meaning sendmsg() will return 1+ bytes sent; and if not writable
200 * then it'd return EWOULDBLOCK/EAGAIN. Hence we must in this case use reactor-style pattern which is a fancy way
201 * way of saying we must split the typical proactor-style wait-and-write-when-writable operation into 2:
202 * first wait; then once ready try to write. boost.asio will nicely do the first part for us; but once it claims
203 * peer_socket is in fact writable, then we step in with the aforementioned native sendmsg() stuff. So do the
204 * wait now (peer_socket.async_wait()).
205 *
206 * Subtlety: In most cases we'd try to put the body of the handler fully right in here; stylistically that's more
207 * compact and arguably readable. Instead we delegate 99.9% of the code to a helper executed from inside this
208 * closure that'd normally have all the code. Why? Answer: The handler may need to retry this same wait; so it
209 * must specify itself as a handler again. That wouldn't work, even if syntactically achievable by saving
210 * the handler itself via [capture], because async_wait() immediately returns, hence the present function does
211 * too, hence anything on the stack disappears, hence whatever was [captured] would point to nothing. If one tries
212 * to do it, there's probably purely syntactically going to be a chicken-egg problem. This could be worked
213 * around via the oft-used shared_ptr<> technique... but why go crazy? It is more straightforward to
214 * have a permanent actual (helper) function and just have it refer to itself when it must. Splitting this
215 * function body into the above and a helper is perfectly fine anyway, so let's not get too clever.
216 *
217 * Subtlety: I have confirmed that the proper wait type is _write. _error may sound like it detects error conditions
218 * on the socket; but actually that refers to (no thanks to boost.asio docs BTW) obscure
219 * stuff like MSG_OOB... nothing relevant. An actual error like ECONNRESET would stop the _write wait and pass
220 * the triggering error code (as desired). */
221
222 peer_socket.async_wait
223 (Peer_socket::wait_write,
224 [logger_ptr, payload_hndl, payload_blob, peer_socket_ptr,
225 on_sent_or_error = std::move(on_sent_or_error)]
226 (const Error_code& sys_err_code) mutable
227 {
228 on_wait_writable_or_error(logger_ptr, sys_err_code, payload_hndl, payload_blob, peer_socket_ptr,
229 std::move(on_sent_or_error));
230 });
231} // async_write_with_native_handle()
232
233template<typename Task_err_blob, typename Target_payload_blob_func, typename Should_interrupt_func>
235 (flow::log::Logger* logger_ptr, Peer_socket* peer_socket,
236 Target_payload_blob_func&& target_payload_blob_func, Should_interrupt_func&& should_interrupt_func,
237 Task_err_blob&& on_rcvd_or_error)
238{
239 using util::Blob_mutable;
240
241 assert(peer_socket);
242
243 FLOW_LOG_SET_CONTEXT(logger_ptr, Log_component::S_TRANSPORT);
244 FLOW_LOG_TRACE("Starting: Connected local peer socket wants to read 1+ bytes to some currently-unknown location "
245 "TBD when at least some data are available to read. Will try to receive.");
246
247 /* Without having to support should_interrupt_func feature, we could have done async_wait()->determine target
248 * buffer->async_read(). We must support it, so we have so split the latter into repeated
249 * async_wait()->read_some(), so we can check should_interrupt_func() ahead of each read_some().
250 * Kick it off here. */
251
252 peer_socket->async_wait
253 (Peer_socket::wait_read, [logger_ptr, peer_socket,
254 target_payload_blob_func = std::move(target_payload_blob_func),
255 on_rcvd_or_error = std::move(on_rcvd_or_error),
256 should_interrupt_func = std::move(should_interrupt_func)]
257 (const Error_code& async_err_code) mutable
258 {
259 on_wait_readable_or_error<true> // true => See just below for meaning.
260 (logger_ptr, async_err_code, peer_socket, std::move(should_interrupt_func), std::move(on_rcvd_or_error),
261 std::move(target_payload_blob_func), // true => Use this to determine target blob. false would mean ignore it.
262 Blob_mutable(), // true => Ignore this. But it will async-invoke itself with <false> next time.
263 0); // Ditto.
264 });
265} // async_read_with_target_func()
266
267template<typename Task_err, typename Should_interrupt_func>
269 (flow::log::Logger* logger_ptr, Peer_socket* peer_socket, util::Blob_mutable target_payload_blob,
270 Should_interrupt_func&& should_interrupt_func, Task_err&& on_rcvd_or_error)
271{
272 assert(peer_socket);
273
274 FLOW_LOG_SET_CONTEXT(logger_ptr, Log_component::S_TRANSPORT);
275 FLOW_LOG_TRACE("Starting: Connected local peer socket wants to read 1+ bytes to already-known location "
276 "TBD when at least some data are available to read. Will try to receive.");
277
278 auto on_rcvd_or_error_expected_form_func
279 = [on_rcvd_or_error = std::move(on_rcvd_or_error)]
280 (const Error_code& err_code, auto)
281 {
282 // Just ignore the blob location (2nd arg). It's known from the start anyway.
283 on_rcvd_or_error(err_code);
284 };
285
286 peer_socket->async_wait
287 (Peer_socket::wait_read, [logger_ptr, peer_socket,
288 target_payload_blob,
289 on_rcvd_or_error_expected_form_func = std::move(on_rcvd_or_error_expected_form_func),
290 should_interrupt_func = std::move(should_interrupt_func)]
291 (const Error_code& async_err_code) mutable
292 {
293 on_wait_readable_or_error<false> // false => See just below for meaning.
294 (logger_ptr, async_err_code, peer_socket, std::move(should_interrupt_func),
295 std::move(on_rcvd_or_error_expected_form_func),
296 0, // false => Ignore this.
297 target_payload_blob, // false => Just read into this buffer.
298 0); // No bytes read yet.
299 });
300} // async_read_interruptible()
301
302// Opt_peer_process_credentials template implementations.
303
304template<typename Protocol>
306{
307 return AF_LOCAL;
308}
309
310template<typename Protocol>
312{
313 return SO_PEERCRED;
314}
315
316template<typename Protocol>
318{
319 return static_cast<void*>(static_cast<util::Process_credentials*>(this));
320}
321
322template<typename Protocol>
324{
325 return sizeof(util::Process_credentials);
326}
327
328template<typename Protocol>
329void Opt_peer_process_credentials::resize(const Protocol& proto, size_t new_size_but_really_must_equal_current) const
330{
331 using flow::util::ostream_op_string;
332 using std::length_error;
333
334 if (new_size_but_really_must_equal_current != size(proto))
335 {
336 throw length_error(ostream_op_string
337 ("Opt_peer_process_credentials does not actually support resizing; requested size [",
338 new_size_but_really_must_equal_current, "] differs from forever-size [",
339 size(proto), "]. boost.asio internal bug or misuse?"));
340 }
341}
342
343} // namespace ipc::transport::asio_local_stream_socket
Gettable (read-only) socket option for use with asio_local_stream_socket::Peer_socket ....
size_t size(const Protocol &proto) const
For internal boost.asio use, to enable Peer_socket::get_option(Opt_peer_process_credentials&) to work...
Opt_peer_process_credentials()
Default ctor: each value is initialized to zero or equivalent.
Opt_peer_process_credentials & operator=(const Opt_peer_process_credentials &src)
Boring copy assignment.
int level(const Protocol &proto) const
For internal boost.asio use, to enable Peer_socket::get_option(Opt_peer_process_credentials&) to work...
Opt_peer_process_credentials(const Opt_peer_process_credentials &src)
Boring copy ctor.
void * data(const Protocol &proto)
For internal boost.asio use, to enable Peer_socket::get_option(Opt_peer_process_credentials&) to work...
int name(const Protocol &proto) const
For internal boost.asio use, to enable Peer_socket::get_option(Opt_peer_process_credentials&) to work...
void resize(const Protocol &proto, size_t new_size_but_really_must_equal_current) const
For internal boost.asio use, to enable Peer_socket::get_option(Opt_peer_process_credentials&) to work...
A process's credentials (PID, UID, GID as of this writing).
Additional (versus boost.asio) APIs for advanced work with local stream (Unix domain) sockets includi...
local_ns::stream_protocol Protocol
Short-hand for boost.asio Unix domain stream-socket protocol.
Protocol::socket Peer_socket
Short-hand for boost.asio Unix domain peer stream-socket (usually-connected-or-empty guy).
void on_wait_writable_or_error(flow::log::Logger *logger_ptr, const Error_code &sys_err_code, Native_handle payload_hndl, const util::Blob_const &payload_blob_ref, Peer_socket *peer_socket_ptr, Task_err &&on_sent_or_error)
Helper of async_write_with_native_handle() used as the callback executed when waiting for writability...
void async_write_with_native_handle(flow::log::Logger *logger_ptr, Peer_socket *peer_socket_ptr, Native_handle payload_hndl, const util::Blob_const &payload_blob, Task_err &&on_sent_or_error)
boost.asio extension similar to boost::asio::async_write(Peer_socket&, Blob_const,...
void async_read_with_target_func(flow::log::Logger *logger_ptr, Peer_socket *peer_socket, Target_payload_blob_func &&target_payload_blob_func, Should_interrupt_func &&should_interrupt_func, Task_err_blob &&on_rcvd_or_error)
boost.asio extension similar to boost::asio::async_read(Peer_socket&, Blob_mutable,...
void async_read_interruptible(flow::log::Logger *logger_ptr, Peer_socket *peer_socket, util::Blob_mutable target_payload_blob, Should_interrupt_func &&should_interrupt_func, Task_err &&on_rcvd_or_error)
boost.asio extension similar to boost::asio::async_read(Peer_socket&, Blob_mutable,...
const uint8_t * blob_data(const Blob_const &blob)
Syntactic-sugary helper that returns pointer to first byte in an immutable buffer,...
Definition: util.cpp:158
boost::asio::mutable_buffer Blob_mutable
Short-hand for an mutable blob somewhere in memory, stored as exactly a void* and a size_t.
Definition: util_fwd.hpp:140
boost::asio::const_buffer Blob_const
Short-hand for an immutable blob somewhere in memory, stored as exactly a void const * and a size_t.
Definition: util_fwd.hpp:134
flow::Error_code Error_code
Short-hand for flow::Error_code which is very common.
Definition: common.hpp:298
A monolayer-thin wrapper around a native handle, a/k/a descriptor a/k/a FD.
bool null() const
Returns true if and only if m_native_handle equals S_NULL_HANDLE.