TLA Line data Source code
1 : //
2 : // Copyright (c) 2026 Vinnie Falco (vinnie.falco@gmail.com)
3 : //
4 : // Distributed under the Boost Software License, Version 1.0. (See accompanying
5 : // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6 : //
7 : // Official repository: https://github.com/cppalliance/corosio
8 : //
9 :
10 : #ifndef BOOST_COROSIO_NATIVE_DETAIL_ENDPOINT_CONVERT_HPP
11 : #define BOOST_COROSIO_NATIVE_DETAIL_ENDPOINT_CONVERT_HPP
12 :
13 : #include <boost/corosio/endpoint.hpp>
14 : #include <boost/corosio/family.hpp>
15 : #include <boost/corosio/local_endpoint.hpp>
16 : #include <boost/corosio/detail/platform.hpp>
17 :
18 : #include <algorithm>
19 : #include <cstring>
20 :
21 : #if BOOST_COROSIO_POSIX
22 : #include <sys/socket.h>
23 : #include <sys/un.h>
24 : #include <netinet/in.h>
25 : #include <arpa/inet.h>
26 : #else
27 : #ifndef WIN32_LEAN_AND_MEAN
28 : #define WIN32_LEAN_AND_MEAN
29 : #endif
30 : #ifndef NOMINMAX
31 : #define NOMINMAX
32 : #endif
33 : #include <winsock2.h>
34 : #include <ws2tcpip.h>
35 : #endif
36 :
37 : #include <cstddef> // offsetof
38 : #include <tuple>
39 :
40 : #ifndef AF_UNIX
41 : #define AF_UNIX 1
42 : #endif
43 :
44 : namespace boost::corosio::detail {
45 :
46 : /** Convert IPv4 endpoint to sockaddr_in.
47 :
48 : @param ep The endpoint to convert. Must be IPv4 (is_v4() == true);
49 : a wrong-family endpoint terminates (throwing conversion inside
50 : a noexcept function).
51 : @return A sockaddr_in structure with fields in network byte order.
52 : */
53 : inline sockaddr_in
54 HIT 10792 : to_sockaddr_in(endpoint const& ep) noexcept
55 : {
56 10792 : sockaddr_in sa{};
57 10792 : sa.sin_family = AF_INET;
58 10792 : sa.sin_port = htons(ep.port());
59 10792 : auto bytes = ep.address().to_v4().to_bytes();
60 10792 : std::memcpy(&sa.sin_addr, bytes.data(), 4);
61 10792 : return sa;
62 : }
63 :
64 : /** Convert IPv6 endpoint to sockaddr_in6.
65 :
66 : @param ep The endpoint to convert. Must be IPv6 (is_v6() == true);
67 : a wrong-family endpoint terminates (throwing conversion inside
68 : a noexcept function).
69 : @return A sockaddr_in6 structure with fields in network byte order.
70 : */
71 : inline sockaddr_in6
72 56 : to_sockaddr_in6(endpoint const& ep) noexcept
73 : {
74 56 : sockaddr_in6 sa{};
75 56 : sa.sin6_family = AF_INET6;
76 56 : sa.sin6_port = htons(ep.port());
77 56 : auto addr = ep.address().to_v6();
78 56 : auto bytes = addr.to_bytes();
79 56 : std::memcpy(&sa.sin6_addr, bytes.data(), 16);
80 56 : sa.sin6_scope_id = addr.scope_id();
81 56 : return sa;
82 : }
83 :
84 : /** Create endpoint from sockaddr_in.
85 :
86 : @param sa The sockaddr_in structure with fields in network byte order.
87 : @return An endpoint with address and port extracted from sa.
88 : */
89 : inline endpoint
90 27155 : from_sockaddr_in(sockaddr_in const& sa) noexcept
91 : {
92 : ipv4_address::bytes_type bytes;
93 27155 : std::memcpy(bytes.data(), &sa.sin_addr, 4);
94 27155 : return endpoint(ipv4_address(bytes), ntohs(sa.sin_port));
95 : }
96 :
97 : /** Create endpoint from sockaddr_in6.
98 :
99 : @param sa The sockaddr_in6 structure with fields in network byte order.
100 : @return An endpoint with address and port extracted from sa.
101 : */
102 : inline endpoint
103 87 : from_sockaddr_in6(sockaddr_in6 const& sa) noexcept
104 : {
105 : ipv6_address::bytes_type bytes;
106 87 : std::memcpy(bytes.data(), &sa.sin6_addr, 16);
107 87 : return endpoint(ipv6_address(bytes, sa.sin6_scope_id), ntohs(sa.sin6_port));
108 : }
109 :
110 : /** Convert an IPv4 endpoint to an IPv4-mapped IPv6 sockaddr_in6.
111 :
112 : Produces a `sockaddr_in6` with the `::ffff:` prefix, suitable
113 : for passing an IPv4 destination to a dual-stack IPv6 socket.
114 :
115 : @param ep The endpoint to convert. Must be IPv4 (is_v4() == true);
116 : a wrong-family endpoint terminates (throwing conversion inside
117 : a noexcept function).
118 : @return A sockaddr_in6 with the IPv4-mapped address.
119 : */
120 : inline sockaddr_in6
121 2 : to_v4_mapped_sockaddr_in6(endpoint const& ep) noexcept
122 : {
123 2 : sockaddr_in6 sa{};
124 2 : sa.sin6_family = AF_INET6;
125 2 : sa.sin6_port = htons(ep.port());
126 : // The mapping constructor supplies the ::ffff:0:0/96 prefix
127 2 : auto bytes = ipv6_address(ep.address().to_v4()).to_bytes();
128 2 : std::memcpy(&sa.sin6_addr, bytes.data(), 16);
129 2 : return sa;
130 : }
131 :
132 : /** Convert endpoint to sockaddr_storage.
133 :
134 : Dispatches to @ref to_sockaddr_in or @ref to_sockaddr_in6
135 : based on the endpoint's address family.
136 :
137 : @param ep The endpoint to convert.
138 : @param storage Output parameter filled with the sockaddr.
139 : @return The length of the filled sockaddr structure.
140 : */
141 : inline socklen_t
142 10829 : to_sockaddr(endpoint const& ep, sockaddr_storage& storage) noexcept
143 : {
144 10829 : std::memset(&storage, 0, sizeof(storage));
145 10829 : if (ep.is_v4())
146 : {
147 10778 : auto sa = to_sockaddr_in(ep);
148 10778 : std::memcpy(&storage, &sa, sizeof(sa));
149 10778 : return sizeof(sa);
150 : }
151 51 : auto sa6 = to_sockaddr_in6(ep);
152 51 : std::memcpy(&storage, &sa6, sizeof(sa6));
153 51 : return sizeof(sa6);
154 : }
155 :
156 : /** Convert endpoint to sockaddr_storage for a specific socket family.
157 :
158 : When the socket is AF_INET6 and the endpoint is IPv4, the address
159 : is converted to an IPv4-mapped IPv6 address (`::ffff:x.x.x.x`) so
160 : dual-stack sockets can connect to IPv4 destinations.
161 :
162 : @param ep The endpoint to convert.
163 : @param socket_family The address family of the socket (AF_INET or
164 : AF_INET6).
165 : @param storage Output parameter filled with the sockaddr.
166 : @return The length of the filled sockaddr structure.
167 : */
168 : inline socklen_t
169 8566 : to_sockaddr(
170 : endpoint const& ep, int socket_family, sockaddr_storage& storage) noexcept
171 : {
172 : // IPv4 endpoint on IPv6 socket: use IPv4-mapped address.
173 : // The reverse mismatch (v4-mapped IPv6 endpoint on an AF_INET
174 : // socket) is deliberately not bridged; the kernel rejects it.
175 8566 : if (ep.is_v4() && socket_family == AF_INET6)
176 : {
177 2 : std::memset(&storage, 0, sizeof(storage));
178 2 : auto sa6 = to_v4_mapped_sockaddr_in6(ep);
179 2 : std::memcpy(&storage, &sa6, sizeof(sa6));
180 2 : return sizeof(sa6);
181 : }
182 8564 : return to_sockaddr(ep, storage);
183 : }
184 :
185 : /** Create endpoint from sockaddr_storage.
186 :
187 : Dispatches on `ss_family` to reconstruct the appropriate
188 : IPv4 or IPv6 endpoint.
189 :
190 : @param storage The sockaddr_storage with fields in network byte order.
191 : @return An endpoint with address and port extracted from storage.
192 : */
193 : inline endpoint
194 27215 : from_sockaddr(sockaddr_storage const& storage) noexcept
195 : {
196 27215 : if (storage.ss_family == AF_INET)
197 : {
198 : sockaddr_in sa;
199 27132 : std::memcpy(&sa, &storage, sizeof(sa));
200 27132 : return from_sockaddr_in(sa);
201 : }
202 83 : if (storage.ss_family == AF_INET6)
203 : {
204 : sockaddr_in6 sa6;
205 83 : std::memcpy(&sa6, &storage, sizeof(sa6));
206 83 : return from_sockaddr_in6(sa6);
207 : }
208 MIS 0 : return endpoint{};
209 : }
210 :
211 : /** Convert a native address family to the portable one.
212 :
213 : @param af The native family (`AF_INET`, `AF_INET6`, or anything
214 : else, which maps to v4 as the inert value).
215 : @return The portable family.
216 : */
217 : inline corosio::family
218 HIT 8759 : to_family(int af) noexcept
219 : {
220 8759 : return af == AF_INET6 ? corosio::family::v6 : corosio::family::v4;
221 : }
222 :
223 : /** Convert the portable address family to the native one.
224 :
225 : @param f The portable family.
226 : @return `AF_INET` or `AF_INET6`.
227 : */
228 : inline int
229 10987 : native_family(corosio::family f) noexcept
230 : {
231 10987 : return f == corosio::family::v6 ? AF_INET6 : AF_INET;
232 : }
233 :
234 : /** Return the native address family for an endpoint.
235 :
236 : @param ep The endpoint to query.
237 : @return `AF_INET` for IPv4, `AF_INET6` for IPv6.
238 : */
239 : inline int
240 : endpoint_family(endpoint const& ep) noexcept
241 : {
242 : return ep.is_v6() ? AF_INET6 : AF_INET;
243 : }
244 :
245 : /** Return the address family of a socket descriptor.
246 :
247 : @param fd The socket file descriptor.
248 : @return AF_INET, AF_INET6, or AF_UNSPEC on failure.
249 : */
250 : inline int
251 17526 : socket_family(
252 : #if BOOST_COROSIO_POSIX
253 : int fd
254 : #else
255 : std::uintptr_t fd
256 : #endif
257 : ) noexcept
258 : {
259 17526 : sockaddr_storage storage{};
260 17526 : socklen_t len = sizeof(storage);
261 17526 : if (getsockname(
262 : #if BOOST_COROSIO_POSIX
263 : fd,
264 : #else
265 : static_cast<SOCKET>(fd),
266 : #endif
267 17526 : reinterpret_cast<sockaddr*>(&storage), &len) != 0)
268 2 : return AF_UNSPEC;
269 17524 : return storage.ss_family;
270 : }
271 :
272 : //----------------------------------------------------------
273 : // local_endpoint (AF_UNIX) conversions
274 : //----------------------------------------------------------
275 :
276 : // Platform-agnostic sockaddr_un alias. POSIX uses the real
277 : // sockaddr_un from <sys/un.h>; Windows uses a private struct
278 : // matching the layout (same approach as Boost.Asio).
279 : #if BOOST_COROSIO_POSIX
280 : using un_sa_t = sockaddr_un;
281 : #else
282 : struct un_sa_t
283 : {
284 : u_short sun_family;
285 : char sun_path[108];
286 : };
287 : #endif
288 :
289 : /** Convert a local_endpoint to sockaddr_storage.
290 :
291 : @param ep The local endpoint to convert.
292 : @param storage Output parameter filled with the sockaddr_un.
293 : @return The length of the filled sockaddr structure.
294 : */
295 : inline socklen_t
296 290 : to_sockaddr(local_endpoint const& ep, sockaddr_storage& storage) noexcept
297 : {
298 290 : std::memset(&storage, 0, sizeof(storage));
299 290 : un_sa_t sa{};
300 290 : sa.sun_family = AF_UNIX;
301 290 : auto path = ep.path();
302 580 : auto copy_len = (std::min)(
303 290 : path.size(),
304 290 : (std::min)(local_endpoint::max_path_length, sizeof(sa.sun_path)));
305 290 : if (copy_len > 0)
306 290 : std::memcpy(sa.sun_path, path.data(), copy_len);
307 290 : std::memcpy(&storage, &sa, sizeof(sa));
308 :
309 290 : if (ep.is_abstract())
310 6 : return static_cast<socklen_t>(offsetof(un_sa_t, sun_path) + copy_len);
311 284 : return static_cast<socklen_t>(sizeof(sa));
312 : }
313 :
314 : /** Convert a local_endpoint to sockaddr_storage (family-aware overload).
315 :
316 : The socket_family parameter is ignored for Unix sockets since
317 : there is no dual-stack mapping.
318 :
319 : @param ep The local endpoint to convert.
320 : @param socket_family Ignored.
321 : @param storage Output parameter filled with the sockaddr_un.
322 : @return The length of the filled sockaddr structure.
323 : */
324 : inline socklen_t
325 201 : to_sockaddr(
326 : local_endpoint const& ep,
327 : int /*socket_family*/,
328 : sockaddr_storage& storage) noexcept
329 : {
330 201 : return to_sockaddr(ep, storage);
331 : }
332 :
333 : /** Create a local_endpoint from sockaddr_storage.
334 :
335 : @param storage The sockaddr_storage (must have ss_family == AF_UNIX).
336 : @param len The address length returned by the kernel.
337 : @return A local_endpoint with the path extracted from the
338 : sockaddr_un, or an empty endpoint if the family is not AF_UNIX.
339 : */
340 : inline local_endpoint
341 946 : from_sockaddr_local(sockaddr_storage const& storage, socklen_t len) noexcept
342 : {
343 946 : if (storage.ss_family != AF_UNIX)
344 1 : return local_endpoint{};
345 :
346 945 : un_sa_t sa{};
347 945 : std::memcpy(
348 945 : &sa, &storage, (std::min)(static_cast<std::size_t>(len), sizeof(sa)));
349 :
350 945 : auto path_offset = offsetof(un_sa_t, sun_path);
351 945 : if (static_cast<std::size_t>(len) <= path_offset)
352 707 : return local_endpoint{};
353 :
354 : // Clamp to the buffer: a foreign len may overstate the payload,
355 : // and sun_path is the struct's last member.
356 476 : auto path_len = (std::min)(static_cast<std::size_t>(len) - path_offset,
357 238 : sizeof(sa.sun_path));
358 :
359 : // Non-abstract paths may be null-terminated by the kernel
360 238 : if (path_len > 0 && sa.sun_path[0] != '\0')
361 : {
362 : auto* end =
363 232 : static_cast<char const*>(std::memchr(sa.sun_path, '\0', path_len));
364 232 : if (end)
365 232 : path_len = static_cast<std::size_t>(end - sa.sun_path);
366 : }
367 :
368 : // A foreign sun_path may exceed corosio's cap; the factory then
369 : // yields the empty endpoint, which is what we return.
370 238 : return std::get<1>(
371 476 : make_local_endpoint(std::string_view(sa.sun_path, path_len)));
372 : }
373 :
374 : //----------------------------------------------------------
375 : // Tag-dispatch helpers for templatized reactor code.
376 : // Overload resolution selects the correct conversion based
377 : // on the Endpoint type.
378 : //----------------------------------------------------------
379 :
380 : /** Convert sockaddr_storage to an IP endpoint (tag overload).
381 :
382 : @param storage The sockaddr_storage with fields in network byte order.
383 : @param len The address length returned by the kernel.
384 : @return An endpoint with address and port extracted from storage.
385 : */
386 : inline endpoint
387 27214 : from_sockaddr_as(
388 : sockaddr_storage const& storage,
389 : socklen_t /*len*/,
390 : endpoint const&) noexcept
391 : {
392 27214 : return from_sockaddr(storage);
393 : }
394 :
395 : /** Convert sockaddr_storage to a local_endpoint (tag overload).
396 :
397 : @param storage The sockaddr_storage.
398 : @param len The address length returned by the kernel.
399 : @return A local_endpoint with path extracted from storage.
400 : */
401 : inline local_endpoint
402 946 : from_sockaddr_as(
403 : sockaddr_storage const& storage,
404 : socklen_t len,
405 : local_endpoint const&) noexcept
406 : {
407 946 : return from_sockaddr_local(storage, len);
408 : }
409 :
410 : } // namespace boost::corosio::detail
411 :
412 : #endif
|