TLA Line data Source code
1 : //
2 : // Copyright (c) 2026 Steve Gerbino
3 : // Copyright (c) 2026 Michael Vandeberg
4 : //
5 : // Distributed under the Boost Software License, Version 1.0. (See accompanying
6 : // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
7 : //
8 : // Official repository: https://github.com/cppalliance/corosio
9 : //
10 :
11 : #ifndef BOOST_COROSIO_SOCKET_OPTION_HPP
12 : #define BOOST_COROSIO_SOCKET_OPTION_HPP
13 :
14 : #include <boost/corosio/detail/config.hpp>
15 : #include <boost/corosio/detail/except.hpp>
16 : #include <boost/corosio/family.hpp>
17 : #include <boost/corosio/ip_address.hpp>
18 : #include <boost/corosio/ipv4_address.hpp>
19 : #include <boost/corosio/ipv6_address.hpp>
20 :
21 : #include <cstddef>
22 :
23 : /** @file socket_option.hpp
24 :
25 : Type-erased socket option types that avoid platform-specific
26 : headers. The protocol level and option name for each type are
27 : resolved at link time via the compiled library.
28 :
29 : For an inline (zero-overhead) alternative that includes platform
30 : headers, use `<boost/corosio/native/native_socket_option.hpp>`
31 : (`boost::corosio::native_socket_option`).
32 :
33 : Both variants satisfy the same option-type interface and work
34 : interchangeably with `tcp_socket::set_option` /
35 : `tcp_socket::get_option` and the corresponding acceptor methods.
36 :
37 : @see native_socket_option
38 : */
39 :
40 : namespace boost::corosio::socket_option {
41 :
42 : /** Base class for concrete boolean socket options.
43 :
44 : Stores a boolean as an `int` suitable for `setsockopt`/`getsockopt`.
45 : Derived types provide `level()` and `name()` for the specific option.
46 : */
47 : class BOOST_COROSIO_DECL boolean_option
48 : {
49 : int value_ = 0;
50 :
51 : public:
52 : /// Construct with default value (disabled).
53 : boolean_option() = default;
54 :
55 : /** Construct with an explicit value.
56 :
57 : @param v `true` to enable the option, `false` to disable.
58 : */
59 HIT 670 : explicit boolean_option(bool v) noexcept : value_(v ? 1 : 0) {}
60 :
61 : /// Assign a new value.
62 4 : boolean_option& operator=(bool v) noexcept
63 : {
64 4 : value_ = v ? 1 : 0;
65 4 : return *this;
66 : }
67 :
68 : /// Return the option value.
69 60 : bool value() const noexcept
70 : {
71 60 : return value_ != 0;
72 : }
73 :
74 : /// Return the option value.
75 4 : explicit operator bool() const noexcept
76 : {
77 4 : return value_ != 0;
78 : }
79 :
80 : /// Return the negated option value.
81 4 : bool operator!() const noexcept
82 : {
83 4 : return value_ == 0;
84 : }
85 :
86 : /// Return a pointer to the underlying storage.
87 85 : void* data(family) noexcept
88 : {
89 85 : return &value_;
90 : }
91 :
92 : /// Return a pointer to the underlying storage.
93 662 : void const* data(family) const noexcept
94 : {
95 662 : return &value_;
96 : }
97 :
98 : /// Return the size of the underlying storage.
99 747 : std::size_t size(family) const noexcept
100 : {
101 747 : return sizeof(value_);
102 : }
103 :
104 : /** Normalize after `getsockopt` returns fewer bytes than expected.
105 :
106 : Windows Vista+ may write only 1 byte for boolean options.
107 :
108 : @param s The number of bytes actually written by `getsockopt`.
109 : */
110 64 : void resize(family, std::size_t s) noexcept
111 : {
112 64 : if (s == sizeof(char))
113 2 : value_ = *reinterpret_cast<unsigned char*>(&value_) ? 1 : 0;
114 64 : }
115 : };
116 :
117 : /** Base class for concrete integer socket options.
118 :
119 : Stores an integer suitable for `setsockopt`/`getsockopt`.
120 : Derived types provide `level()` and `name()` for the specific option.
121 : */
122 : class BOOST_COROSIO_DECL integer_option
123 : {
124 : int value_ = 0;
125 :
126 : public:
127 : /// Construct with default value (zero).
128 : integer_option() = default;
129 :
130 : /** Construct with an explicit value.
131 :
132 : @param v The option value.
133 : */
134 83 : explicit integer_option(int v) noexcept : value_(v) {}
135 :
136 : /// Assign a new value.
137 2 : integer_option& operator=(int v) noexcept
138 : {
139 2 : value_ = v;
140 2 : return *this;
141 : }
142 :
143 : /// Return the option value.
144 58 : int value() const noexcept
145 : {
146 58 : return value_;
147 : }
148 :
149 : /// Return a pointer to the underlying storage.
150 54 : void* data(family) noexcept
151 : {
152 54 : return &value_;
153 : }
154 :
155 : /// Return a pointer to the underlying storage.
156 77 : void const* data(family) const noexcept
157 : {
158 77 : return &value_;
159 : }
160 :
161 : /// Return the size of the underlying storage.
162 131 : std::size_t size(family) const noexcept
163 : {
164 131 : return sizeof(value_);
165 : }
166 :
167 : /** Normalize after `getsockopt` returns fewer bytes than expected.
168 :
169 : @param s The number of bytes actually written by `getsockopt`.
170 : */
171 56 : void resize(family, std::size_t s) noexcept
172 : {
173 56 : if (s == sizeof(char))
174 2 : value_ =
175 2 : static_cast<int>(*reinterpret_cast<unsigned char*>(&value_));
176 56 : }
177 : };
178 :
179 : /** Disable Nagle's algorithm (TCP_NODELAY).
180 :
181 : @par Example
182 : @par !example no_delay
183 : */
184 : class BOOST_COROSIO_DECL no_delay : public boolean_option
185 : {
186 : public:
187 : using boolean_option::boolean_option;
188 : using boolean_option::operator=;
189 :
190 : /// Return the protocol level.
191 : int level(family) const noexcept;
192 :
193 : /// Return the option name.
194 : int name(family) const noexcept;
195 : };
196 :
197 : /** Enable periodic keepalive probes (SO_KEEPALIVE).
198 :
199 : @par Example
200 : @par !example keep_alive
201 : */
202 : class BOOST_COROSIO_DECL keep_alive : public boolean_option
203 : {
204 : public:
205 : using boolean_option::boolean_option;
206 : using boolean_option::operator=;
207 :
208 : /// Return the protocol level.
209 : int level(family) const noexcept;
210 :
211 : /// Return the option name.
212 : int name(family) const noexcept;
213 : };
214 :
215 : /** Restrict an IPv6 socket to IPv6 only (IPV6_V6ONLY).
216 :
217 : When enabled, the socket only accepts IPv6 connections.
218 : When disabled, the socket accepts both IPv4 and IPv6
219 : connections (dual-stack mode).
220 :
221 : @par Example
222 : @par !example v6_only
223 : */
224 : class BOOST_COROSIO_DECL v6_only : public boolean_option
225 : {
226 : public:
227 : using boolean_option::boolean_option;
228 : using boolean_option::operator=;
229 :
230 : /// Return the protocol level.
231 : int level(family) const noexcept;
232 :
233 : /// Return the option name.
234 : int name(family) const noexcept;
235 : };
236 :
237 : /** Allow local address reuse (SO_REUSEADDR).
238 :
239 : @par Example
240 : @par !example reuse_address
241 : */
242 : class BOOST_COROSIO_DECL reuse_address : public boolean_option
243 : {
244 : public:
245 : using boolean_option::boolean_option;
246 : using boolean_option::operator=;
247 :
248 : /// Return the protocol level.
249 : int level(family) const noexcept;
250 :
251 : /// Return the option name.
252 : int name(family) const noexcept;
253 : };
254 :
255 : /** Allow sending to broadcast addresses (SO_BROADCAST).
256 :
257 : Required for UDP sockets that send to broadcast addresses
258 : such as 255.255.255.255. Without this option, `send_to`
259 : returns an error.
260 :
261 : @par Example
262 : @par !example broadcast
263 : */
264 : class BOOST_COROSIO_DECL broadcast : public boolean_option
265 : {
266 : public:
267 : using boolean_option::boolean_option;
268 : using boolean_option::operator=;
269 :
270 : /// Return the protocol level.
271 : int level(family) const noexcept;
272 :
273 : /// Return the option name.
274 : int name(family) const noexcept;
275 : };
276 :
277 : /** Allow multiple sockets to bind to the same port (SO_REUSEPORT).
278 :
279 : Not available on all platforms. On unsupported platforms,
280 : `set_option` throws `std::system_error`.
281 :
282 : @par Example
283 : @par !example reuse_port
284 : */
285 : class BOOST_COROSIO_DECL reuse_port : public boolean_option
286 : {
287 : public:
288 : using boolean_option::boolean_option;
289 : using boolean_option::operator=;
290 :
291 : /// Return the protocol level.
292 : int level(family) const noexcept;
293 :
294 : /// Return the option name.
295 : int name(family) const noexcept;
296 : };
297 :
298 : /** Set the receive buffer size (SO_RCVBUF).
299 :
300 : @par Example
301 : @par !example receive_buffer_size
302 : */
303 : class BOOST_COROSIO_DECL receive_buffer_size : public integer_option
304 : {
305 : public:
306 : using integer_option::integer_option;
307 : using integer_option::operator=;
308 :
309 : /// Return the protocol level.
310 : int level(family) const noexcept;
311 :
312 : /// Return the option name.
313 : int name(family) const noexcept;
314 : };
315 :
316 : /** Set the send buffer size (SO_SNDBUF).
317 :
318 : @par Example
319 : @par !example send_buffer_size
320 : */
321 : class BOOST_COROSIO_DECL send_buffer_size : public integer_option
322 : {
323 : public:
324 : using integer_option::integer_option;
325 : using integer_option::operator=;
326 :
327 : /// Return the protocol level.
328 : int level(family) const noexcept;
329 :
330 : /// Return the option name.
331 : int name(family) const noexcept;
332 : };
333 :
334 : /** The SO_LINGER socket option.
335 :
336 : Controls behavior when closing a socket with unsent data.
337 : When enabled, `close()` blocks until pending data is sent
338 : or the timeout expires.
339 :
340 : @par Example
341 : @par !example linger
342 : */
343 : class BOOST_COROSIO_DECL linger
344 : {
345 : // Opaque storage for the platform's struct linger.
346 : // POSIX: { int, int } = 8 bytes.
347 : // Windows: { u_short, u_short } = 4 bytes.
348 : static constexpr std::size_t max_storage_ = 8;
349 : alignas(4) unsigned char storage_[max_storage_]{};
350 :
351 : public:
352 : /// Construct with default values (disabled, zero timeout).
353 : linger() noexcept = default;
354 :
355 : /** Construct with explicit values.
356 :
357 : @param enabled `true` to enable linger behavior on close.
358 : @param timeout The linger timeout in seconds.
359 : */
360 : linger(bool enabled, int timeout) noexcept;
361 :
362 : /// Return whether linger is enabled.
363 : bool enabled() const noexcept;
364 :
365 : /// Set whether linger is enabled.
366 : void enabled(bool v) noexcept;
367 :
368 : /// Return the linger timeout in seconds.
369 : int timeout() const noexcept;
370 :
371 : /// Set the linger timeout in seconds.
372 : void timeout(int v) noexcept;
373 :
374 : /// Return the protocol level.
375 : int level(family) const noexcept;
376 :
377 : /// Return the option name.
378 : int name(family) const noexcept;
379 :
380 : /// Return a pointer to the underlying storage.
381 12 : void* data(family) noexcept
382 : {
383 12 : return storage_;
384 : }
385 :
386 : /// Return a pointer to the underlying storage.
387 203 : void const* data(family) const noexcept
388 : {
389 203 : return storage_;
390 : }
391 :
392 : /// Return the size of the underlying storage.
393 : std::size_t size(family) const noexcept;
394 :
395 : /** Normalize after `getsockopt`.
396 :
397 : No-op — `struct linger` is always returned at full size.
398 :
399 : @param s The number of bytes actually written by `getsockopt`.
400 : */
401 12 : void resize(family, std::size_t) noexcept {}
402 : };
403 :
404 : /** Enable loopback of outgoing multicast (IP_MULTICAST_LOOP /
405 : IPV6_MULTICAST_LOOP).
406 :
407 : The socket's family selects the wire rendering: a single byte
408 : at the IPv4 level (BSD-derived kernels reject the four-byte
409 : form), an `int` at the IPv6 level.
410 :
411 : @par Example
412 : @par !example multicast_loop
413 : */
414 : class BOOST_COROSIO_DECL multicast_loop
415 : {
416 : unsigned char byte_ = 0; // IPv4 rendering
417 : int int_ = 0; // IPv6 rendering
418 :
419 : public:
420 : /// Construct with default value (disabled).
421 : multicast_loop() = default;
422 :
423 : /** Construct with an explicit value.
424 :
425 : @param v `true` to enable loopback, `false` to disable.
426 : */
427 20 : explicit multicast_loop(bool v) noexcept : byte_(v ? 1 : 0), int_(v ? 1 : 0)
428 : {
429 20 : }
430 :
431 : /// Assign a new value.
432 : multicast_loop& operator=(bool v) noexcept
433 : {
434 : byte_ = v ? 1 : 0;
435 : int_ = v ? 1 : 0;
436 : return *this;
437 : }
438 :
439 : /// Return the option value.
440 16 : bool value() const noexcept
441 : {
442 16 : return int_ != 0;
443 : }
444 :
445 : /// Return the protocol level.
446 : int level(family) const noexcept;
447 :
448 : /// Return the option name.
449 : int name(family) const noexcept;
450 :
451 : /// Return a pointer to the rendering for `f`.
452 20 : void* data(family f) noexcept
453 : {
454 20 : return f == family::v6 ? static_cast<void*>(&int_)
455 20 : : static_cast<void*>(&byte_);
456 : }
457 :
458 : /// Return a pointer to the rendering for `f`.
459 18 : void const* data(family f) const noexcept
460 : {
461 18 : return f == family::v6 ? static_cast<void const*>(&int_)
462 18 : : static_cast<void const*>(&byte_);
463 : }
464 :
465 : /// Return the size of the rendering for `f`.
466 38 : std::size_t size(family f) const noexcept
467 : {
468 38 : return f == family::v6 ? sizeof(int_) : sizeof(byte_);
469 : }
470 :
471 : /** Synchronize both renderings after `getsockopt`.
472 :
473 : Only the rendering the socket's family selected was written;
474 : fold it into the other so `value()` answers from either.
475 :
476 : @param f The family `getsockopt` was performed for.
477 : */
478 16 : void resize(family f, std::size_t) noexcept
479 : {
480 16 : if (f == family::v6)
481 8 : byte_ = int_ ? 1 : 0;
482 : else
483 8 : int_ = byte_ ? 1 : 0;
484 16 : }
485 : };
486 :
487 : /** Set the multicast TTL / hop limit (IP_MULTICAST_TTL /
488 : IPV6_MULTICAST_HOPS).
489 :
490 : The socket's family selects the wire rendering: a single byte
491 : at the IPv4 level, an `int` at the IPv6 level.
492 :
493 : @par Example
494 : @par !example multicast_hops
495 : */
496 : class BOOST_COROSIO_DECL multicast_hops
497 : {
498 : unsigned char byte_ = 0; // IPv4 rendering
499 : int int_ = 0; // IPv6 rendering
500 :
501 : public:
502 : /// Construct with default value (zero).
503 : multicast_hops() = default;
504 :
505 : /** Construct with an explicit value.
506 :
507 : @param v The hop count, 0 to 255 — the range the IPv4 wire
508 : rendering can carry.
509 :
510 : @throws std::logic_error if `v` is outside [0, 255].
511 : */
512 14 : explicit multicast_hops(int v)
513 14 : {
514 14 : if (v < 0 || v > 255)
515 4 : detail::throw_logic_error("multicast hops value out of range");
516 10 : byte_ = static_cast<unsigned char>(v);
517 10 : int_ = v;
518 10 : }
519 :
520 : /** Assign a new value.
521 :
522 : @throws std::logic_error if `v` is outside [0, 255].
523 : */
524 : multicast_hops& operator=(int v)
525 : {
526 : if (v < 0 || v > 255)
527 : detail::throw_logic_error("multicast hops value out of range");
528 : byte_ = static_cast<unsigned char>(v);
529 : int_ = v;
530 : return *this;
531 : }
532 :
533 : /// Return the option value.
534 8 : int value() const noexcept
535 : {
536 8 : return int_;
537 : }
538 :
539 : /// Return the protocol level.
540 : int level(family) const noexcept;
541 :
542 : /// Return the option name.
543 : int name(family) const noexcept;
544 :
545 : /// Return a pointer to the rendering for `f`.
546 12 : void* data(family f) noexcept
547 : {
548 12 : return f == family::v6 ? static_cast<void*>(&int_)
549 12 : : static_cast<void*>(&byte_);
550 : }
551 :
552 : /// Return a pointer to the rendering for `f`.
553 8 : void const* data(family f) const noexcept
554 : {
555 8 : return f == family::v6 ? static_cast<void const*>(&int_)
556 8 : : static_cast<void const*>(&byte_);
557 : }
558 :
559 : /// Return the size of the rendering for `f`.
560 20 : std::size_t size(family f) const noexcept
561 : {
562 20 : return f == family::v6 ? sizeof(int_) : sizeof(byte_);
563 : }
564 :
565 : /** Synchronize both renderings after `getsockopt`.
566 :
567 : @param f The family `getsockopt` was performed for.
568 : */
569 8 : void resize(family f, std::size_t) noexcept
570 : {
571 8 : if (f == family::v6)
572 4 : byte_ = static_cast<unsigned char>(int_);
573 : else
574 4 : int_ = byte_;
575 8 : }
576 : };
577 :
578 : /** Join a multicast group (IP_ADD_MEMBERSHIP / IPV6_JOIN_GROUP).
579 :
580 : The group's family — not the socket's — selects the wire
581 : struct and protocol level: a v4 group renders as an `ip_mreq`
582 : at the IPv4 level even when applied to a dual-stack v6 socket,
583 : which is the level such a join actually targets.
584 :
585 : @par Example
586 : @par !example join_group
587 : */
588 : class BOOST_COROSIO_DECL join_group
589 : {
590 : // Opaque storage sized for the larger of ip_mreq / ipv6_mreq
591 : static constexpr std::size_t max_storage_ = 20;
592 : alignas(4) unsigned char storage_[max_storage_]{};
593 : family group_family_ = family::v4;
594 :
595 : public:
596 : /// Construct with default values.
597 : join_group() noexcept = default;
598 :
599 : /** Construct from a group address.
600 :
601 : The group's family selects the wire representation; the
602 : interface defaults to any (v4) or the group's zone (v6).
603 :
604 : @param group The multicast group address to join.
605 : */
606 : explicit join_group(ip_address const& group) noexcept;
607 :
608 : /** Construct from an IPv4 group and interface address.
609 :
610 : @param group The multicast group address to join.
611 : @param iface The local interface to use (default: any).
612 : */
613 : join_group(
614 : ipv4_address group, ipv4_address iface = ipv4_address()) noexcept;
615 :
616 : /** Construct from an IPv6 group and interface index.
617 :
618 : @param group The multicast group address to join.
619 : @param if_index The interface index; 0 uses the group's
620 : zone, and a zone of 0 lets the kernel choose.
621 : */
622 : join_group(ipv6_address const& group, unsigned int if_index = 0) noexcept;
623 :
624 : /// Return the protocol level for the group's family.
625 : int level(family) const noexcept;
626 :
627 : /// Return the option name for the group's family.
628 : int name(family) const noexcept;
629 :
630 : /// Return a pointer to the underlying storage.
631 14 : void const* data(family) const noexcept
632 : {
633 14 : return storage_;
634 : }
635 :
636 : /// Return the size of the wire struct for the group's family.
637 : std::size_t size(family) const noexcept;
638 :
639 : /// No-op resize.
640 : void resize(family, std::size_t) noexcept {}
641 : };
642 :
643 : /** Leave a multicast group (IP_DROP_MEMBERSHIP / IPV6_LEAVE_GROUP).
644 :
645 : The group's family — not the socket's — selects the wire
646 : struct and protocol level, mirroring @ref join_group.
647 :
648 : @par Example
649 : @par !example leave_group
650 : */
651 : class BOOST_COROSIO_DECL leave_group
652 : {
653 : static constexpr std::size_t max_storage_ = 20;
654 : alignas(4) unsigned char storage_[max_storage_]{};
655 : family group_family_ = family::v4;
656 :
657 : public:
658 : /// Construct with default values.
659 : leave_group() noexcept = default;
660 :
661 : /** Construct from a group address.
662 :
663 : @param group The multicast group address to leave.
664 : */
665 : explicit leave_group(ip_address const& group) noexcept;
666 :
667 : /** Construct from an IPv4 group and interface address.
668 :
669 : @param group The multicast group address to leave.
670 : @param iface The local interface (default: any).
671 : */
672 : leave_group(
673 : ipv4_address group, ipv4_address iface = ipv4_address()) noexcept;
674 :
675 : /** Construct from an IPv6 group and interface index.
676 :
677 : @param group The multicast group address to leave.
678 : @param if_index The interface index; 0 uses the group's
679 : zone, and a zone of 0 lets the kernel choose.
680 : */
681 : leave_group(ipv6_address const& group, unsigned int if_index = 0) noexcept;
682 :
683 : /// Return the protocol level for the group's family.
684 : int level(family) const noexcept;
685 :
686 : /// Return the option name for the group's family.
687 : int name(family) const noexcept;
688 :
689 : /// Return a pointer to the underlying storage.
690 12 : void const* data(family) const noexcept
691 : {
692 12 : return storage_;
693 : }
694 :
695 : /// Return the size of the wire struct for the group's family.
696 : std::size_t size(family) const noexcept;
697 :
698 : /// No-op resize.
699 : void resize(family, std::size_t) noexcept {}
700 : };
701 :
702 : /** Set the outgoing multicast interface (IP_MULTICAST_IF /
703 : IPV6_MULTICAST_IF).
704 :
705 : The two families name interfaces differently on the wire — IPv4
706 : by interface address, IPv6 by interface index — so the option
707 : stores both renderings and the socket's family selects one; the
708 : other stays at its default (any address, kernel-chosen index).
709 :
710 : @par Example
711 : @par !example multicast_interface
712 : */
713 : class BOOST_COROSIO_DECL multicast_interface
714 : {
715 : alignas(4) unsigned char v4_storage_[4]{};
716 : unsigned int if_index_ = 0;
717 :
718 : public:
719 : /// Construct with default values (any address, kernel-chosen index).
720 : multicast_interface() noexcept = default;
721 :
722 : /** Construct with an IPv4 interface address.
723 :
724 : @param iface The local interface address.
725 : */
726 : explicit multicast_interface(ipv4_address iface) noexcept;
727 :
728 : /** Construct with an IPv6 interface index.
729 :
730 : @param if_index The interface index (0 = kernel chooses).
731 : */
732 4 : explicit multicast_interface(unsigned int if_index) noexcept
733 4 : : if_index_(if_index)
734 : {
735 4 : }
736 :
737 : /// Return the IPv4 rendering as an address.
738 : ipv4_address address() const noexcept;
739 :
740 : /// Return the IPv6 rendering as an interface index.
741 6 : unsigned int if_index() const noexcept
742 : {
743 6 : return if_index_;
744 : }
745 :
746 : /// Return the protocol level.
747 : int level(family) const noexcept;
748 :
749 : /// Return the option name.
750 : int name(family) const noexcept;
751 :
752 : /// Return a pointer to the rendering for `f`.
753 4 : void* data(family f) noexcept
754 : {
755 4 : return f == family::v6 ? static_cast<void*>(&if_index_)
756 4 : : static_cast<void*>(v4_storage_);
757 : }
758 :
759 : /// Return a pointer to the rendering for `f`.
760 4 : void const* data(family f) const noexcept
761 : {
762 4 : return f == family::v6 ? static_cast<void const*>(&if_index_)
763 4 : : static_cast<void const*>(v4_storage_);
764 : }
765 :
766 : /// Return the size of the rendering for `f`.
767 : std::size_t size(family) const noexcept;
768 :
769 : /// No-op resize.
770 2 : void resize(family, std::size_t) noexcept {}
771 : };
772 :
773 : } // namespace boost::corosio::socket_option
774 :
775 : #endif // BOOST_COROSIO_SOCKET_OPTION_HPP
|