@@ -75,6 +75,7 @@ struct Config {
75
75
reuse_address : bool ,
76
76
send_buffer_size : Option < usize > ,
77
77
recv_buffer_size : Option < usize > ,
78
+ interface : Option < String > ,
78
79
}
79
80
80
81
#[ derive( Default , Debug , Clone , Copy ) ]
@@ -165,6 +166,7 @@ impl<R> HttpConnector<R> {
165
166
reuse_address : false ,
166
167
send_buffer_size : None ,
167
168
recv_buffer_size : None ,
169
+ interface : None ,
168
170
} ) ,
169
171
resolver,
170
172
}
@@ -288,6 +290,25 @@ impl<R> HttpConnector<R> {
288
290
self
289
291
}
290
292
293
+ /// Sets the value for the `SO_BINDTODEVICE` option on this socket.
294
+ ///
295
+ /// If a socket is bound to an interface, only packets received from that particular
296
+ /// interface are processed by the socket. Note that this only works for some socket
297
+ /// types, particularly AF_INET sockets.
298
+ ///
299
+ /// On Linux it can be used to specify a [VRF], but the binary needs
300
+ /// to either have `CAP_NET_RAW` or to be run as root.
301
+ ///
302
+ /// This function is only available on Android、Fuchsia and Linux.
303
+ ///
304
+ /// [VRF]: https://www.kernel.org/doc/Documentation/networking/vrf.txt
305
+ #[ cfg( any( target_os = "android" , target_os = "fuchsia" , target_os = "linux" ) ) ]
306
+ #[ inline]
307
+ pub fn set_interface < S : Into < String > > ( & mut self , interface : S ) -> & mut Self {
308
+ self . config_mut ( ) . interface = Some ( interface. into ( ) ) ;
309
+ self
310
+ }
311
+
291
312
// private
292
313
293
314
fn config_mut ( & mut self ) -> & mut Config {
@@ -673,6 +694,14 @@ fn connect(
673
694
}
674
695
}
675
696
697
+ #[ cfg( any( target_os = "android" , target_os = "fuchsia" , target_os = "linux" ) ) ]
698
+ // That this only works for some socket types, particularly AF_INET sockets.
699
+ if let Some ( interface) = & config. interface {
700
+ socket
701
+ . bind_device ( Some ( interface. as_bytes ( ) ) )
702
+ . map_err ( ConnectError :: m ( "tcp bind interface error" ) ) ?;
703
+ }
704
+
676
705
bind_local_address (
677
706
& socket,
678
707
addr,
@@ -828,6 +857,14 @@ mod tests {
828
857
( ip_v4, ip_v6)
829
858
}
830
859
860
+ #[ cfg( any( target_os = "android" , target_os = "fuchsia" , target_os = "linux" ) ) ]
861
+ fn default_interface ( ) -> Option < String > {
862
+ pnet_datalink:: interfaces ( )
863
+ . iter ( )
864
+ . find ( |e| e. is_up ( ) && !e. is_loopback ( ) && !e. ips . is_empty ( ) )
865
+ . map ( |e| e. name . clone ( ) )
866
+ }
867
+
831
868
#[ tokio:: test]
832
869
#[ cfg_attr( miri, ignore) ]
833
870
async fn test_errors_missing_scheme ( ) {
@@ -877,6 +914,57 @@ mod tests {
877
914
}
878
915
}
879
916
917
+ // NOTE: pnet crate that we use in this test doesn't compile on Windows
918
+ #[ cfg( any( target_os = "android" , target_os = "fuchsia" , target_os = "linux" ) ) ]
919
+ #[ tokio:: test]
920
+ #[ ignore = "setting `SO_BINDTODEVICE` requires the `CAP_NET_RAW` capability (works when running as root)" ]
921
+ async fn interface ( ) {
922
+ use socket2:: { Domain , Protocol , Socket , Type } ;
923
+ use std:: net:: TcpListener ;
924
+
925
+ let interface: Option < String > = default_interface ( ) ;
926
+
927
+ let server4 = TcpListener :: bind ( "127.0.0.1:0" ) . unwrap ( ) ;
928
+ let port = server4. local_addr ( ) . unwrap ( ) . port ( ) ;
929
+
930
+ let server6 = TcpListener :: bind ( & format ! ( "[::1]:{}" , port) ) . unwrap ( ) ;
931
+
932
+ let assert_interface_name =
933
+ |dst : String ,
934
+ server : TcpListener ,
935
+ bind_iface : Option < String > ,
936
+ expected_interface : Option < String > | async move {
937
+ let mut connector = HttpConnector :: new ( ) ;
938
+ if let Some ( iface) = bind_iface {
939
+ connector. set_interface ( iface) ;
940
+ }
941
+
942
+ connect ( connector, dst. parse ( ) . unwrap ( ) ) . await . unwrap ( ) ;
943
+ let domain = Domain :: for_address ( server. local_addr ( ) . unwrap ( ) ) ;
944
+ let socket = Socket :: new ( domain, Type :: STREAM , Some ( Protocol :: TCP ) ) . unwrap ( ) ;
945
+
946
+ assert_eq ! (
947
+ socket. device( ) . unwrap( ) . as_deref( ) ,
948
+ expected_interface. as_deref( ) . map( |val| val. as_bytes( ) )
949
+ ) ;
950
+ } ;
951
+
952
+ assert_interface_name (
953
+ format ! ( "http://127.0.0.1:{}" , port) ,
954
+ server4,
955
+ interface. clone ( ) ,
956
+ interface. clone ( ) ,
957
+ )
958
+ . await ;
959
+ assert_interface_name (
960
+ format ! ( "http://[::1]:{}" , port) ,
961
+ server6,
962
+ interface. clone ( ) ,
963
+ interface. clone ( ) ,
964
+ )
965
+ . await ;
966
+ }
967
+
880
968
#[ test]
881
969
#[ cfg_attr( not( feature = "__internal_happy_eyeballs_tests" ) , ignore) ]
882
970
fn client_happy_eyeballs ( ) {
@@ -1005,6 +1093,7 @@ mod tests {
1005
1093
enforce_http : false ,
1006
1094
send_buffer_size : None ,
1007
1095
recv_buffer_size : None ,
1096
+ interface : None ,
1008
1097
} ;
1009
1098
let connecting_tcp = ConnectingTcp :: new ( dns:: SocketAddrs :: new ( addrs) , & cfg) ;
1010
1099
let start = Instant :: now ( ) ;
0 commit comments