@@ -4,7 +4,7 @@ use std::{
44 future:: Future ,
55 io,
66 marker:: PhantomData ,
7- net:: { Ipv4Addr , Ipv6Addr , SocketAddr } ,
7+ net:: { IpAddr , Ipv4Addr , Ipv6Addr , SocketAddr } ,
88 pin:: Pin ,
99 sync:: Arc ,
1010 task:: { self , Poll , ready} ,
@@ -24,7 +24,7 @@ use super::{
2424 Connected , Connection ,
2525 dns:: { self , GaiResolver , Resolve , resolve} ,
2626} ;
27- use crate :: core:: { client :: connect :: options :: TcpConnectOptions , error:: BoxError , rt:: TokioIo } ;
27+ use crate :: core:: { error:: BoxError , rt:: TokioIo } ;
2828
2929/// A connector for the `http` scheme.
3030///
@@ -66,6 +66,143 @@ pub struct HttpInfo {
6666 local_addr : SocketAddr ,
6767}
6868
69+ /// Options for configuring a TCP network connection.
70+ ///
71+ /// `TcpConnectOptions` allows fine-grained control over how TCP sockets
72+ /// are created and connected. It can be used to:
73+ ///
74+ /// - Bind a socket to a specific **network interface**
75+ /// - Bind to a **local IPv4 or IPv6 address**
76+ ///
77+ /// This is especially useful for scenarios involving:
78+ /// - Virtual routing tables (e.g. Linux VRFs)
79+ /// - Multiple NICs (network interface cards)
80+ /// - Explicit source IP routing or firewall rules
81+ ///
82+ /// Platform-specific behavior is handled internally, with the interface binding
83+ /// mechanism differing across Unix-like systems.
84+ ///
85+ /// # Platform Notes
86+ ///
87+ /// ## Interface binding (`set_interface`)
88+ ///
89+ /// - **Linux / Android / Fuchsia**: uses the `SO_BINDTODEVICE` socket option See [`man 7 socket`](https://man7.org/linux/man-pages/man7/socket.7.html)
90+ ///
91+ /// - **macOS / iOS / tvOS / watchOS / visionOS / illumos / Solaris**: uses the `IP_BOUND_IF` socket
92+ /// option See [`man 7p ip`](https://docs.oracle.com/cd/E86824_01/html/E54777/ip-7p.html)
93+ ///
94+ /// Binding to an interface ensures that:
95+ /// - **Outgoing packets** are sent through the specified interface
96+ /// - **Incoming packets** are only accepted if received via that interface
97+ ///
98+ /// ❗ This only applies to certain socket types (e.g. `AF_INET`), and may require
99+ /// elevated permissions (e.g. `CAP_NET_RAW` on Linux).
100+ #[ derive( Debug , Clone , Hash , PartialEq , Eq , Default ) ]
101+ pub struct TcpConnectOptions {
102+ #[ cfg( any( target_os = "android" , target_os = "fuchsia" , target_os = "linux" ) ) ]
103+ pub ( super ) interface : Option < std:: borrow:: Cow < ' static , str > > ,
104+ #[ cfg( any(
105+ target_os = "illumos" ,
106+ target_os = "ios" ,
107+ target_os = "macos" ,
108+ target_os = "solaris" ,
109+ target_os = "tvos" ,
110+ target_os = "visionos" ,
111+ target_os = "watchos" ,
112+ ) ) ]
113+ pub ( super ) interface : Option < std:: ffi:: CString > ,
114+ pub ( super ) local_ipv4 : Option < Ipv4Addr > ,
115+ pub ( super ) local_ipv6 : Option < Ipv6Addr > ,
116+ }
117+
118+ impl TcpConnectOptions {
119+ /// Sets the name of the network interface to bind the socket to.
120+ ///
121+ /// ## Platform behavior
122+ /// - On Linux/Fuchsia/Android: sets `SO_BINDTODEVICE`
123+ /// - On macOS/illumos/Solaris/iOS/etc.: sets `IP_BOUND_IF`
124+ ///
125+ /// If `interface` is `None`, the socket will not be explicitly bound to any device.
126+ ///
127+ /// # Errors
128+ ///
129+ /// On platforms that require a `CString` (e.g. macOS), this will return an error if the
130+ /// interface name contains an internal null byte (`\0`), which is invalid in C strings.
131+ ///
132+ /// # See Also
133+ /// - [VRF documentation](https://www.kernel.org/doc/Documentation/networking/vrf.txt)
134+ /// - [`man 7 socket`](https://man7.org/linux/man-pages/man7/socket.7.html)
135+ /// - [`man 7p ip`](https://docs.oracle.com/cd/E86824_01/html/E54777/ip-7p.html)
136+ #[ cfg( any(
137+ target_os = "android" ,
138+ target_os = "fuchsia" ,
139+ target_os = "illumos" ,
140+ target_os = "ios" ,
141+ target_os = "linux" ,
142+ target_os = "macos" ,
143+ target_os = "solaris" ,
144+ target_os = "tvos" ,
145+ target_os = "visionos" ,
146+ target_os = "watchos" ,
147+ ) ) ]
148+ #[ inline]
149+ pub fn set_interface < S > ( & mut self , interface : S ) -> & mut Self
150+ where
151+ S : Into < Option < std:: borrow:: Cow < ' static , str > > > ,
152+ {
153+ #[ cfg( any( target_os = "android" , target_os = "fuchsia" , target_os = "linux" ) ) ]
154+ {
155+ self . interface = interface. into ( ) ;
156+ }
157+
158+ #[ cfg( not( any( target_os = "android" , target_os = "fuchsia" , target_os = "linux" ) ) ) ]
159+ {
160+ self . interface = interface
161+ . into ( )
162+ . and_then ( |iface| std:: ffi:: CString :: new ( iface. into_owned ( ) ) . ok ( ) ) ;
163+ }
164+
165+ self
166+ }
167+
168+ /// Sets the local address the socket will bind to before connecting.
169+ ///
170+ /// If an address is provided, the socket will explicitly bind to it,
171+ /// ensuring that the outgoing connection uses this address as the source.
172+ ///
173+ /// - If an `Ipv4Addr` is given, it will set `local_ipv4` and clear `local_ipv6`.
174+ /// - If an `Ipv6Addr` is given, it will set `local_ipv6` and clear `local_ipv4`.
175+ ///
176+ /// If `None` is passed, both addresses are cleared and the OS will choose automatically.
177+ #[ inline]
178+ pub fn set_local_address ( & mut self , local_addr : Option < IpAddr > ) {
179+ match local_addr {
180+ Some ( IpAddr :: V4 ( a) ) => {
181+ self . local_ipv4 = Some ( a) ;
182+ }
183+ Some ( IpAddr :: V6 ( a) ) => {
184+ self . local_ipv6 = Some ( a) ;
185+ }
186+ _ => { }
187+ } ;
188+ }
189+
190+ /// Sets both local IPv4 and IPv6 addresses explicitly.
191+ ///
192+ /// Use this method to assign both address families independently.
193+ ///
194+ /// If either argument is `None`, the socket will not be bound for that protocol.
195+ #[ inline]
196+ pub fn set_local_addresses (
197+ & mut self ,
198+ local_ipv4 : Option < Ipv4Addr > ,
199+ local_ipv6 : Option < Ipv6Addr > ,
200+ ) {
201+ self . local_ipv4 = local_ipv4;
202+ self . local_ipv6 = local_ipv6;
203+ }
204+ }
205+
69206#[ derive( Clone ) ]
70207struct Config {
71208 connect_timeout : Option < Duration > ,
@@ -310,7 +447,6 @@ impl<R> HttpConnector<R> {
310447 /// Default is 300 milliseconds.
311448 ///
312449 /// [RFC 6555]: https://tools.ietf.org/html/rfc6555
313- #[ allow( unused) ]
314450 #[ inline]
315451 pub fn set_happy_eyeballs_timeout ( & mut self , dur : Option < Duration > ) {
316452 self . config_mut ( ) . happy_eyeballs_timeout = dur;
0 commit comments