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