Skip to content

Commit 114d97c

Browse files
authored
ClientBuilder::interface on macOS/Solarish OSes (#2623)
The `ClientBuilder` type has an `interface` method that sets the name of a specific network interface to bind TCP connections on. This method is currently only enabled on Linux, Android, and Fuchsia, as it uses the `SO_BINDTODEVICE` socket option, which only exists on those systems. Upstream changes to `socket2` (rust-lang/socket2#561) and `hyper-util` (hyperium/hyper-util#176) have added support for similar functionality on macOS (and other Apple operating systems, such as iOS, watchOS, tvOS, and visionOS), and Solaris and Solaris-like systems like illumos, using the `IP_BOUND_IF`/`IPV6_BOUND_IF` socket options on those systems. This branch enables the `ClientBuilder::interface` API on these additional target OSes. Fixes #2571
1 parent 2fd902e commit 114d97c

File tree

3 files changed

+201
-18
lines changed

3 files changed

+201
-18
lines changed

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ encoding_rs = { version = "0.8", optional = true }
119119
http-body = "1"
120120
http-body-util = "0.1"
121121
hyper = { version = "1.1", features = ["http1", "client"] }
122-
hyper-util = { version = "0.1.10", features = ["http1", "client", "client-legacy", "tokio"] }
122+
hyper-util = { version = "0.1.11", features = ["http1", "client", "client-legacy", "tokio"] }
123123
h2 = { version = "0.4", optional = true }
124124
once_cell = "1.18"
125125
log = "0.4.17"

src/async_impl/client.rs

Lines changed: 104 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,18 @@ struct Config {
158158
#[cfg(feature = "http2")]
159159
http2_keep_alive_while_idle: bool,
160160
local_address: Option<IpAddr>,
161-
#[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))]
161+
#[cfg(any(
162+
target_os = "android",
163+
target_os = "fuchsia",
164+
target_os = "illumos",
165+
target_os = "ios",
166+
target_os = "linux",
167+
target_os = "macos",
168+
target_os = "solaris",
169+
target_os = "tvos",
170+
target_os = "visionos",
171+
target_os = "watchos",
172+
))]
162173
interface: Option<String>,
163174
nodelay: bool,
164175
#[cfg(feature = "cookies")]
@@ -266,7 +277,18 @@ impl ClientBuilder {
266277
#[cfg(feature = "http2")]
267278
http2_keep_alive_while_idle: false,
268279
local_address: None,
269-
#[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))]
280+
#[cfg(any(
281+
target_os = "android",
282+
target_os = "fuchsia",
283+
target_os = "illumos",
284+
target_os = "ios",
285+
target_os = "linux",
286+
target_os = "macos",
287+
target_os = "solaris",
288+
target_os = "tvos",
289+
target_os = "visionos",
290+
target_os = "watchos",
291+
))]
270292
interface: None,
271293
nodelay: true,
272294
hickory_dns: cfg!(feature = "hickory-dns"),
@@ -483,7 +505,14 @@ impl ClientBuilder {
483505
#[cfg(any(
484506
target_os = "android",
485507
target_os = "fuchsia",
486-
target_os = "linux"
508+
target_os = "illumos",
509+
target_os = "ios",
510+
target_os = "linux",
511+
target_os = "macos",
512+
target_os = "solaris",
513+
target_os = "tvos",
514+
target_os = "visionos",
515+
target_os = "watchos",
487516
))]
488517
config.interface.as_deref(),
489518
config.nodelay,
@@ -529,7 +558,14 @@ impl ClientBuilder {
529558
#[cfg(any(
530559
target_os = "android",
531560
target_os = "fuchsia",
532-
target_os = "linux"
561+
target_os = "illumos",
562+
target_os = "ios",
563+
target_os = "linux",
564+
target_os = "macos",
565+
target_os = "solaris",
566+
target_os = "tvos",
567+
target_os = "visionos",
568+
target_os = "watchos",
533569
))]
534570
config.interface.as_deref(),
535571
config.nodelay,
@@ -726,7 +762,14 @@ impl ClientBuilder {
726762
#[cfg(any(
727763
target_os = "android",
728764
target_os = "fuchsia",
729-
target_os = "linux"
765+
target_os = "illumos",
766+
target_os = "ios",
767+
target_os = "linux",
768+
target_os = "macos",
769+
target_os = "solaris",
770+
target_os = "tvos",
771+
target_os = "visionos",
772+
target_os = "watchos",
730773
))]
731774
config.interface.as_deref(),
732775
config.nodelay,
@@ -746,7 +789,18 @@ impl ClientBuilder {
746789
http,
747790
proxies.clone(),
748791
config.local_address,
749-
#[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))]
792+
#[cfg(any(
793+
target_os = "android",
794+
target_os = "fuchsia",
795+
target_os = "illumos",
796+
target_os = "ios",
797+
target_os = "linux",
798+
target_os = "macos",
799+
target_os = "solaris",
800+
target_os = "tvos",
801+
target_os = "visionos",
802+
target_os = "watchos",
803+
))]
750804
config.interface.as_deref(),
751805
config.nodelay,
752806
)
@@ -1428,7 +1482,23 @@ impl ClientBuilder {
14281482
self
14291483
}
14301484

1431-
/// Bind to an interface by `SO_BINDTODEVICE`.
1485+
/// Bind connections only on the specified network interface.
1486+
///
1487+
/// This option is only available on the following operating systems:
1488+
///
1489+
/// - Android
1490+
/// - Fuchsia
1491+
/// - Linux,
1492+
/// - macOS and macOS-like systems (iOS, tvOS, watchOS and visionOS)
1493+
/// - Solaris and illumos
1494+
///
1495+
/// On Android, Linux, and Fuchsia, this uses the
1496+
/// [`SO_BINDTODEVICE`][man-7-socket] socket option. On macOS and macOS-like
1497+
/// systems, Solaris, and illumos, this instead uses the [`IP_BOUND_IF` and
1498+
/// `IPV6_BOUND_IF`][man-7p-ip] socket options (as appropriate).
1499+
///
1500+
/// Note that connections will fail if the provided interface name is not a
1501+
/// network interface that currently exists when a connection is established.
14321502
///
14331503
/// # Example
14341504
///
@@ -1440,7 +1510,21 @@ impl ClientBuilder {
14401510
/// .interface(interface)
14411511
/// .build().unwrap();
14421512
/// ```
1443-
#[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))]
1513+
///
1514+
/// [man-7-socket]: https://man7.org/linux/man-pages/man7/socket.7.html
1515+
/// [man-7p-ip]: https://docs.oracle.com/cd/E86824_01/html/E54777/ip-7p.html
1516+
#[cfg(any(
1517+
target_os = "android",
1518+
target_os = "fuchsia",
1519+
target_os = "illumos",
1520+
target_os = "ios",
1521+
target_os = "linux",
1522+
target_os = "macos",
1523+
target_os = "solaris",
1524+
target_os = "tvos",
1525+
target_os = "visionos",
1526+
target_os = "watchos",
1527+
))]
14441528
pub fn interface(mut self, interface: &str) -> ClientBuilder {
14451529
self.config.interface = Some(interface.to_string());
14461530
self
@@ -2414,7 +2498,18 @@ impl Config {
24142498
f.field("local_address", v);
24152499
}
24162500

2417-
#[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))]
2501+
#[cfg(any(
2502+
target_os = "android",
2503+
target_os = "fuchsia",
2504+
target_os = "illumos",
2505+
target_os = "ios",
2506+
target_os = "linux",
2507+
target_os = "macos",
2508+
target_os = "solaris",
2509+
target_os = "tvos",
2510+
target_os = "visionos",
2511+
target_os = "watchos",
2512+
))]
24182513
if let Some(ref v) = self.interface {
24192514
f.field("interface", v);
24202515
}

src/connect.rs

Lines changed: 96 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -148,15 +148,37 @@ where {
148148
mut http: HttpConnector,
149149
proxies: Arc<Vec<Proxy>>,
150150
local_addr: T,
151-
#[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))]
151+
#[cfg(any(
152+
target_os = "android",
153+
target_os = "fuchsia",
154+
target_os = "illumos",
155+
target_os = "ios",
156+
target_os = "linux",
157+
target_os = "macos",
158+
target_os = "solaris",
159+
target_os = "tvos",
160+
target_os = "visionos",
161+
target_os = "watchos",
162+
))]
152163
interface: Option<&str>,
153164
nodelay: bool,
154165
) -> ConnectorBuilder
155166
where
156167
T: Into<Option<IpAddr>>,
157168
{
158169
http.set_local_address(local_addr.into());
159-
#[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))]
170+
#[cfg(any(
171+
target_os = "android",
172+
target_os = "fuchsia",
173+
target_os = "illumos",
174+
target_os = "ios",
175+
target_os = "linux",
176+
target_os = "macos",
177+
target_os = "solaris",
178+
target_os = "tvos",
179+
target_os = "visionos",
180+
target_os = "watchos",
181+
))]
160182
if let Some(interface) = interface {
161183
http.set_interface(interface.to_owned());
162184
}
@@ -177,7 +199,18 @@ where {
177199
proxies: Arc<Vec<Proxy>>,
178200
user_agent: Option<HeaderValue>,
179201
local_addr: T,
180-
#[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))]
202+
#[cfg(any(
203+
target_os = "android",
204+
target_os = "fuchsia",
205+
target_os = "illumos",
206+
target_os = "ios",
207+
target_os = "linux",
208+
target_os = "macos",
209+
target_os = "solaris",
210+
target_os = "tvos",
211+
target_os = "visionos",
212+
target_os = "watchos",
213+
))]
181214
interface: Option<&str>,
182215
nodelay: bool,
183216
tls_info: bool,
@@ -192,7 +225,18 @@ where {
192225
proxies,
193226
user_agent,
194227
local_addr,
195-
#[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))]
228+
#[cfg(any(
229+
target_os = "android",
230+
target_os = "fuchsia",
231+
target_os = "illumos",
232+
target_os = "ios",
233+
target_os = "linux",
234+
target_os = "macos",
235+
target_os = "solaris",
236+
target_os = "tvos",
237+
target_os = "visionos",
238+
target_os = "watchos",
239+
))]
196240
interface,
197241
nodelay,
198242
tls_info,
@@ -206,7 +250,18 @@ where {
206250
proxies: Arc<Vec<Proxy>>,
207251
user_agent: Option<HeaderValue>,
208252
local_addr: T,
209-
#[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))]
253+
#[cfg(any(
254+
target_os = "android",
255+
target_os = "fuchsia",
256+
target_os = "illumos",
257+
target_os = "ios",
258+
target_os = "linux",
259+
target_os = "macos",
260+
target_os = "solaris",
261+
target_os = "tvos",
262+
target_os = "visionos",
263+
target_os = "watchos",
264+
))]
210265
interface: Option<&str>,
211266
nodelay: bool,
212267
tls_info: bool,
@@ -215,7 +270,18 @@ where {
215270
T: Into<Option<IpAddr>>,
216271
{
217272
http.set_local_address(local_addr.into());
218-
#[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))]
273+
#[cfg(any(
274+
target_os = "android",
275+
target_os = "fuchsia",
276+
target_os = "illumos",
277+
target_os = "ios",
278+
target_os = "linux",
279+
target_os = "macos",
280+
target_os = "solaris",
281+
target_os = "tvos",
282+
target_os = "visionos",
283+
target_os = "watchos",
284+
))]
219285
if let Some(interface) = interface {
220286
http.set_interface(interface);
221287
}
@@ -240,7 +306,18 @@ where {
240306
proxies: Arc<Vec<Proxy>>,
241307
user_agent: Option<HeaderValue>,
242308
local_addr: T,
243-
#[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))]
309+
#[cfg(any(
310+
target_os = "android",
311+
target_os = "fuchsia",
312+
target_os = "illumos",
313+
target_os = "ios",
314+
target_os = "linux",
315+
target_os = "macos",
316+
target_os = "solaris",
317+
target_os = "tvos",
318+
target_os = "visionos",
319+
target_os = "watchos",
320+
))]
244321
interface: Option<&str>,
245322
nodelay: bool,
246323
tls_info: bool,
@@ -249,7 +326,18 @@ where {
249326
T: Into<Option<IpAddr>>,
250327
{
251328
http.set_local_address(local_addr.into());
252-
#[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))]
329+
#[cfg(any(
330+
target_os = "android",
331+
target_os = "fuchsia",
332+
target_os = "illumos",
333+
target_os = "ios",
334+
target_os = "linux",
335+
target_os = "macos",
336+
target_os = "solaris",
337+
target_os = "tvos",
338+
target_os = "visionos",
339+
target_os = "watchos",
340+
))]
253341
if let Some(interface) = interface {
254342
http.set_interface(interface.to_owned());
255343
}

0 commit comments

Comments
 (0)