Flow-IPC 3.0.0
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/log/log.hpp>
24#include <flow/error/error.hpp>
25#include <boost/array.hpp>
26#include <string>
27#include <cstdint>
28#include <sys/socket.h>
29
31{
32
33// Types.
34
35/**
36 * Ancillary data (for `recv[m]msg()` and `send[m]msg()`) buffer, used in our case for storing transmitted
37 * native handle(s), properly sized to store the max supported # of handles and wrapped in a union in
38 * order to ensure it is suitably aligned.
39 *
40 * ...If I (ygoldfel) understand correctly, and I think I do, the idea is that:
41 * - `msg_control` points to a contiguous data area (ancillary data); which is split up into variable-length
42 * sub-areas, each of which starts with a header, in the form of a `cmsghdr`, which contains a couple of
43 * `enum`-like `int`s, a `cmsg_len` specifying the length of the rest of that sub-area (within the sequence),
44 * and then that area which contains the stuff to be transmitted (whose exact size/meaning depends on the
45 * `enum`s; in our case it'll be native handles a/k/a FDs).
46 * - A key caveat is that in order to be properly interpreted, the header -- probably cast by the kernel/something
47 * to `cmsghdr` -- must begin on an aligned address. So, in particular, if whatever comes before it
48 * (could be the preceding ancillary data sub-area, depending) ends just before an odd address, then
49 * the data sub-area must be a byte or a few to the right.
50 * - So this trick explicitly puts a `cmsghdr` there; so that its co-inhabitant #m_buf (the actual area where we'll
51 * shove handles) will also begin on the same address/byte.
52 * - `CMSG_ALIGN()` is a Linux extension that might be usable to avoid having to use the trick, but seeing as
53 * how `man cmsg` in my Linux says it's not portable and itself foregoes it in favor of the trick in the
54 * example, I'll just also use the trick.
55 */
57{
58 /// The ancillary-data buffer.
59 boost::array<uint8_t, CMSG_SPACE(sizeof(Native_handle::handle_t) * N_PAYLOAD_FDS)> m_buf;
60 /// The alignment-ensuring thing.
61 ::cmsghdr m_align;
62};
63
64// Template implementations.
65
66template<typename Protocol>
67Endpoint<Protocol> endpoint_at_shared_name(flow::log::Logger* logger_ptr,
68 const Shared_name& absolute_name, Error_code* err_code)
69{
70 using boost::system::system_error;
71 using std::string;
72
73 FLOW_ERROR_EXEC_AND_THROW_ON_ERROR(Endpoint<Protocol>, endpoint_at_shared_name<Protocol>,
74 logger_ptr, absolute_name, _1);
75 // ^-- Call ourselves and return if err_code is null. If got to present line, err_code is not null.
76
77 FLOW_LOG_SET_CONTEXT(logger_ptr, Log_component::S_TRANSPORT);
78
79 /* Make the name "string" as required to construct the endpoint. This is more subtle than one might expect.
80 * POSIX-y Unix domain sockets are either anonymous (not OK for us), or the endpoints live as files in the
81 * file system. Linux adds the delightful extension, "abstract namespace," wherein instead the endpoints are
82 * named in a cosmically present namespace unconnected to the file system. This avoids the file system issues,
83 * such as permissions and directories and ..., so we want to use that. How to specify it, though?
84 * Well, more or less, at the native level, the sockaddr_un member sun_path (a `char` array) is used either way;
85 * to specify an abstract name one sets the first byte to NUL (which wouldn't be a valid value otherwise); then
86 * the rest of sun_path[] (up to the specified size of the sockaddr_un) is taken as the abstract-namespace name.
87 * Fine... but we use boost.asio; how to get its `endpoint` to behave that way? Bad news is it's not explicitly
88 * documented all the way. Good news is it's quite doable. Firstly, endpoint::data() lets one manually set
89 * the whole sockaddr_un structure ourselves -- that part being no prettier than coding this stuff sans boost.asio
90 * but overall still nicer than all-native code. However, turns out we need not do that either (though this is
91 * the part that isn't explicitly documented but clearly intended if one checks the Boost header code).
92 * Namely: `endpoint` ctor, and the parallel counterpart endpoint::path() mutator overloads, have 2 forms:
93 * one taking `const char*`; and one taking `string[_view]`. `const char*` is clearly insufficient (proof left
94 * as exercise to comment reader). Fascinatingly, string[_view] *is* sufficient: A string[_view] can perfectly
95 * safely store multiple NUL characters; and indeed one can see in the Boost header code that it *specifically*
96 * supports the weird NUL-led abstract-namespace use case (again, I leave details for you to verify). */
97
98 string abstract_namespace_name(size_t(1), '\0'); // Start with NUL!
99 abstract_namespace_name += absolute_name.str(); // After the NUL append the real deal!
100 FLOW_LOG_TRACE("Abstract-namespace (Linux extension) name above consists of 1 NUL + the name "
101 "[" << absolute_name << "]; total of [" << abstract_namespace_name.size() << "] bytes.");
102
103 Endpoint<Protocol> endpoint;
104 auto& sys_err_code = *err_code;
105 try
106 {
107 /* Throws on error. (It's annoying there's no error-code-returning API; a bit odd actually.)
108 * In practice it'll throw only on too-long name (as of this writing; Boost 1.76), but we don't rely on this. */
109 endpoint.path(abstract_namespace_name);
110 sys_err_code.clear(); // If it didn't throw.
111 }
112 catch (const system_error& exc)
113 {
114 FLOW_LOG_WARNING("Unable to set up native local stream endpoint structure; "
115 "could be due to name length; details logged below.");
116 sys_err_code = exc.code();
117 FLOW_ERROR_SYS_ERROR_LOG_WARNING();
118 }
119
120 return endpoint; // sys_err_code is set; `endpoint` might be empty.
121} // endpoint_at_shared_name()
122
123} // namespace ipc::transport::asio_local_stream_socket
String-wrapping abstraction representing a name uniquely distinguishing a kernel-persistent entity fr...
const std::string & str() const
Returns (sans copying) ref to immutable entire wrapped name string, suitable to pass into sys calls w...
Definition: shared_name.cpp:89
size_t size() const
Returns str().size().
Definition: shared_name.cpp:99
Additional (versus boost.asio) APIs for advanced work with local (Unix domain) stream and connected-d...
Endpoint< Protocol > endpoint_at_shared_name(flow::log::Logger *logger_ptr, const Shared_name &absolute_name, Error_code *err_code)
Returns an Endpoint corresponding to the given absolute Shared_name, so that an Acceptor or Peer_sock...
typename Protocol::endpoint Endpoint
Short-hand for boost.asio Unix domain peer stream-socket endpoint.
constexpr size_t N_PAYLOAD_FDS
Our transport::asio_local_stream_socket utilities support transmitting at most this many native handl...
flow::Error_code Error_code
Short-hand for flow::Error_code which is very common.
Definition: common.hpp:313
int handle_t
The native handle type. Much logic relies on this type being light-weight (fast to copy).
Ancillary data (for recv[m]msg() and send[m]msg()) buffer, used in our case for storing transmitted n...
boost::array< uint8_t, CMSG_SPACE(sizeof(Native_handle::handle_t) *N_PAYLOAD_FDS)> m_buf
The ancillary-data buffer.