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