Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

initial support bidirectional, server-initiated channels #19

Closed

Conversation

connor4312
Copy link
Contributor

Channels need not be exclusively client-created, in fact few (if any?)
channels specify directionality, and refer only to a sender and receiver.

This does an initial, somewhat minimal pass at making servers capable of
initiating channels. This is rather rough and I think greater
abstraction is warranted here; I pulled out relevant parsing code into
parsing.rs, but there is logical duplication between the client and
server which I don't have time to disentagle. This also only implements
some channel functionality on the server-side, it's not as complete as
the client.

The significant breaking change here is that server::run_stream now
returns (almost) immediatel with a SessionHandle, similar to the client;
this is used to open channels.

Ultimately this is a big set of changes I would understand if there's
not great desire to merge this upstream :)

Channels need not be exclusively client-created, in fact few (if any?)
channels specify directionality, and refer only to a sender and receiver.

This does an initial, somewhat minimal pass at making servers capable of
initiating channels. This is rather rough and I think greater
abstraction is warranted here; I pulled out relevant parsing code into
`parsing.rs`, but there is logical duplication between the client and
server which I don't have time to disentagle. This also only implements
_some_ channel functionality on the server-side, it's not as complete as
the client.

The significant breaking change here is that `server::run_stream` now
returns (almost) immediatel with a SessionHandle, similar to the client;
this is used to open channels.

Ultimately this is a big set of changes I would understand if there's
not great desire to merge this upstream :)
@connor4312
Copy link
Contributor Author

connor4312 commented Aug 16, 2022

Oh, also -- after implementing the bulk of this, I realized that I didn't actually need server-initiated channels for our particular use case (I thought we needed to implement direct-tcpip from the server, but it was actually forward-tcpip). So I won't be heartbroken if this doesn't make the cut, but I figured I'd get it at least workable in case anyone else needs it.

However I do depend on the client handling server-opened channels (via the new server_channel_* method on the Client), which can be pulled out of this PR into a separate one if desired.

@Eugeny
Copy link
Member

Eugeny commented Aug 17, 2022

Will need some time to wrap my head around it - stay tuned :)

BTW what's the use case for russh in vscode?

@connor4312
Copy link
Contributor Author

connor4312 commented Aug 17, 2022

A couple months ago we released a private preview of the VS Code server. This is powered by the forwarding service being built by a partner team in Azure (public release in a little while) which operates via SSH over websockets. In our private preview, this works by downloading their (C#) library and subprocessing it, but this is undesirable for several reasons, so I've been working to integrate hosting tunnels natively in our new Rust-based VS Code CLI alluded to at the bottom of that blog post.

Since the SDKs are already public and my contribution to add host functionality to the Rust SDK will be OSS soon, I'll just share the docblock comment that explains our flow 🙂

Tunneling communicates via SSH over websockets. Data is sent fairly opaquely in
binary websocket messages. We use Tungstenite for the websocket, and then
wrap it into an AsyncRead/AsyncWrite type that we can give to the websocket
library, russh.

We then connect as a client over SSH. Today, Tunneling doesn't require additional auth in
the SSH connection, and also has a somewhat non-standard "none" value for
its key exchange. This prevents many off-the-shelf SSH implementations from
working. Once a client connects to the other end of the tunnel, it'll
open a new Channel from the server (of type "client-ssh-session-stream").

The stream of data over this channel is actually an SSH client connection.
So, for each client, we create an SSH server instance, and do some more
work to make that channel AsyncRead/AsyncWrite as well. (Note that this
client actually does do a key exchange and its data is E2E encrypted.) Once
established, the host (this program) requests the client to forward the
ports that it via tcpip-forward requests. Clients then open
forwarded-tcpip channels for each new connection.

       ┌───────────┐     ┌───────┐      ┌───────┐
       │Host (this)│     │Service│      │Client │
       └─────┬─────┘     └───┬───┘      └───┬───┘
             │ Connect as    │              │
             ├─SSH client────▶              │
             │               │  Connect to  │
             │               ◀──service ws──┤
             │   Create new  │              │
             ◀──SSH channel ─┤              │
             │               │              │
             │    SSH server handshake.     │
             ├────(Service just proxies ────▶
             │      traffic through)        │
             │               │              │
             ├────tcpip-forward for ports───▶
             │               │              │
             │               │              ◀───asked to
             │               │              │   connect
             ◀────create forwarded-tcpip ───┤
     make    │            channel           │
local tcp ◀──┤               │              │
connection   │               │              │
             ◀ ─ ─ ─ ─forward traffic─ ─ ─ ─▶
             │               │              │
             │               │              │
             ▼               ▼              ▼

@Eugeny
Copy link
Member

Eugeny commented Aug 19, 2022

Wow, glad to be a part of it! :) I've merged the changes manually and added and tested forward-tcpip from the client side of things.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants