@@ -78,6 +78,16 @@ struct Config {
7878 recv_buffer_size : Option < usize > ,
7979 #[ cfg( any( target_os = "android" , target_os = "fuchsia" , target_os = "linux" ) ) ]
8080 interface : Option < String > ,
81+ #[ cfg( any(
82+ target_os = "illumos" ,
83+ target_os = "ios" ,
84+ target_os = "macos" ,
85+ target_os = "solaris" ,
86+ target_os = "tvos" ,
87+ target_os = "visionos" ,
88+ target_os = "watchos" ,
89+ ) ) ]
90+ interface : Option < std:: ffi:: CString > ,
8191 #[ cfg( any( target_os = "android" , target_os = "fuchsia" , target_os = "linux" ) ) ]
8292 tcp_user_timeout : Option < Duration > ,
8393}
@@ -226,7 +236,18 @@ impl<R> HttpConnector<R> {
226236 reuse_address : false ,
227237 send_buffer_size : None ,
228238 recv_buffer_size : None ,
229- #[ cfg( any( target_os = "android" , target_os = "fuchsia" , target_os = "linux" ) ) ]
239+ #[ cfg( any(
240+ target_os = "android" ,
241+ target_os = "fuchsia" ,
242+ target_os = "illumos" ,
243+ target_os = "ios" ,
244+ target_os = "linux" ,
245+ target_os = "macos" ,
246+ target_os = "solaris" ,
247+ target_os = "tvos" ,
248+ target_os = "visionos" ,
249+ target_os = "watchos" ,
250+ ) ) ]
230251 interface : None ,
231252 #[ cfg( any( target_os = "android" , target_os = "fuchsia" , target_os = "linux" ) ) ]
232253 tcp_user_timeout : None ,
@@ -353,22 +374,55 @@ impl<R> HttpConnector<R> {
353374 self
354375 }
355376
356- /// Sets the value for the `SO_BINDTODEVICE` option on this socket.
377+ /// Sets the name of the interface to bind sockets produced by this
378+ /// connector.
379+ ///
380+ /// On Linux, this sets the `SO_BINDTODEVICE` option on this socket (see
381+ /// [`man 7 socket`] for details). On macOS (and macOS-derived systems like
382+ /// iOS), illumos, and Solaris, this will instead use the `IP_BOUND_IF`
383+ /// socket option (see [`man 7p ip`]).
357384 ///
358385 /// If a socket is bound to an interface, only packets received from that particular
359386 /// interface are processed by the socket. Note that this only works for some socket
360- /// types, particularly AF_INET sockets.
387+ /// types, particularly ` AF_INET`` sockets.
361388 ///
362389 /// On Linux it can be used to specify a [VRF], but the binary needs
363390 /// to either have `CAP_NET_RAW` or to be run as root.
364391 ///
365- /// This function is only available on Android、Fuchsia and Linux.
392+ /// This function is only available on the following operating systems:
393+ /// - Linux, including Android
394+ /// - Fuchsia
395+ /// - illumos and Solaris
396+ /// - macOS, iOS, visionOS, watchOS, and tvOS
366397 ///
367398 /// [VRF]: https://www.kernel.org/doc/Documentation/networking/vrf.txt
368- #[ cfg( any( target_os = "android" , target_os = "fuchsia" , target_os = "linux" ) ) ]
399+ /// [`man 7 socket`] https://man7.org/linux/man-pages/man7/socket.7.html
400+ /// [`man 7p ip`]: https://docs.oracle.com/cd/E86824_01/html/E54777/ip-7p.html
401+ #[ cfg( any(
402+ target_os = "android" ,
403+ target_os = "fuchsia" ,
404+ target_os = "illumos" ,
405+ target_os = "ios" ,
406+ target_os = "linux" ,
407+ target_os = "macos" ,
408+ target_os = "solaris" ,
409+ target_os = "tvos" ,
410+ target_os = "visionos" ,
411+ target_os = "watchos" ,
412+ ) ) ]
369413 #[ inline]
370414 pub fn set_interface < S : Into < String > > ( & mut self , interface : S ) -> & mut Self {
371- self . config_mut ( ) . interface = Some ( interface. into ( ) ) ;
415+ let interface = interface. into ( ) ;
416+ #[ cfg( any( target_os = "android" , target_os = "fuchsia" , target_os = "linux" ) ) ]
417+ {
418+ self . config_mut ( ) . interface = Some ( interface) ;
419+ }
420+ #[ cfg( not( any( target_os = "android" , target_os = "fuchsia" , target_os = "linux" ) ) ) ]
421+ {
422+ let interface = std:: ffi:: CString :: new ( interface)
423+ . expect ( "interface name should not have nulls in it" ) ;
424+ self . config_mut ( ) . interface = Some ( interface) ;
425+ }
372426 self
373427 }
374428
@@ -789,12 +843,57 @@ fn connect(
789843 }
790844 }
791845
792- #[ cfg( any( target_os = "android" , target_os = "fuchsia" , target_os = "linux" ) ) ]
793846 // That this only works for some socket types, particularly AF_INET sockets.
847+ #[ cfg( any(
848+ target_os = "android" ,
849+ target_os = "fuchsia" ,
850+ target_os = "illumos" ,
851+ target_os = "ios" ,
852+ target_os = "linux" ,
853+ target_os = "macos" ,
854+ target_os = "solaris" ,
855+ target_os = "tvos" ,
856+ target_os = "visionos" ,
857+ target_os = "watchos" ,
858+ ) ) ]
794859 if let Some ( interface) = & config. interface {
860+ // On Linux-like systems, set the interface to bind using
861+ // `SO_BINDTODEVICE`.
862+ #[ cfg( any( target_os = "android" , target_os = "fuchsia" , target_os = "linux" ) ) ]
795863 socket
796864 . bind_device ( Some ( interface. as_bytes ( ) ) )
797865 . map_err ( ConnectError :: m ( "tcp bind interface error" ) ) ?;
866+
867+ // On macOS-like and Solaris-like systems, we instead use `IP_BOUND_IF`.
868+ // This socket option desires an integer index for the interface, so we
869+ // must first determine the index of the requested interface name using
870+ // `if_nametoindex`.
871+ #[ cfg( any(
872+ target_os = "illumos" ,
873+ target_os = "ios" ,
874+ target_os = "macos" ,
875+ target_os = "solaris" ,
876+ target_os = "tvos" ,
877+ target_os = "visionos" ,
878+ target_os = "watchos" ,
879+ ) ) ]
880+ {
881+ let idx = unsafe { libc:: if_nametoindex ( interface. as_ptr ( ) ) } ;
882+ let idx = std:: num:: NonZeroU32 :: new ( idx) . ok_or_else ( || {
883+ // If the index is 0, check errno and return an I/O error.
884+ ConnectError :: new (
885+ "error converting interface name to index" ,
886+ io:: Error :: last_os_error ( ) ,
887+ )
888+ } ) ?;
889+ // Different setsockopt calls are necessary depending on whether the
890+ // address is IPv4 or IPv6.
891+ match addr {
892+ SocketAddr :: V4 ( _) => socket. bind_device_by_index_v4 ( Some ( idx) ) ,
893+ SocketAddr :: V6 ( _) => socket. bind_device_by_index_v6 ( Some ( idx) ) ,
894+ }
895+ . map_err ( ConnectError :: m ( "tcp bind interface error" ) ) ?;
896+ }
798897 }
799898
800899 #[ cfg( any( target_os = "android" , target_os = "fuchsia" , target_os = "linux" ) ) ]
@@ -1214,6 +1313,16 @@ mod tests {
12141313 target_os = "linux"
12151314 ) ) ]
12161315 interface : None ,
1316+ #[ cfg( any(
1317+ target_os = "illumos" ,
1318+ target_os = "ios" ,
1319+ target_os = "macos" ,
1320+ target_os = "solaris" ,
1321+ target_os = "tvos" ,
1322+ target_os = "visionos" ,
1323+ target_os = "watchos" ,
1324+ ) ) ]
1325+ interface : None ,
12171326 #[ cfg( any(
12181327 target_os = "android" ,
12191328 target_os = "fuchsia" ,
0 commit comments