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