Skip to content

Commit 4735a0d

Browse files
add CI
1 parent 8cdc95b commit 4735a0d

File tree

8 files changed

+367
-91
lines changed

8 files changed

+367
-91
lines changed

.evergreen/config.yml

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -399,6 +399,14 @@ buildvariants:
399399
tasks:
400400
- happy-eyeballs-task-group
401401

402+
- name: socks5-proxy-no-tls
403+
display_name: "SOCKS5 Proxy (No TLS)"
404+
patchable: false
405+
run_on:
406+
- rhel87-small
407+
tasks:
408+
- test-socks5-proxy
409+
402410
#- name: graviton-legacy
403411
# display_name: "Graviton (legacy versions)"
404412
# run_on:
@@ -1246,6 +1254,24 @@ tasks:
12461254
include_expansions_in_env:
12471255
- PROJECT_DIRECTORY
12481256

1257+
- name: test-socks5-proxy
1258+
commands:
1259+
- func: bootstrap mongo-orchestration
1260+
vars:
1261+
MONGODB_VERSION: rapid
1262+
TOPOLOGY: replica-set
1263+
- command: subprocess.exec
1264+
type: test
1265+
params:
1266+
working_dir: src
1267+
binary: bash
1268+
args:
1269+
- .evergreen/run-socks5-proxy-test.sh
1270+
include_expansions_in_env:
1271+
- PROJECT_DIRECTORY
1272+
- MONGODB_URI
1273+
- DRIVERS_TOOLS
1274+
12491275
#############
12501276
# Functions #
12511277
#############
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
#!/bin/bash
2+
3+
set -o errexit
4+
5+
source .evergreen/env.sh
6+
source .evergreen/cargo-test.sh
7+
8+
FEATURE_FLAGS+=("socks5-proxy")
9+
CARGO_OPTIONS+=("--ignore-default-filter")
10+
11+
# with auth
12+
python $DRIVERS_TOOLS/.evergreen/socks5srv.py --map "127.0.0.1:12345 to 127.0.0.1:27017" --port 1080 --auth username:p4ssw0rd &
13+
AUTH_PID=$!
14+
# without auth
15+
python $DRIVERS_TOOLS/.evergreen/socks5srv.py --map "127.0.0.1:12345 to 127.0.0.1:27017" --port 1081 &
16+
NOAUTH_PID=$!
17+
18+
echo "testing socks5proxy with URI $MONGODB_URI"
19+
20+
set +o errexit
21+
22+
cargo_test socks5_proxy
23+
24+
kill $AUTH_PID
25+
kill $NOAUTH_PID
26+
27+
exit ${CARGO_RESULT}

src/client/csfle/state_machine.rs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -188,8 +188,15 @@ impl CryptExecutor {
188188
.and_then(|tls| tls.get(&provider))
189189
.cloned()
190190
.unwrap_or_default();
191-
let mut stream =
192-
AsyncStream::connect(addr, Some(&TlsConfig::new(tls_options)?)).await?;
191+
// isabeltodo propagate proxy, cache config?
192+
let mut stream = AsyncStream::connect(
193+
addr,
194+
Some(&TlsConfig::new(tls_options)?),
195+
Duration::MAX,
196+
#[cfg(feature = "socks5-proxy")]
197+
None,
198+
)
199+
.await?;
193200
stream.write_all(kms_ctx.message()?).await?;
194201
let mut buf = vec![0];
195202
while kms_ctx.bytes_needed() > 0 {

src/client/options.rs

Lines changed: 90 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,10 @@ const TLS_INSECURE: &str = "tlsinsecure";
5959
const TLS_ALLOW_INVALID_CERTIFICATES: &str = "tlsallowinvalidcertificates";
6060
#[cfg(feature = "openssl-tls")]
6161
const TLS_ALLOW_INVALID_HOSTNAMES: &str = "tlsallowinvalidhostnames";
62+
const PROXY_HOST: &str = "proxyhost";
63+
const PROXY_PORT: &str = "proxyport";
64+
const PROXY_USERNAME: &str = "proxyusername";
65+
const PROXY_PASSWORD: &str = "proxypassword";
6266
const URI_OPTIONS: &[&str] = &[
6367
"appname",
6468
"authmechanism",
@@ -75,10 +79,10 @@ const URI_OPTIONS: &[&str] = &[
7579
"maxpoolsize",
7680
"minpoolsize",
7781
"maxconnecting",
78-
"proxyHost",
79-
"proxyPort",
80-
"proxyUsername",
81-
"proxyPassword",
82+
PROXY_HOST,
83+
PROXY_PORT,
84+
PROXY_USERNAME,
85+
PROXY_PASSWORD,
8286
"readconcernlevel",
8387
"readpreference",
8488
"readpreferencetags",
@@ -993,6 +997,14 @@ pub struct ConnectionString {
993997
/// Overrides the default "mongodb" service name for SRV lookup in both discovery and polling
994998
pub srv_service_name: Option<String>,
995999

1000+
pub proxy_host: Option<String>,
1001+
1002+
pub proxy_port: Option<u16>,
1003+
1004+
pub proxy_username: Option<String>,
1005+
1006+
pub proxy_password: Option<String>,
1007+
9961008
#[serde(serialize_with = "serde_util::serialize_duration_option_as_int_millis")]
9971009
wait_queue_timeout: Option<Duration>,
9981010
tls_insecure: Option<bool>,
@@ -1333,6 +1345,42 @@ impl ClientOptions {
13331345
}
13341346
}
13351347

1348+
#[cfg(feature = "socks5-proxy")]
1349+
{
1350+
if self.proxy_host.is_some() {
1351+
if self
1352+
.hosts
1353+
.iter()
1354+
.any(|address| !matches!(address, ServerAddress::Tcp { .. }))
1355+
{
1356+
return Err(Error::invalid_argument(
1357+
"cannot specify a non-TCP address when connected to a proxy host",
1358+
));
1359+
}
1360+
1361+
if self.proxy_username.is_some() != self.proxy_password.is_some() {
1362+
return Err(Error::invalid_argument(
1363+
"proxy username and proxy password must both be specified or unset",
1364+
));
1365+
}
1366+
} else {
1367+
let error = |option: &str| {
1368+
Error::invalid_argument(format!(
1369+
"cannot specify a proxy {option} without a proxy host"
1370+
))
1371+
};
1372+
if self.proxy_port.is_some() {
1373+
return Err(error("port"));
1374+
}
1375+
if self.proxy_username.is_some() {
1376+
return Err(error("username"));
1377+
}
1378+
if self.proxy_password.is_some() {
1379+
return Err(error("password"));
1380+
}
1381+
}
1382+
}
1383+
13361384
Ok(())
13371385
}
13381386

@@ -1720,6 +1768,10 @@ impl ConnectionString {
17201768
srv_service_name: _,
17211769
wait_queue_timeout,
17221770
tls_insecure,
1771+
proxy_host,
1772+
proxy_port,
1773+
proxy_username,
1774+
proxy_password,
17231775
#[cfg(test)]
17241776
original_uri: _,
17251777
} = self;
@@ -2021,6 +2073,22 @@ impl ConnectionString {
20212073
opts.push_str(&format!("&srvMaxHosts={srv_max_hosts}"));
20222074
}
20232075

2076+
if let Some(proxy_host) = proxy_host {
2077+
opts.push_str(&format!("&proxyHost={proxy_host}"));
2078+
}
2079+
2080+
if let Some(proxy_port) = proxy_port {
2081+
opts.push_str(&format!("&proxyPort={proxy_port}"));
2082+
}
2083+
2084+
if let Some(proxy_username) = proxy_username {
2085+
opts.push_str(&format!("&proxyUsername={proxy_username}"));
2086+
}
2087+
2088+
if let Some(proxy_password) = proxy_password {
2089+
opts.push_str(&format!("&proxyPassword={proxy_password}"));
2090+
}
2091+
20242092
if !opts.is_empty() {
20252093
opts.replace_range(0..1, "?"); // mark start of options
20262094
res.push_str(&opts);
@@ -2599,6 +2667,24 @@ impl ConnectionString {
25992667

26002668
parts.zlib_compression = Some(i);
26012669
}
2670+
#[cfg(feature = "socks5-proxy")]
2671+
PROXY_HOST => self.proxy_host = Some(value.to_string()),
2672+
#[cfg(feature = "socks5-proxy")]
2673+
PROXY_PORT => {
2674+
let port = u16::from_str(value)
2675+
.map_err(|_| Error::invalid_argument(format!("invalid proxy port: {value}")))?;
2676+
self.proxy_port = Some(port);
2677+
}
2678+
#[cfg(feature = "socks5-proxy")]
2679+
PROXY_USERNAME => self.proxy_username = Some(value.to_string()),
2680+
#[cfg(feature = "socks5-proxy")]
2681+
PROXY_PASSWORD => self.proxy_password = Some(value.to_string()),
2682+
#[cfg(not(feature = "socks5-proxy"))]
2683+
PROXY_HOST | PROXY_PORT | PROXY_USERNAME | PROXY_PASSWORD => {
2684+
return Err(Error::invalid_argument(format!(
2685+
"cannot specify {key} if socks5-proxy feature is not enabled"
2686+
)));
2687+
}
26022688

26032689
other => {
26042690
let (jaro_winkler, option) = URI_OPTIONS.iter().fold((0.0, ""), |acc, option| {

src/client/options/parse.rs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -164,15 +164,14 @@ impl ClientOptions {
164164
srv_service_name: conn_str.srv_service_name,
165165
#[cfg(feature = "opentelemetry")]
166166
tracing: None,
167-
// isabeltodo propagate from connstr
168167
#[cfg(feature = "socks5-proxy")]
169-
proxy_host: None,
168+
proxy_host: conn_str.proxy_host,
170169
#[cfg(feature = "socks5-proxy")]
171-
proxy_port: None,
170+
proxy_port: conn_str.proxy_port,
172171
#[cfg(feature = "socks5-proxy")]
173-
proxy_username: None,
172+
proxy_username: conn_str.proxy_username,
174173
#[cfg(feature = "socks5-proxy")]
175-
proxy_password: None,
174+
proxy_password: conn_str.proxy_password,
176175
}
177176
}
178177
}

src/cmap/establish.rs

Lines changed: 13 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,11 @@ use crate::{
2222
},
2323
error::{Error as MongoError, ErrorKind, Result},
2424
hello::HelloReply,
25-
runtime::{self, stream::DEFAULT_CONNECT_TIMEOUT, AsyncStream, TlsConfig},
25+
runtime::{
26+
stream::{Proxy, DEFAULT_CONNECT_TIMEOUT},
27+
AsyncStream,
28+
TlsConfig,
29+
},
2630
sdam::{topology::TopologySpec, HandshakePhase},
2731
};
2832

@@ -55,62 +59,6 @@ pub(crate) struct EstablisherOptions {
5559
proxy: Option<Proxy>,
5660
}
5761

58-
#[derive(Clone)]
59-
#[cfg(feature = "socks5-proxy")]
60-
struct Proxy {
61-
host: String,
62-
port: u16,
63-
authentication: Option<(String, String)>,
64-
}
65-
66-
#[cfg(feature = "socks5-proxy")]
67-
impl Proxy {
68-
fn from_client_options(options: &crate::options::ClientOptions) -> Option<Self> {
69-
let host = options.proxy_host.as_ref()?.clone();
70-
let port = options.proxy_port.unwrap_or(1080);
71-
let authentication = match (&options.proxy_username, &options.proxy_password) {
72-
(Some(username), Some(password)) => Some((username.clone(), password.clone())),
73-
// ClientOptions::validate will return an error if the username and password are not
74-
// provided together or both omitted.
75-
_ => None,
76-
};
77-
Some(Self {
78-
host,
79-
port,
80-
authentication,
81-
})
82-
}
83-
84-
async fn connect(
85-
&self,
86-
address: ServerAddress,
87-
connect_timeout: Duration,
88-
) -> Result<AsyncStream> {
89-
use fast_socks5::client::{Config, Socks5Stream};
90-
91-
let mut config = Config::default();
92-
config.set_connect_timeout(connect_timeout.as_secs());
93-
94-
let stream = if let Some((username, password)) = self.authentication.as_ref() {
95-
Socks5Stream::connect_with_password(
96-
address.to_string(),
97-
self.host.clone(),
98-
self.port,
99-
username.clone(),
100-
password.clone(),
101-
config,
102-
)
103-
.await
104-
} else {
105-
Socks5Stream::connect(address.to_string(), self.host.clone(), self.port, config).await
106-
}
107-
.map_err(|error| ErrorKind::ProxyConnect {
108-
message: error.to_string(),
109-
})?;
110-
Ok(AsyncStream::Socks5(stream))
111-
}
112-
}
113-
11462
impl From<&TopologySpec> for EstablisherOptions {
11563
fn from(spec: &TopologySpec) -> Self {
11664
Self {
@@ -155,15 +103,20 @@ impl ConnectionEstablisher {
155103
connect_timeout,
156104
#[cfg(test)]
157105
test_patch_reply: options.test_patch_reply,
106+
#[cfg(feature = "socks5-proxy")]
107+
proxy: options.proxy,
158108
})
159109
}
160110

161111
async fn make_stream(&self, address: ServerAddress) -> Result<AsyncStream> {
162-
runtime::timeout(
112+
AsyncStream::connect(
113+
address,
114+
self.tls_config.as_ref(),
163115
self.connect_timeout,
164-
AsyncStream::connect(address, self.tls_config.as_ref()),
116+
#[cfg(feature = "socks5-proxy")]
117+
self.proxy.as_ref(),
165118
)
166-
.await?
119+
.await
167120
}
168121

169122
/// Establishes a connection.
@@ -176,8 +129,6 @@ impl ConnectionEstablisher {
176129
let address = pending_connection.address.clone();
177130
let cancellation_receiver = pending_connection.cancellation_receiver.take();
178131

179-
if let Some(ref proxy) = self.proxy {}
180-
181132
let stream = self
182133
.make_stream(address)
183134
.await

0 commit comments

Comments
 (0)