Skip to content

Commit 7508c1b

Browse files
add public proxy type
1 parent 5f5c53d commit 7508c1b

File tree

6 files changed

+155
-133
lines changed

6 files changed

+155
-133
lines changed

src/client/csfle.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ impl ClientState {
7373
mongocryptd_client,
7474
aux_clients.metadata_client,
7575
#[cfg(feature = "socks5-proxy")]
76-
crate::runtime::stream::Proxy::from_client_options(client.options()),
76+
client.options().socks5_proxy.clone(),
7777
)
7878
.await?;
7979

src/client/csfle/state_machine.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,6 @@ use std::{
55
time::Duration,
66
};
77

8-
use crate::bson::{rawdoc, Document, RawDocument, RawDocumentBuf};
9-
#[cfg(feature = "socks5-proxy")]
10-
use crate::runtime::stream::Proxy;
118
use futures_util::{stream, TryStreamExt};
129
use mongocrypt::ctx::{Ctx, KmsCtx, KmsProviderType, State};
1310
use rayon::ThreadPool;
@@ -16,7 +13,10 @@ use tokio::{
1613
sync::{oneshot, Mutex},
1714
};
1815

16+
#[cfg(feature = "socks5-proxy")]
17+
use crate::options::Socks5Proxy;
1918
use crate::{
19+
bson::{rawdoc, Document, RawDocument, RawDocumentBuf},
2020
client::{csfle::options::KmsProvidersTlsOptions, options::ServerAddress, WeakClient},
2121
error::{Error, Result},
2222
operation::{raw_output::RawOutput, run_command::RunCommand},
@@ -40,7 +40,7 @@ pub(crate) struct CryptExecutor {
4040
#[cfg(feature = "azure-kms")]
4141
azure: azure::ExecutorState,
4242
#[cfg(feature = "socks5-proxy")]
43-
proxy: Option<Proxy>,
43+
proxy: Option<Socks5Proxy>,
4444
}
4545

4646
impl CryptExecutor {
@@ -76,7 +76,7 @@ impl CryptExecutor {
7676
mongocryptd_opts: Option<MongocryptdOptions>,
7777
mongocryptd_client: Option<Client>,
7878
metadata_client: Option<WeakClient>,
79-
#[cfg(feature = "socks5-proxy")] proxy: Option<Proxy>,
79+
#[cfg(feature = "socks5-proxy")] proxy: Option<Socks5Proxy>,
8080
) -> Result<Self> {
8181
let mongocryptd = match mongocryptd_opts {
8282
Some(opts) => Some(Mongocryptd::new(opts).await?),
@@ -188,7 +188,7 @@ impl CryptExecutor {
188188
async fn execute(
189189
kms_ctx: &mut KmsCtx<'_>,
190190
tls_options: Option<&KmsProvidersTlsOptions>,
191-
#[cfg(feature = "socks5-proxy")] proxy: Option<&Proxy>,
191+
#[cfg(feature = "socks5-proxy")] proxy: Option<&Socks5Proxy>,
192192
) -> Result<()> {
193193
let endpoint = kms_ctx.endpoint()?;
194194
let addr = ServerAddress::parse(endpoint)?;

src/client/options.rs

Lines changed: 124 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -415,6 +415,54 @@ pub struct ServerApi {
415415
pub deprecation_errors: Option<bool>,
416416
}
417417

418+
/// Configuration for connecting to a SOCKS5 proxy.
419+
#[cfg(feature = "socks5-proxy")]
420+
#[derive(Clone, Debug, Deserialize, PartialEq, TypedBuilder)]
421+
#[builder(field_defaults(default, setter(into)))]
422+
#[non_exhaustive]
423+
pub struct Socks5Proxy {
424+
/// The hostname or IP address on which the proxy is listening.
425+
pub host: String,
426+
427+
/// The port on which the proxy is listening. Defaults to 1080 if unset.
428+
pub port: Option<u16>,
429+
430+
/// A username/password pair to authenticate to the proxy.
431+
pub authentication: Option<(String, String)>,
432+
}
433+
434+
#[cfg(feature = "socks5-proxy")]
435+
impl Socks5Proxy {
436+
fn serialize<S>(
437+
proxy: &Option<Socks5Proxy>,
438+
serializer: S,
439+
) -> std::result::Result<S::Ok, S::Error>
440+
where
441+
S: serde::Serializer,
442+
{
443+
#[derive(Serialize)]
444+
#[serde(rename_all = "camelCase")]
445+
struct Helper<'a> {
446+
proxy_host: &'a String,
447+
proxy_port: Option<u16>,
448+
proxy_username: Option<&'a String>,
449+
proxy_password: Option<&'a String>,
450+
}
451+
452+
if let Some(proxy) = proxy.as_ref() {
453+
let helper = Helper {
454+
proxy_host: &proxy.host,
455+
proxy_port: proxy.port,
456+
proxy_username: proxy.authentication.as_ref().map(|auth| &auth.0),
457+
proxy_password: proxy.authentication.as_ref().map(|auth| &auth.1),
458+
};
459+
helper.serialize(serializer)
460+
} else {
461+
serializer.serialize_none()
462+
}
463+
}
464+
}
465+
418466
/// Contains the options that can be used to create a new [`Client`](../struct.Client.html).
419467
#[derive(Clone, Deserialize, TypedBuilder)]
420468
#[builder(field_defaults(default, setter(into)))]
@@ -624,22 +672,9 @@ pub struct ClientOptions {
624672
#[cfg(feature = "opentelemetry")]
625673
pub tracing: Option<crate::otel::OpentelemetryOptions>,
626674

627-
/// The IPv4 or IPv6 address or domain name of a proxy server used for connecting to MongoDB
628-
/// services.
675+
/// Configuration for connecting to a SOCKS5 proxy.
629676
#[cfg(feature = "socks5-proxy")]
630-
pub proxy_host: Option<String>,
631-
632-
/// The port of the proxy server. Defaults to 1080 if unspecified.
633-
#[cfg(feature = "socks5-proxy")]
634-
pub proxy_port: Option<u16>,
635-
636-
/// The username for authentication to the proxy server.
637-
#[cfg(feature = "socks5-proxy")]
638-
pub proxy_username: Option<String>,
639-
640-
/// The password for authentication to the proxy server.
641-
#[cfg(feature = "socks5-proxy")]
642-
pub proxy_password: Option<String>,
677+
pub socks5_proxy: Option<Socks5Proxy>,
643678

644679
/// Information from the SRV URI that generated these client options, if applicable.
645680
#[builder(setter(skip))]
@@ -773,6 +808,10 @@ impl Serialize for ClientOptions {
773808
srvmaxhosts: Option<i32>,
774809

775810
srvservicename: &'a Option<String>,
811+
812+
#[cfg(feature = "socks5-proxy")]
813+
#[serde(flatten, serialize_with = "Socks5Proxy::serialize")]
814+
socks5proxy: &'a Option<Socks5Proxy>,
776815
}
777816

778817
let client_options = ClientOptionsHelper {
@@ -807,6 +846,8 @@ impl Serialize for ClientOptions {
807846
.transpose()
808847
.map_err(serde::ser::Error::custom)?,
809848
srvservicename: &self.srv_service_name,
849+
#[cfg(feature = "socks5-proxy")]
850+
socks5proxy: &self.socks5_proxy,
810851
};
811852

812853
client_options.serialize(serializer)
@@ -1002,22 +1043,9 @@ pub struct ConnectionString {
10021043
/// Overrides the default "mongodb" service name for SRV lookup in both discovery and polling
10031044
pub srv_service_name: Option<String>,
10041045

1005-
/// The IPv4 or IPv6 address or domain name of a proxy server used for connecting to MongoDB
1006-
/// services.
1007-
#[cfg(feature = "socks5-proxy")]
1008-
pub proxy_host: Option<String>,
1009-
1010-
/// The port of the proxy server. Defaults to 1080 if unspecified.
1011-
#[cfg(feature = "socks5-proxy")]
1012-
pub proxy_port: Option<u16>,
1013-
1014-
/// The username for authentication to the proxy server.
1015-
#[cfg(feature = "socks5-proxy")]
1016-
pub proxy_username: Option<String>,
1017-
1018-
/// The password for authentication to the proxy server.
1019-
#[cfg(feature = "socks5-proxy")]
1020-
pub proxy_password: Option<String>,
1046+
/// Configuration for connecting to a SOCKS5 proxy.
1047+
#[serde(serialize_with = "Socks5Proxy::serialize")]
1048+
pub socks5_proxy: Option<Socks5Proxy>,
10211049

10221050
#[serde(serialize_with = "serde_util::serialize_duration_option_as_int_millis")]
10231051
wait_queue_timeout: Option<Duration>,
@@ -1037,6 +1065,14 @@ struct ConnectionStringParts {
10371065
auth_mechanism_properties: Option<Document>,
10381066
zlib_compression: Option<i32>,
10391067
auth_source: Option<String>,
1068+
#[cfg(feature = "socks5-proxy")]
1069+
proxy_host: Option<String>,
1070+
#[cfg(feature = "socks5-proxy")]
1071+
proxy_port: Option<u16>,
1072+
#[cfg(feature = "socks5-proxy")]
1073+
proxy_username: Option<String>,
1074+
#[cfg(feature = "socks5-proxy")]
1075+
proxy_password: Option<String>,
10401076
}
10411077

10421078
/// Specification for mongodb server connections.
@@ -1361,7 +1397,7 @@ impl ClientOptions {
13611397

13621398
#[cfg(feature = "socks5-proxy")]
13631399
{
1364-
if self.proxy_host.is_some() {
1400+
if let Some(proxy) = self.socks5_proxy.as_ref() {
13651401
if self
13661402
.hosts
13671403
.iter()
@@ -1372,25 +1408,12 @@ impl ClientOptions {
13721408
));
13731409
}
13741410

1375-
if self.proxy_username.is_some() != self.proxy_password.is_some() {
1376-
return Err(Error::invalid_argument(
1377-
"proxy username and proxy password must both be specified or unset",
1378-
));
1379-
}
1380-
} else {
1381-
let error = |option: &str| {
1382-
Error::invalid_argument(format!(
1383-
"cannot specify a proxy {option} without a proxy host"
1384-
))
1385-
};
1386-
if self.proxy_port.is_some() {
1387-
return Err(error("port"));
1388-
}
1389-
if self.proxy_username.is_some() {
1390-
return Err(error("username"));
1391-
}
1392-
if self.proxy_password.is_some() {
1393-
return Err(error("password"));
1411+
if let Some((username, password)) = proxy.authentication.as_ref() {
1412+
if username.is_empty() || password.is_empty() {
1413+
return Err(Error::invalid_argument(
1414+
"cannot specify an empty username or password for proxy host",
1415+
));
1416+
}
13941417
}
13951418
}
13961419
}
@@ -1742,6 +1765,43 @@ impl ConnectionString {
17421765
conn_str.tls = Some(Tls::Enabled(Default::default()));
17431766
}
17441767

1768+
#[cfg(feature = "socks5-proxy")]
1769+
{
1770+
if let Some(host) = parts.proxy_host {
1771+
let mut proxy = Socks5Proxy::builder().host(host).build();
1772+
if let Some(port) = parts.proxy_port {
1773+
proxy.port = Some(port);
1774+
}
1775+
match (parts.proxy_username, parts.proxy_password) {
1776+
(Some(username), Some(password)) => {
1777+
proxy.authentication = Some((username, password))
1778+
}
1779+
(None, None) => {}
1780+
_ => {
1781+
return Err(Error::invalid_argument(
1782+
"proxy username and password must both be specified as nonempty \
1783+
strings or unset",
1784+
));
1785+
}
1786+
}
1787+
} else {
1788+
let error = |option: &str| {
1789+
Error::invalid_argument(format!(
1790+
"{option} cannot be set if {PROXY_HOST} is unspecified"
1791+
))
1792+
};
1793+
if parts.proxy_port.is_some() {
1794+
return Err(error(PROXY_PORT));
1795+
}
1796+
if parts.proxy_username.is_some() {
1797+
return Err(error(PROXY_USERNAME));
1798+
}
1799+
if parts.proxy_password.is_some() {
1800+
return Err(error(PROXY_PASSWORD));
1801+
}
1802+
}
1803+
}
1804+
17451805
Ok(conn_str)
17461806
}
17471807

@@ -1783,13 +1843,7 @@ impl ConnectionString {
17831843
wait_queue_timeout,
17841844
tls_insecure,
17851845
#[cfg(feature = "socks5-proxy")]
1786-
proxy_host,
1787-
#[cfg(feature = "socks5-proxy")]
1788-
proxy_port,
1789-
#[cfg(feature = "socks5-proxy")]
1790-
proxy_username,
1791-
#[cfg(feature = "socks5-proxy")]
1792-
proxy_password,
1846+
socks5_proxy,
17931847
#[cfg(test)]
17941848
original_uri: _,
17951849
} = self;
@@ -2092,21 +2146,15 @@ impl ConnectionString {
20922146
}
20932147

20942148
#[cfg(feature = "socks5-proxy")]
2095-
{
2096-
if let Some(proxy_host) = proxy_host {
2097-
opts.push_str(&format!("&proxyHost={proxy_host}"));
2098-
}
2099-
2100-
if let Some(proxy_port) = proxy_port {
2101-
opts.push_str(&format!("&proxyPort={proxy_port}"));
2149+
if let Some(proxy) = socks5_proxy {
2150+
opts.push_str(&format!("&proxyHost={}", proxy.host));
2151+
if let Some(port) = proxy.port {
2152+
opts.push_str(&format!("&proxyPort={port}"));
21022153
}
2103-
2104-
if let Some(proxy_username) = proxy_username {
2105-
opts.push_str(&format!("&proxyUsername={proxy_username}"));
2106-
}
2107-
2108-
if let Some(proxy_password) = proxy_password {
2109-
opts.push_str(&format!("&proxyPassword={proxy_password}"));
2154+
if let Some((username, password)) = proxy.authentication.as_ref() {
2155+
opts.push_str(&format!(
2156+
"&proxyUsername={username}&proxyPassword={password}"
2157+
));
21102158
}
21112159
}
21122160

@@ -2689,17 +2737,17 @@ impl ConnectionString {
26892737
parts.zlib_compression = Some(i);
26902738
}
26912739
#[cfg(feature = "socks5-proxy")]
2692-
PROXY_HOST => self.proxy_host = Some(value.to_string()),
2740+
PROXY_HOST => parts.proxy_host = Some(value.to_string()),
26932741
#[cfg(feature = "socks5-proxy")]
26942742
PROXY_PORT => {
26952743
let port = u16::from_str(value)
26962744
.map_err(|_| Error::invalid_argument(format!("invalid proxy port: {value}")))?;
2697-
self.proxy_port = Some(port);
2745+
parts.proxy_port = Some(port);
26982746
}
26992747
#[cfg(feature = "socks5-proxy")]
2700-
PROXY_USERNAME => self.proxy_username = Some(value.to_string()),
2748+
PROXY_USERNAME if !value.is_empty() => parts.proxy_username = Some(value.to_string()),
27012749
#[cfg(feature = "socks5-proxy")]
2702-
PROXY_PASSWORD => self.proxy_password = Some(value.to_string()),
2750+
PROXY_PASSWORD if !value.is_empty() => parts.proxy_password = Some(value.to_string()),
27032751
#[cfg(not(feature = "socks5-proxy"))]
27042752
PROXY_HOST | PROXY_PORT | PROXY_USERNAME | PROXY_PASSWORD => {
27052753
return Err(Error::invalid_argument(format!(

src/client/options/parse.rs

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -165,13 +165,7 @@ impl ClientOptions {
165165
#[cfg(feature = "opentelemetry")]
166166
tracing: None,
167167
#[cfg(feature = "socks5-proxy")]
168-
proxy_host: conn_str.proxy_host,
169-
#[cfg(feature = "socks5-proxy")]
170-
proxy_port: conn_str.proxy_port,
171-
#[cfg(feature = "socks5-proxy")]
172-
proxy_username: conn_str.proxy_username,
173-
#[cfg(feature = "socks5-proxy")]
174-
proxy_password: conn_str.proxy_password,
168+
socks5_proxy: conn_str.socks5_proxy,
175169
}
176170
}
177171
}

0 commit comments

Comments
 (0)