Skip to content

Commit 67a6ba8

Browse files
kanpovEugeny
andauthored
Implement streamlocal-forward for remote => local UDS forwarding (#312)
I left a `// NEED HELP` comment on places where I didn't fully figure out what to do, so I'd really appreciate it if some maintainers helped me out in those places. --------- Co-authored-by: Eugene <inbox@null.page>
1 parent a78d798 commit 67a6ba8

File tree

9 files changed

+278
-3
lines changed

9 files changed

+278
-3
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ This is a fork of [Thrussh](https://nest.pijul.com/pijul/thrussh) by Pierre-Éti
1717
* `direct-tcpip` (local port forwarding)
1818
* `forward-tcpip` (remote port forwarding) ✨
1919
* `direct-streamlocal` (local UNIX socket forwarding, client only) ✨
20+
* `forward-streamlocal` (remote UNIX socket forwarding) ✨
2021
* Ciphers:
2122
* `chacha20-poly1305@openssh.com`
2223
* `aes256-gcm@openssh.com`

russh/src/client/encrypted.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -805,6 +805,17 @@ impl Session {
805805
)
806806
.await?
807807
}
808+
ChannelType::ForwardedStreamLocal(d) => {
809+
confirm();
810+
let channel = self.accept_server_initiated_channel(id, &msg);
811+
client
812+
.server_channel_open_forwarded_streamlocal(
813+
channel,
814+
&d.socket_path,
815+
self,
816+
)
817+
.await?;
818+
}
808819
ChannelType::AgentForward => {
809820
confirm();
810821
client.server_channel_open_agent_forward(id, self).await?
@@ -848,6 +859,12 @@ impl Session {
848859
Some(GlobalRequestResponse::CancelTcpIpForward(return_channel)) => {
849860
let _ = return_channel.send(true);
850861
}
862+
Some(GlobalRequestResponse::StreamLocalForward(return_channel)) => {
863+
let _ = return_channel.send(true);
864+
}
865+
Some(GlobalRequestResponse::CancelStreamLocalForward(return_channel)) => {
866+
let _ = return_channel.send(true);
867+
}
851868
None => {
852869
error!("Received global request failure for unknown request!")
853870
}
@@ -866,6 +883,12 @@ impl Session {
866883
Some(GlobalRequestResponse::CancelTcpIpForward(return_channel)) => {
867884
let _ = return_channel.send(false);
868885
}
886+
Some(GlobalRequestResponse::StreamLocalForward(return_channel)) => {
887+
let _ = return_channel.send(false);
888+
}
889+
Some(GlobalRequestResponse::CancelStreamLocalForward(return_channel)) => {
890+
let _ = return_channel.send(false);
891+
}
869892
None => {
870893
error!("Received global request failure for unknown request!")
871894
}

russh/src/client/mod.rs

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,16 @@ pub enum Msg {
160160
address: String,
161161
port: u32,
162162
},
163+
StreamLocalForward {
164+
/// Provide a channel for the reply result to request a reply from the server
165+
reply_channel: Option<oneshot::Sender<bool>>,
166+
socket_path: String,
167+
},
168+
CancelStreamLocalForward {
169+
/// Provide a channel for the reply result to request a reply from the server
170+
reply_channel: Option<oneshot::Sender<bool>>,
171+
socket_path: String,
172+
},
163173
Close {
164174
id: ChannelId,
165175
},
@@ -571,6 +581,7 @@ impl<H: Handler> Handle<H> {
571581
}
572582
}
573583

584+
// Requests the server to close a TCP/IP forward channel
574585
pub async fn cancel_tcpip_forward<A: Into<String>>(
575586
&self,
576587
address: A,
@@ -596,6 +607,54 @@ impl<H: Handler> Handle<H> {
596607
}
597608
}
598609

610+
// Requests the server to open a UDS forward channel
611+
pub async fn streamlocal_forward<A: Into<String>>(
612+
&mut self,
613+
socket_path: A,
614+
) -> Result<(), crate::Error> {
615+
let (reply_send, reply_recv) = oneshot::channel();
616+
self.sender
617+
.send(Msg::StreamLocalForward {
618+
reply_channel: Some(reply_send),
619+
socket_path: socket_path.into(),
620+
})
621+
.await
622+
.map_err(|_| crate::Error::SendError)?;
623+
624+
match reply_recv.await {
625+
Ok(true) => Ok(()),
626+
Ok(false) => Err(crate::Error::RequestDenied),
627+
Err(e) => {
628+
error!("Unable to receive StreamLocalForward result: {e:?}");
629+
Err(crate::Error::Disconnect)
630+
}
631+
}
632+
}
633+
634+
// Requests the server to close a UDS forward channel
635+
pub async fn cancel_streamlocal_forward<A: Into<String>>(
636+
&self,
637+
socket_path: A,
638+
) -> Result<(), crate::Error> {
639+
let (reply_send, reply_recv) = oneshot::channel();
640+
self.sender
641+
.send(Msg::CancelStreamLocalForward {
642+
reply_channel: Some(reply_send),
643+
socket_path: socket_path.into(),
644+
})
645+
.await
646+
.map_err(|_| crate::Error::SendError)?;
647+
648+
match reply_recv.await {
649+
Ok(true) => Ok(()),
650+
Ok(false) => Err(crate::Error::RequestDenied),
651+
Err(e) => {
652+
error!("Unable to receive CancelStreamLocalForward result: {e:?}");
653+
Err(crate::Error::Disconnect)
654+
}
655+
}
656+
}
657+
599658
/// Sends a disconnect message.
600659
pub async fn disconnect(
601660
&self,
@@ -1050,6 +1109,14 @@ impl Session {
10501109
address,
10511110
port,
10521111
} => self.cancel_tcpip_forward(reply_channel, &address, port),
1112+
Msg::StreamLocalForward {
1113+
reply_channel,
1114+
socket_path,
1115+
} => self.streamlocal_forward(reply_channel, &socket_path),
1116+
Msg::CancelStreamLocalForward {
1117+
reply_channel,
1118+
socket_path,
1119+
} => self.cancel_streamlocal_forward(reply_channel, &socket_path),
10531120
Msg::Disconnect {
10541121
reason,
10551122
description,
@@ -1551,6 +1618,17 @@ pub trait Handler: Sized + Send {
15511618
Ok(())
15521619
}
15531620

1621+
// Called when the server opens a channel for a new remote UDS forwarding connection
1622+
#[allow(unused_variables)]
1623+
async fn server_channel_open_forwarded_streamlocal(
1624+
&mut self,
1625+
channel: Channel<Msg>,
1626+
socket_path: &str,
1627+
session: &mut Session,
1628+
) -> Result<(), Self::Error> {
1629+
Ok(())
1630+
}
1631+
15541632
/// Called when the server opens an agent forwarding channel
15551633
#[allow(unused_variables)]
15561634
async fn server_channel_open_agent_forward(

russh/src/client/session.rs

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -293,7 +293,7 @@ impl Session {
293293

294294
/// Requests cancellation of TCP/IP forwarding from the server
295295
///
296-
/// If `want_reply` is `true`, returns a oneshot receiveing the server's reply:
296+
/// If `reply_channel` is not None, sets want_reply and returns the server's response via the channel,
297297
/// `true` for a success message, or `false` for failure
298298
pub fn cancel_tcpip_forward(
299299
&mut self,
@@ -318,6 +318,58 @@ impl Session {
318318
}
319319
}
320320

321+
/// Requests a UDS forwarding from the server, `socket path` being the server side socket path.
322+
///
323+
/// If `reply_channel` is not None, sets want_reply and returns the server's response via the channel,
324+
/// `true` for a success message, or `false` for failure
325+
pub fn streamlocal_forward(
326+
&mut self,
327+
reply_channel: Option<oneshot::Sender<bool>>,
328+
socket_path: &str,
329+
) {
330+
if let Some(ref mut enc) = self.common.encrypted {
331+
let want_reply = reply_channel.is_some();
332+
if let Some(reply_channel) = reply_channel {
333+
self.open_global_requests.push_back(
334+
crate::session::GlobalRequestResponse::StreamLocalForward(reply_channel),
335+
);
336+
}
337+
push_packet!(enc.write, {
338+
enc.write.push(msg::GLOBAL_REQUEST);
339+
enc.write
340+
.extend_ssh_string(b"streamlocal-forward@openssh.com");
341+
enc.write.push(want_reply as u8);
342+
enc.write.extend_ssh_string(socket_path.as_bytes());
343+
});
344+
}
345+
}
346+
347+
/// Requests cancellation of UDS forwarding from the server
348+
///
349+
/// If `reply_channel` is not None, sets want_reply and returns the server's response via the channel,
350+
/// `true` for a success message and `false` for failure.
351+
pub fn cancel_streamlocal_forward(
352+
&mut self,
353+
reply_channel: Option<oneshot::Sender<bool>>,
354+
socket_path: &str,
355+
) {
356+
if let Some(ref mut enc) = self.common.encrypted {
357+
let want_reply = reply_channel.is_some();
358+
if let Some(reply_channel) = reply_channel {
359+
self.open_global_requests.push_back(
360+
crate::session::GlobalRequestResponse::CancelStreamLocalForward(reply_channel),
361+
);
362+
}
363+
push_packet!(enc.write, {
364+
enc.write.push(msg::GLOBAL_REQUEST);
365+
enc.write
366+
.extend_ssh_string(b"cancel-streamlocal-forward@openssh.com");
367+
enc.write.push(want_reply as u8);
368+
enc.write.extend_ssh_string(socket_path.as_bytes());
369+
});
370+
}
371+
}
372+
321373
pub fn send_keepalive(&mut self, want_reply: bool) {
322374
self.open_global_requests
323375
.push_back(crate::session::GlobalRequestResponse::Keepalive);

russh/src/parsing.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@ impl OpenChannelMessage {
3232
}
3333
b"direct-tcpip" => ChannelType::DirectTcpip(TcpChannelInfo::new(r)?),
3434
b"forwarded-tcpip" => ChannelType::ForwardedTcpIp(TcpChannelInfo::new(r)?),
35+
b"forwarded-streamlocal@openssh.com" => {
36+
ChannelType::ForwardedStreamLocal(StreamLocalChannelInfo::new(r)?)
37+
}
3538
b"auth-agent@openssh.com" => ChannelType::AgentForward,
3639
t => ChannelType::Unknown { typ: t.to_vec() },
3740
};
@@ -91,6 +94,7 @@ pub enum ChannelType {
9194
},
9295
DirectTcpip(TcpChannelInfo),
9396
ForwardedTcpIp(TcpChannelInfo),
97+
ForwardedStreamLocal(StreamLocalChannelInfo),
9498
AgentForward,
9599
Unknown {
96100
typ: Vec<u8>,
@@ -105,6 +109,21 @@ pub struct TcpChannelInfo {
105109
pub originator_port: u32,
106110
}
107111

112+
#[derive(Debug)]
113+
pub struct StreamLocalChannelInfo {
114+
pub socket_path: String,
115+
}
116+
117+
impl StreamLocalChannelInfo {
118+
fn new(r: &mut Position) -> Result<Self, crate::Error> {
119+
let socket_path = std::str::from_utf8(r.read_string().map_err(crate::Error::from)?)
120+
.map_err(crate::Error::from)?
121+
.to_owned();
122+
123+
Ok(Self { socket_path })
124+
}
125+
}
126+
108127
impl TcpChannelInfo {
109128
fn new(r: &mut Position) -> Result<Self, crate::Error> {
110129
let host_to_connect = std::str::from_utf8(r.read_string().map_err(crate::Error::from)?)

russh/src/server/encrypted.rs

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1023,6 +1023,40 @@ impl Session {
10231023
}
10241024
Ok(())
10251025
}
1026+
b"streamlocal-forward@openssh.com" => {
1027+
let server_socket_path =
1028+
std::str::from_utf8(r.read_string().map_err(crate::Error::from)?)
1029+
.map_err(crate::Error::from)?;
1030+
debug!("handler.streamlocal_forward {:?}", server_socket_path);
1031+
let result = handler
1032+
.streamlocal_forward(server_socket_path, self)
1033+
.await?;
1034+
if let Some(ref mut enc) = self.common.encrypted {
1035+
if result {
1036+
push_packet!(enc.write, enc.write.push(msg::REQUEST_SUCCESS))
1037+
} else {
1038+
push_packet!(enc.write, enc.write.push(msg::REQUEST_FAILURE))
1039+
}
1040+
}
1041+
Ok(())
1042+
}
1043+
b"cancel-streamlocal-forward@openssh.com" => {
1044+
let socket_path =
1045+
std::str::from_utf8(r.read_string().map_err(crate::Error::from)?)
1046+
.map_err(crate::Error::from)?;
1047+
debug!("handler.cancel_streamlocal_forward {:?}", socket_path);
1048+
let result = handler
1049+
.cancel_streamlocal_forward(socket_path, self)
1050+
.await?;
1051+
if let Some(ref mut enc) = self.common.encrypted {
1052+
if result {
1053+
push_packet!(enc.write, enc.write.push(msg::REQUEST_SUCCESS))
1054+
} else {
1055+
push_packet!(enc.write, enc.write.push(msg::REQUEST_FAILURE))
1056+
}
1057+
}
1058+
Ok(())
1059+
}
10261060
_ => {
10271061
if let Some(ref mut enc) = self.common.encrypted {
10281062
push_packet!(enc.write, {
@@ -1087,7 +1121,7 @@ impl Session {
10871121
Some(GlobalRequestResponse::CancelTcpIpForward(return_channel)) => {
10881122
let _ = return_channel.send(true);
10891123
}
1090-
None => {
1124+
_ => {
10911125
error!("Received global request failure for unknown request!")
10921126
}
10931127
}
@@ -1105,7 +1139,7 @@ impl Session {
11051139
Some(GlobalRequestResponse::CancelTcpIpForward(return_channel)) => {
11061140
let _ = return_channel.send(false);
11071141
}
1108-
None => {
1142+
_ => {
11091143
error!("Received global request failure for unknown request!")
11101144
}
11111145
}
@@ -1211,6 +1245,16 @@ impl Session {
12111245
}
12121246
result
12131247
}
1248+
ChannelType::ForwardedStreamLocal(_) => {
1249+
if let Some(ref mut enc) = self.common.encrypted {
1250+
msg.fail(
1251+
&mut enc.write,
1252+
msg::SSH_OPEN_ADMINISTRATIVELY_PROHIBITED,
1253+
b"Unsupported channel type",
1254+
);
1255+
}
1256+
Ok(false)
1257+
}
12141258
ChannelType::AgentForward => {
12151259
if let Some(ref mut enc) = self.common.encrypted {
12161260
msg.fail(

russh/src/server/mod.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -523,6 +523,24 @@ pub trait Handler: Sized {
523523
) -> Result<bool, Self::Error> {
524524
Ok(false)
525525
}
526+
527+
#[allow(unused_variables)]
528+
async fn streamlocal_forward(
529+
&mut self,
530+
socket_path: &str,
531+
session: &mut Session,
532+
) -> Result<bool, Self::Error> {
533+
Ok(false)
534+
}
535+
536+
#[allow(unused_variables)]
537+
async fn cancel_streamlocal_forward(
538+
&mut self,
539+
socket_path: &str,
540+
session: &mut Session,
541+
) -> Result<bool, Self::Error> {
542+
Ok(false)
543+
}
526544
}
527545

528546
#[async_trait]

0 commit comments

Comments
 (0)