@@ -75,6 +75,7 @@ struct Config {
7575 reuse_address : bool ,
7676 send_buffer_size : Option < usize > ,
7777 recv_buffer_size : Option < usize > ,
78+ interface : Option < String > ,
7879}
7980
8081#[ derive( Default , Debug , Clone , Copy ) ]
@@ -165,6 +166,7 @@ impl<R> HttpConnector<R> {
165166 reuse_address : false ,
166167 send_buffer_size : None ,
167168 recv_buffer_size : None ,
169+ interface : None ,
168170 } ) ,
169171 resolver,
170172 }
@@ -288,6 +290,25 @@ impl<R> HttpConnector<R> {
288290 self
289291 }
290292
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+
291312 // private
292313
293314 fn config_mut ( & mut self ) -> & mut Config {
@@ -673,6 +694,14 @@ fn connect(
673694 }
674695 }
675696
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+
676705 bind_local_address (
677706 & socket,
678707 addr,
@@ -828,6 +857,14 @@ mod tests {
828857 ( ip_v4, ip_v6)
829858 }
830859
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+
831868 #[ tokio:: test]
832869 #[ cfg_attr( miri, ignore) ]
833870 async fn test_errors_missing_scheme ( ) {
@@ -877,6 +914,57 @@ mod tests {
877914 }
878915 }
879916
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+
880968 #[ test]
881969 #[ cfg_attr( not( feature = "__internal_happy_eyeballs_tests" ) , ignore) ]
882970 fn client_happy_eyeballs ( ) {
@@ -1005,6 +1093,7 @@ mod tests {
10051093 enforce_http : false ,
10061094 send_buffer_size : None ,
10071095 recv_buffer_size : None ,
1096+ interface : None ,
10081097 } ;
10091098 let connecting_tcp = ConnectingTcp :: new ( dns:: SocketAddrs :: new ( addrs) , & cfg) ;
10101099 let start = Instant :: now ( ) ;
0 commit comments