Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 14 additions & 14 deletions crates/jmux-proto/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,27 +85,27 @@ impl DestinationUrl {
let scheme = &s[..scheme_end_idx];
let rest = &s[scheme_end_idx + "://".len()..];

let host_end_idx = rest.rfind(':').ok_or_else(|| Error::InvalidDestinationUrl {
value: s.to_owned(),
Self::with_scheme(scheme, rest)
}
Comment on lines +88 to +89

/// Builds a destination URL from a `<host>:<port>` authority and an explicit scheme.
///
/// Useful when the scheme is known from context (e.g.: a TCP listener) and only the
/// `<host>:<port>` part comes from user input.
pub fn with_scheme(scheme: &str, host_port: &str) -> Result<Self, Error> {
let host_end_idx = host_port.rfind(':').ok_or_else(|| Error::InvalidDestinationUrl {
value: host_port.to_owned(),
reason: "port is missing",
})?;
let host = &rest[..host_end_idx];
let port = &rest[host_end_idx + 1..];
let host = &host_port[..host_end_idx];
let port = &host_port[host_end_idx + 1..];

let port = port.parse().map_err(|_| Error::InvalidDestinationUrl {
value: s.to_owned(),
value: host_port.to_owned(),
reason: "bad port",
})?;
let scheme = SmolStr::new(scheme);
let host = SmolStr::new(host);
let inner = SmolStr::new(s);

Ok(Self {
inner,
scheme,
host,
port,
})
Ok(Self::new(scheme, host, port))
}

pub fn as_str(&self) -> &str {
Expand Down
29 changes: 13 additions & 16 deletions jetsocat/src/listener.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,20 @@ use tracing::Instrument as _;

#[derive(Debug, Clone)]
pub enum ListenerMode {
Tcp { bind_addr: String, destination_url: String },
Http { bind_addr: String },
Socks5 { bind_addr: String },
Tcp {
bind_addr: String,
destination_url: DestinationUrl,
},
Http {
bind_addr: String,
},
Socks5 {
bind_addr: String,
},
}

#[instrument(skip(api_request_tx))]
pub async fn tcp_listener_task(api_request_tx: ApiRequestSender, bind_addr: String, destination_url: String) {
let destination_url = format!("tcp://{destination_url}");

pub async fn tcp_listener_task(api_request_tx: ApiRequestSender, bind_addr: String, destination_url: DestinationUrl) {
let processor = |stream, addr| {
let api_request_tx = api_request_tx.clone();
let destination_url = destination_url.clone();
Expand All @@ -28,14 +33,6 @@ pub async fn tcp_listener_task(api_request_tx: ApiRequestSender, bind_addr: Stri
async move {
debug!("Got request");

let destination_url = match DestinationUrl::parse_str(&destination_url) {
Ok(url) => url,
Err(error) => {
debug!(%error, "Bad request");
return;
}
};

let (sender, receiver) = oneshot::channel();

match api_request_tx
Expand Down Expand Up @@ -63,10 +60,10 @@ pub async fn tcp_listener_task(api_request_tx: ApiRequestSender, bind_addr: Stri
.await;
}
Ok(JmuxApiResponse::Failure { id, reason_code }) => {
debug!(%id, %reason_code, "Channel failure");
warn!(%id, %reason_code, "Couldn’t open JMUX channel for incoming connection");
}
Err(error) => {
debug!(%error, "Couldn't receive API response");
warn!(%error, "Couldnt receive JMUX API response");
}
}
}
Expand Down
7 changes: 5 additions & 2 deletions jetsocat/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ use jetsocat::DoctorOutputFormat;
use jetsocat::listener::ListenerMode;
use jetsocat::pipe::PipeMode;
use jetsocat::proxy::{ProxyConfig, ProxyType, detect_proxy};
use jmux_proxy::JmuxConfig;
use jmux_proxy::{DestinationUrl, JmuxConfig};
use seahorse::{App, Command, Context, Flag, FlagType};
use tokio::runtime;

Expand Down Expand Up @@ -894,9 +894,12 @@ fn parse_listener_mode(arg: &str) -> anyhow::Result<ListenerMode> {
let bind_addr = it.next().context("binding address is missing")?;
let destination_url = it.next().context("destination URL is missing")?;

let destination_url =
DestinationUrl::with_scheme("tcp", destination_url).context("parse destination URL")?;

Ok(ListenerMode::Tcp {
bind_addr: bind_addr.to_owned(),
destination_url: destination_url.to_owned(),
destination_url,
})
}
"socks5-listen" => Ok(ListenerMode::Socks5 {
Expand Down
26 changes: 26 additions & 0 deletions testsuite/tests/cli/jetsocat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -906,6 +906,32 @@ fn forward_negative_repeat_count() {
);
}

/// A TCP listener destination without a port is a client-side argument error and must be
/// reported clearly during CLI parsing, before any listener is bound.
#[test]
fn jmux_proxy_listener_missing_destination_port() {
let output = jetsocat_assert_cmd()
.args([
"jmux-proxy",
"stdio",
"tcp-listen://127.0.0.1:60605/vdownsrv-test1.downhill.loc",
])
.assert()
.failure()
.code(1);

assert_stderr_eq(
&output,
expect![[r#"
Bad <LISTENER>: `tcp-listen://127.0.0.1:60605/vdownsrv-test1.downhill.loc`

Caused by:
0: parse destination URL
1: invalid destination URL `vdownsrv-test1.downhill.loc`: port is missing
"#]],
);
}

#[rstest]
#[tokio::test]
async fn mcp_proxy_smoke_test(#[values(true, false)] http_transport: bool) {
Expand Down
Loading