Forward local port to remote socket

15 views
Skip to first unread message

Alex Rogozhnikov

unread,
Nov 18, 2022, 7:42:31 PM11/18/22
to asyncssh-users
Hi Ron, and thanks for asyncssh!

I'm currently trying to mimic this ssh command with asyncssh:
ssh -L 9999:/sockets/mysocket myhost

That is, forward local port to remote socket. 
I see API for forwarding port to port and socket to socket, but don't see how to mix the two.

Thank you!
Alex

Ron Frederick

unread,
Nov 18, 2022, 9:37:09 PM11/18/22
to Alex Rogozhnikov, asyncssh-users
Hi Alex,
I must admit this is a use case I never considered when building the AsyncSSH API for forwarding. I’m not even sure at the time that I knew OpenSSH was capable of crossing over like this.

While there’s no built-in function to do this today, the building blocks are there. You’d basically start from something like the forward_local_port() function but replace the tunnel_connection() method defined inside it with something more like the tunnel_connection() from the forward_local_path() function.

Unfortunately, the current implementation relies on a create_tcp_forward_listener() function which is not public in AsyncSSH, and that in turn brings in some other local classes like SSHForwardListener. So, it might be difficult to do this from outside without an internal import.

More specifically, the following seems to work:

import asyncio, asyncssh
from asyncssh.listener import create_tcp_forward_listener

async def cross_forward(conn, listen_host, listen_port, dest_path):
    async def tunnel_connection(session_factory, orig_host, orig_port):
        return await conn.create_unix_connection(session_factory, dest_path)

    loop = asyncio.get_event_loop()

    listener = await create_tcp_forward_listener(conn, loop, tunnel_connection,
                                                 listen_host, listen_port)

    if listen_port == 0:
        listen_port = listener.get_port()

    return listener

async def run_client() -> None:
    async with asyncssh.connect('localhost') as conn:
        listener = await cross_forward(conn, '', 8080, '/tmp/socket')
        await listener.wait_closed()

asyncio.run(run_client())

I’ll have to think about what kind of naming to use if I did want to cover the two crossover cases. I suppose it could be something like forward_local_port_to_path() and forward_local_path_to_port(), with appropriate adjustments in the arguments to each. I’m not sure how common a use case this is, but if I can do it without much disruption, I’d consider adding it.

Can you try out the above and let me know if it works for you?
-- 
Ron Frederick
ro...@timeheart.net



Alex Rogozhnikov

unread,
Nov 18, 2022, 10:09:31 PM11/18/22
to asyncssh-users
> Can you try out the above and let me know if it works for you?

Just tried, and yes it works. Thank you, Ron!

> I’ll have to think about what kind of naming to use if I did want to cover the two crossover cases.

I think you prefer to be descriptive and specific in each function, but I believe for user it may be more convenient having two unmbrella methods like

forward_local(conn, local_socket, remote_socket)
forward_remote(conn, remote_socket, local_socket)

where socket is either TCP socket tuple (host, port) or unix socket (just str)


Cheers,
Alex

Ron Frederick

unread,
Nov 19, 2022, 1:43:22 PM11/19/22
to Alex Rogozhnikov, asyncssh-users
Hi Alex,

If I were designing the forwarding with the cross-over case in mind to begin with, I probably would have considered something like this here. While I could still add something like this now, it would probably mean deprecating the existing APIs, since I don’t think I’d want to keep both variants long-term. More importantly, though, no other AsyncSSH APIs currently take host & port parameters combined into a tuple like this. It would be a bit of a departure from other functions to do that here and not in other places. For example, create_connection() and create_server() pass host & port as separate arguments. In that case, I was modeling the functions after the existing asyncio functions of the same name. There’s also the question of what to do with forward_socks(), which also takes host and port as separate arguments and only supports listening and forwarding via TCP and not UNIX domain sockets. Having a forward_local() and forward_remote() which follow a different convention than forward_socks() doesn’t seem right.

It would be fairly simple for a caller to provide a wrapper which determined which API to call based on the argument types, if that’s needed. It isn’t nearly as elegant, I’ll admit, but I think it may be less disruptive to the existing code out there (both in AsyncSSH and in its callers).
Visit the AsyncSSH home page at http://asyncssh.timeheart.net
---
You received this message because you are subscribed to the Google Groups "asyncssh-users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to asyncssh-user...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/asyncssh-users/f1aadab4-d9cf-4ef3-8f35-91e74491857an%40googlegroups.com.
-- 
Ron Frederick
ro...@timeheart.net



Ron Frederick

unread,
Nov 24, 2022, 1:06:13 PM11/24/22
to Alex Rogozhnikov, asyncssh-users

Alex Rogozhnikov

unread,
Nov 26, 2022, 4:34:07 PM11/26/22
to asyncssh-users
that's great, thank you!
Reply all
Reply to author
Forward
0 new messages