Skip to content

Commit e2485dd

Browse files
Bind to local addr based on network adapter
1 parent e594917 commit e2485dd

13 files changed

Lines changed: 261 additions & 100 deletions

File tree

mtorrent-core/src/trackers/http.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use local_async_utils::prelude::*;
22
use mtorrent_utils::{benc, ip};
33
use reqwest::{ClientBuilder, Url};
44
use std::collections::{BTreeMap, HashMap};
5-
use std::net::SocketAddr;
5+
use std::net::{IpAddr, SocketAddr};
66
use std::{fmt, io, str};
77
use thiserror::Error;
88

@@ -60,10 +60,11 @@ fn set_interface(builder: ClientBuilder, interface: Option<&str>) -> ClientBuild
6060
}
6161

6262
impl TrackerClient {
63-
pub fn new(interface: Option<&str>) -> Result<Self, Error> {
63+
pub fn new(local_addr: IpAddr, interface: Option<&str>) -> Result<Self, Error> {
6464
let builder = reqwest::Client::builder()
6565
.gzip(true)
6666
.user_agent(APP_USER_AGENT)
67+
.local_address(local_addr)
6768
.timeout(sec!(30));
6869

6970
let inner = set_interface(builder, interface).build()?;
@@ -417,6 +418,7 @@ fn dictionary_peers(data: &[benc::Element]) -> impl Iterator<Item = SocketAddr>
417418
#[cfg(test)]
418419
mod tests {
419420
use super::*;
421+
use std::net::Ipv4Addr;
420422

421423
#[test]
422424
fn test_announce_uri() {
@@ -529,7 +531,7 @@ mod tests {
529531
#[tokio::test]
530532
async fn test_https_scrape_and_announce() {
531533
let tracker_url = "https://torrent.ubuntu.com/announce";
532-
let client = TrackerClient::new(Default::default()).unwrap();
534+
let client = TrackerClient::new(Ipv4Addr::UNSPECIFIED.into(), None).unwrap();
533535

534536
let request = TrackerRequestBuilder::try_from(tracker_url).unwrap();
535537
let response = client.scrape(request).await.unwrap_or_else(|e| panic!("Scrape error: {e}"));

mtorrent-core/src/trackers/mod.rs

Lines changed: 32 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ mod url;
44

55
use futures_util::TryFutureExt;
66
use local_async_utils::sec;
7-
use mtorrent_utils::ip::bind_to_interface;
7+
use mtorrent_utils::ip;
88
use mtorrent_utils::peer_id::PeerId;
99
use std::collections::HashMap;
1010
use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr};
@@ -183,7 +183,10 @@ impl Manager {
183183
}};
184184
}
185185

186-
let http_client = http::TrackerClient::new(self.config.bind_interface.as_deref())
186+
let interface = self.config.bind_interface.as_deref();
187+
let local_ipv4 = ip::get_bind_addr_v4(interface);
188+
let local_ipv6 = ip::get_bind_addr_v6(interface);
189+
let http_client = http::TrackerClient::new(local_ipv4.into(), interface)
187190
.inspect_err(|e| log::error!("Failed to create HTTP tracker client: {e}"))
188191
.ok();
189192

@@ -206,8 +209,14 @@ impl Manager {
206209
TrackerUrl::Udp(addr) => {
207210
let interface = self.config.bind_interface.clone();
208211
spawn_child_task!(async move {
209-
let result =
210-
do_udp_announce(&addr, request.data, interface.as_deref()).await;
212+
let result = do_udp_announce(
213+
&addr,
214+
request.data,
215+
interface.as_deref(),
216+
local_ipv4,
217+
local_ipv6,
218+
)
219+
.await;
211220
_ = request.responder.send(result).inspect_err(|_| {
212221
log::warn!("Failed to send back udp announce result")
213222
});
@@ -231,8 +240,14 @@ impl Manager {
231240
TrackerUrl::Udp(addr) => {
232241
let interface = self.config.bind_interface.clone();
233242
spawn_child_task!(async move {
234-
let result =
235-
do_udp_scrape(&addr, request.data, interface.as_deref()).await;
243+
let result = do_udp_scrape(
244+
&addr,
245+
request.data,
246+
interface.as_deref(),
247+
local_ipv4,
248+
local_ipv6,
249+
)
250+
.await;
236251
_ = request.responder.send(result).inspect_err(|_| {
237252
log::warn!("Failed to send back udp scrape result")
238253
});
@@ -318,6 +333,8 @@ async fn do_http_scrape(
318333
async fn new_udp_client(
319334
tracker_addr_str: &str,
320335
interface: Option<&str>,
336+
local_ipv4: Ipv4Addr,
337+
local_ipv6: Ipv6Addr,
321338
) -> io::Result<udp::TrackerConnection> {
322339
async fn bind_and_connect_socket(
323340
bind_addr: &SocketAddr,
@@ -326,16 +343,16 @@ async fn new_udp_client(
326343
) -> io::Result<UdpSocket> {
327344
let socket = UdpSocket::bind(bind_addr).await?;
328345
if let Some(iface) = interface {
329-
bind_to_interface(&socket, iface)?;
346+
ip::bind_to_interface(&socket, iface)?;
330347
}
331348
socket.connect(&remote_addr).await?;
332349
Ok(socket)
333350
}
334351

335352
for tracker_addr in net::lookup_host(tracker_addr_str).await? {
336353
let local_ip = match &tracker_addr {
337-
SocketAddr::V4(_) => Ipv4Addr::UNSPECIFIED.into(),
338-
SocketAddr::V6(_) => Ipv6Addr::UNSPECIFIED.into(),
354+
SocketAddr::V4(_) => local_ipv4.into(),
355+
SocketAddr::V6(_) => local_ipv6.into(),
339356
};
340357
let local_addr = SocketAddr::new(local_ip, 0);
341358
if let Ok(client) = bind_and_connect_socket(&local_addr, &tracker_addr, interface)
@@ -352,8 +369,10 @@ async fn do_udp_announce(
352369
tracker_addr: &str,
353370
data: AnnounceRequest,
354371
interface: Option<&str>,
372+
local_ipv4: Ipv4Addr,
373+
local_ipv6: Ipv6Addr,
355374
) -> io::Result<AnnounceResponse> {
356-
let mut client = new_udp_client(tracker_addr, interface).await?;
375+
let mut client = new_udp_client(tracker_addr, interface, local_ipv4, local_ipv6).await?;
357376

358377
let request = udp::AnnounceRequest {
359378
info_hash: data.info_hash,
@@ -379,8 +398,10 @@ async fn do_udp_scrape(
379398
tracker_addr: &str,
380399
data: ScrapeRequest,
381400
interface: Option<&str>,
401+
local_ipv4: Ipv4Addr,
402+
local_ipv6: Ipv6Addr,
382403
) -> io::Result<ScrapeResponse> {
383-
let mut client = new_udp_client(tracker_addr, interface).await?;
404+
let mut client = new_udp_client(tracker_addr, interface, local_ipv4, local_ipv6).await?;
384405

385406
let request = udp::ScrapeRequest {
386407
info_hashes: data.info_hashes.clone(),

mtorrent-utils/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ socket2 = { workspace = true }
2727

2828
[target.'cfg(not(windows))'.dependencies]
2929
libc = "0.2"
30+
sysinfo = "0.38"
3031

3132
[target.'cfg(windows)'.dependencies]
3233
ipconfig = "0.3"

mtorrent-utils/src/ip.rs

Lines changed: 89 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use bytes::Buf;
22
use socket2::SockRef;
33
use std::hash::{DefaultHasher, Hash, Hasher};
4-
use std::net::{Ipv4Addr, SocketAddrV4, SocketAddrV6};
4+
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddrV4, SocketAddrV6};
55
use std::{io, ops};
66

77
/// Get local (non-loopback) IPv4.
@@ -26,12 +26,82 @@ pub fn get_local_addr() -> io::Result<Ipv4Addr> {
2626
.filter(|adapter| matches!(adapter.oper_status(), ipconfig::OperStatus::IfOperStatusUp))
2727
.flat_map(ipconfig::Adapter::ip_addresses)
2828
.find_map(|addr| match addr {
29-
std::net::IpAddr::V4(ipv4) => Some(*ipv4),
29+
IpAddr::V4(ipv4) => Some(*ipv4),
3030
_ => None,
3131
})
3232
.ok_or_else(|| io::Error::new(io::ErrorKind::NotFound, "IPv4 not found"))
3333
}
3434

35+
#[cfg(windows)]
36+
fn get_adapter_addrs<'a>(
37+
adapters: impl IntoIterator<Item = &'a ipconfig::Adapter>,
38+
iface: &str,
39+
) -> impl Iterator<Item = &'a IpAddr> {
40+
adapters
41+
.into_iter()
42+
.filter(move |adapter| {
43+
matches!(adapter.oper_status(), ipconfig::OperStatus::IfOperStatusUp)
44+
&& (adapter.adapter_name() == iface || adapter.friendly_name() == iface)
45+
})
46+
.flat_map(ipconfig::Adapter::ip_addresses)
47+
}
48+
49+
pub fn get_bind_addr_v4(interface: Option<&str>) -> Ipv4Addr {
50+
let Some(iface) = interface else {
51+
return Ipv4Addr::UNSPECIFIED;
52+
};
53+
54+
#[cfg(windows)]
55+
if let Ok(adapters) = ipconfig::get_adapters() {
56+
let found = get_adapter_addrs(&adapters, iface).find_map(|addr| match addr {
57+
IpAddr::V4(ipv4) => Some(*ipv4),
58+
_ => None,
59+
});
60+
debug_assert!(found.is_some(), "failed to find network adapter");
61+
return found.unwrap_or(Ipv4Addr::UNSPECIFIED);
62+
}
63+
64+
#[cfg(not(windows))]
65+
if let Some(network_data) = sysinfo::Networks::new_with_refreshed_list().get(iface) {
66+
let found = network_data.ip_networks().iter().find_map(|network| match network.addr {
67+
IpAddr::V4(ipv4) => Some(ipv4),
68+
_ => None,
69+
});
70+
debug_assert!(found.is_some(), "failed to find network adapter");
71+
return found.unwrap_or(Ipv4Addr::UNSPECIFIED);
72+
}
73+
74+
Ipv4Addr::UNSPECIFIED
75+
}
76+
77+
pub fn get_bind_addr_v6(interface: Option<&str>) -> Ipv6Addr {
78+
let Some(iface) = interface else {
79+
return Ipv6Addr::UNSPECIFIED;
80+
};
81+
82+
#[cfg(windows)]
83+
if let Ok(adapters) = ipconfig::get_adapters() {
84+
let found = get_adapter_addrs(&adapters, iface).find_map(|addr| match addr {
85+
IpAddr::V6(ipv6) => Some(*ipv6),
86+
_ => None,
87+
});
88+
debug_assert!(found.is_some(), "failed to find network adapter");
89+
return found.unwrap_or(Ipv6Addr::UNSPECIFIED);
90+
}
91+
92+
#[cfg(not(windows))]
93+
if let Some(network_data) = sysinfo::Networks::new_with_refreshed_list().get(iface) {
94+
let found = network_data.ip_networks().iter().find_map(|network| match network.addr {
95+
IpAddr::V6(ipv6) => Some(ipv6),
96+
_ => None,
97+
});
98+
debug_assert!(found.is_some(), "failed to find network adapter");
99+
return found.unwrap_or(Ipv6Addr::UNSPECIFIED);
100+
}
101+
102+
Ipv6Addr::UNSPECIFIED
103+
}
104+
35105
#[cfg(not(any(target_os = "linux", windows)))]
36106
pub fn get_local_addr() -> io::Result<Ipv4Addr> {
37107
Ok(Ipv4Addr::UNSPECIFIED)
@@ -242,4 +312,21 @@ mod tests {
242312
assert_eq!(idx, i);
243313
}
244314
}
315+
316+
#[test]
317+
fn test_get_bind_addr() {
318+
let iface = if cfg!(target_os = "windows") {
319+
"Loopback Pseudo-Interface 1"
320+
} else if cfg!(target_os = "macos") {
321+
"lo0"
322+
} else {
323+
"lo"
324+
};
325+
326+
let addr = get_bind_addr_v4(Some(iface));
327+
assert_eq!(addr, Ipv4Addr::LOCALHOST);
328+
329+
let addr = get_bind_addr_v6(Some(iface));
330+
assert_eq!(addr, Ipv6Addr::LOCALHOST);
331+
}
245332
}

mtorrent/src/app/dht.rs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
use mtorrent_dht as dht;
2-
use mtorrent_utils::ip::bind_to_interface;
32
use mtorrent_utils::{info_stopwatch, ip, upnp, worker};
43
use std::io;
5-
use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4};
4+
use std::net::{SocketAddr, SocketAddrV4};
65
use std::path::PathBuf;
76
use std::time::Duration;
87
use tokio::net::UdpSocket;
@@ -92,13 +91,18 @@ async fn dht_main(
9291
) {
9392
let _sw = info_stopwatch!("DHT");
9493

95-
let socket = match UdpSocket::bind(SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, local_port)).await {
94+
let socket = match UdpSocket::bind(SocketAddrV4::new(
95+
ip::get_bind_addr_v4(bind_interface.as_deref()),
96+
local_port,
97+
))
98+
.await
99+
{
96100
Err(e) => return log::error!("Failed to create a UDP socket for DHT: {e}"),
97101
Ok(socket) => socket,
98102
};
99103

100104
if let Some(interface) = bind_interface
101-
&& let Err(e) = bind_to_interface(&socket, &interface)
105+
&& let Err(e) = ip::bind_to_interface(&socket, &interface)
102106
{
103107
log::error!("Failed to bind DHT UDP socket to interface {interface}: {e}");
104108
return;

0 commit comments

Comments
 (0)