Skip to content

Commit

Permalink
extend x-platform support for binding URL previews to interfaces via …
Browse files Browse the repository at this point in the history
…address

Signed-off-by: Jason Volk <[email protected]>
  • Loading branch information
jevolk committed Dec 8, 2024
1 parent 52cee65 commit f0a1aaf
Show file tree
Hide file tree
Showing 6 changed files with 74 additions and 28 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 7 additions & 5 deletions conduwuit-example.toml
Original file line number Diff line number Diff line change
Expand Up @@ -1117,13 +1117,15 @@
#
#ip_range_denylist = ["127.0.0.0/8", "10.0.0.0/8", "172.16.0.0/12",

# Optional interface to bind to with SO_BINDTODEVICE for URL previews.
# If not set, it will not bind to a specific interface.
# This uses [`reqwest::ClientBuilder::interface`] under the hood.
# Optional IP address or network interface-name to bind as the source of
# URL preview requests. If not set, it will not bind to a specific
# address or interface.
#
# To list the interfaces on your system, use the command `ip link show`
# Interface names only supported on Linux, Android, and Fuchsia platforms;
# all other platforms can specify the IP address. To list the interfaces
# on your system, use the command `ip link show`.
#
# Example: `"eth0"`
# example: `"eth0"` or `"1.2.3.4"`
#
#url_preview_bound_interface =

Expand Down
12 changes: 12 additions & 0 deletions src/core/config/check.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
use std::env::consts::OS;

use either::Either;
use figment::Figment;

use super::DEPRECATED_KEYS;
Expand Down Expand Up @@ -191,6 +194,15 @@ For security and safety reasons, conduwuit will shut down. If you are extra sure
);
}

if let Some(Either::Right(_)) = config.url_preview_bound_interface.as_ref() {
if !matches!(OS, "android" | "fuchsia" | "linux") {
return Err!(Config(
"url_preview_bound_interface",
"Not a valid IP address. Interface names not supported on {OS}."
));
}
}

Ok(())
}

Expand Down
31 changes: 18 additions & 13 deletions src/core/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1250,15 +1250,19 @@ pub struct Config {
#[serde(default = "default_ip_range_denylist")]
pub ip_range_denylist: Vec<String>,

/// Optional interface to bind to with SO_BINDTODEVICE for URL previews.
/// If not set, it will not bind to a specific interface.
/// This uses [`reqwest::ClientBuilder::interface`] under the hood.
/// Optional IP address or network interface-name to bind as the source of
/// URL preview requests. If not set, it will not bind to a specific
/// address or interface.
///
/// To list the interfaces on your system, use the command `ip link show`
/// Interface names only supported on Linux, Android, and Fuchsia platforms;
/// all other platforms can specify the IP address. To list the interfaces
/// on your system, use the command `ip link show`.
///
/// Example: `"eth0"`
#[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))]
pub url_preview_bound_interface: Option<String>,
/// example: `"eth0"` or `"1.2.3.4"`
///
/// default:
#[serde(default, with = "either::serde_untagged_optional")]
pub url_preview_bound_interface: Option<Either<IpAddr, String>>,

/// Vector list of domains allowed to send requests to for URL previews.
/// Defaults to none. Note: this is a *contains* match, not an explicit
Expand Down Expand Up @@ -1970,14 +1974,15 @@ impl fmt::Display for Config {
line("Forbidden room aliases", {
&self.forbidden_alias_names.patterns().iter().join(", ")
});
#[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))]
line(
"URL preview bound interface",
if let Some(interface) = &self.url_preview_bound_interface {
interface
} else {
"not set"
},
self.url_preview_bound_interface
.as_ref()
.map(Either::as_ref)
.map(|either| either.map_left(ToString::to_string))
.map(Either::either_into::<String>)
.unwrap_or_default()
.as_str(),
);
line(
"URL preview domain contains allowlist",
Expand Down
1 change: 1 addition & 0 deletions src/service/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ bytes.workspace = true
conduit-core.workspace = true
conduit-database.workspace = true
const-str.workspace = true
either.workspace = true
futures.workspace = true
hickory-resolver.workspace = true
http.workspace = true
Expand Down
45 changes: 35 additions & 10 deletions src/service/client/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use std::{sync::Arc, time::Duration};

use conduit::{err, implement, trace, Config, Result};
use either::Either;
use ipaddress::IPAddress;
use reqwest::redirect;

Expand All @@ -25,23 +26,27 @@ impl crate::Service for Service {
let config = &args.server.config;
let resolver = args.require::<resolver::Service>("resolver");

let url_preview_builder = base(config)?
.dns_resolver(resolver.resolver.clone())
.redirect(redirect::Policy::limited(3));
let url_preview_bind_addr = config
.url_preview_bound_interface
.clone()
.and_then(Either::left);

#[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))]
let url_preview_builder = if let Some(interface) = &config.url_preview_bound_interface {
url_preview_builder.interface(interface)
} else {
url_preview_builder
};
let url_preview_bind_iface = config
.url_preview_bound_interface
.clone()
.and_then(Either::right);

Ok(Arc::new(Self {
default: base(config)?
.dns_resolver(resolver.resolver.clone())
.build()?,

url_preview: url_preview_builder.build()?,
url_preview: base(config)
.and_then(|builder| builder_interface(builder, url_preview_bind_iface.as_deref()))?
.local_address(url_preview_bind_addr)
.dns_resolver(resolver.resolver.clone())
.redirect(redirect::Policy::limited(3))
.build()?,

extern_media: base(config)?
.dns_resolver(resolver.resolver.clone())
Expand Down Expand Up @@ -172,6 +177,26 @@ fn base(config: &Config) -> Result<reqwest::ClientBuilder> {
}
}

#[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))]
fn builder_interface(builder: reqwest::ClientBuilder, config: Option<&str>) -> Result<reqwest::ClientBuilder> {
if let Some(iface) = config {
Ok(builder.interface(iface))
} else {
Ok(builder)
}
}

#[cfg(not(any(target_os = "android", target_os = "fuchsia", target_os = "linux")))]
fn builder_interface(builder: reqwest::ClientBuilder, config: Option<&str>) -> Result<reqwest::ClientBuilder> {
use conduit::Err;

if let Some(iface) = config {
Err!("Binding to network-interface {iface:?} by name is not supported on this platform.")
} else {
Ok(builder)
}
}

#[inline]
#[must_use]
#[implement(Service)]
Expand Down

0 comments on commit f0a1aaf

Please sign in to comment.