@@ -78,6 +78,16 @@ struct Config {
78
78
recv_buffer_size : Option < usize > ,
79
79
#[ cfg( any( target_os = "android" , target_os = "fuchsia" , target_os = "linux" ) ) ]
80
80
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 > ,
81
91
#[ cfg( any( target_os = "android" , target_os = "fuchsia" , target_os = "linux" ) ) ]
82
92
tcp_user_timeout : Option < Duration > ,
83
93
}
@@ -226,7 +236,18 @@ impl<R> HttpConnector<R> {
226
236
reuse_address : false ,
227
237
send_buffer_size : None ,
228
238
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
+ ) ) ]
230
251
interface : None ,
231
252
#[ cfg( any( target_os = "android" , target_os = "fuchsia" , target_os = "linux" ) ) ]
232
253
tcp_user_timeout : None ,
@@ -353,22 +374,55 @@ impl<R> HttpConnector<R> {
353
374
self
354
375
}
355
376
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`]).
357
384
///
358
385
/// If a socket is bound to an interface, only packets received from that particular
359
386
/// 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.
361
388
///
362
389
/// On Linux it can be used to specify a [VRF], but the binary needs
363
390
/// to either have `CAP_NET_RAW` or to be run as root.
364
391
///
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
366
397
///
367
398
/// [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
+ ) ) ]
369
413
#[ inline]
370
414
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
+ }
372
426
self
373
427
}
374
428
@@ -789,12 +843,57 @@ fn connect(
789
843
}
790
844
}
791
845
792
- #[ cfg( any( target_os = "android" , target_os = "fuchsia" , target_os = "linux" ) ) ]
793
846
// 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
+ ) ) ]
794
859
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" ) ) ]
795
863
socket
796
864
. bind_device ( Some ( interface. as_bytes ( ) ) )
797
865
. 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
+ }
798
897
}
799
898
800
899
#[ cfg( any( target_os = "android" , target_os = "fuchsia" , target_os = "linux" ) ) ]
@@ -1214,6 +1313,16 @@ mod tests {
1214
1313
target_os = "linux"
1215
1314
) ) ]
1216
1315
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 ,
1217
1326
#[ cfg( any(
1218
1327
target_os = "android" ,
1219
1328
target_os = "fuchsia" ,
0 commit comments