3

I’m trying to understand how serveo.net establishes everyone’s reverse proxy. From my understanding of ssh one port can only manage one connection yet you can create a reverse proxy on serveo via ssh -R 80:localhost:3000 serveo.net.

Wondering how this might work theoretically and how it handles it without needing any pub keys or password to login to the server as well

2
  • In generral, servers fork a new thread or process to handle the current client, while the server goes back to listening for the next client.
    – waltinator
    Commented May 29 at 20:49
  • 1
    "From my understanding of ssh one port can only manage one connection": false. (1) SSH has nothing to do with it; (2) every TCP server accepts on a port which results in a connection to the same port from whatever the client IP:port happens to be. Have a look at any netstat output for confirmation. You will usually see > 0 connections in state ESTABLISHED with the same port number that also shows in LISTEN state. The key feature here is that it isn't the local port number that has to be unique, it is the tuple {local IP, local port, remote IP, remote port}.
    – user207421
    Commented May 30 at 9:45

1 Answer 1

7

With the command line ssh -R 80:localhost:3000 serveo.net, your SSH client connects to the port 22 of serveo.net as usual, but instructs the remote server to listen for incoming traffic on port 80 on the server side and pass it through the SSH tunnel to your SSH client, which will then forward it to localhost:3000.

But the serveo.net SSH server will not follow that instruction quite literally, as we can see later.

If you add the option -vvv to the command, you will be able to see a lot of client-side debug output. It indicates that after the initial crypto algorithm and session key negotiation the serveo.net SSH server tells the client that it will accept two forms of authentication: either SSH key authentication or keyboard-interactive authentication.

debug3: receive packet: type 51                                                                                                                                
debug1: Authentications that can continue: publickey,keyboard-interactive                                                                                      
debug3: start over, passed a different list publickey,keyboard-interactive                                                                                     
debug3: preferred gssapi-with-mic,publickey,keyboard-interactive,password                                                                                      
debug3: authmethod_lookup publickey                                                                                                                            
debug3: remaining preferred: keyboard-interactive,password

After an attempt to use key-based authentication fails, keyboard-interactive authentication is tried. There, something funny happens:

debug3: authmethod_is_enabled keyboard-interactive
debug1: Next authentication method: keyboard-interactive
debug2: userauth_kbdint
debug3: send packet: type 50
debug2: we sent a keyboard-interactive packet, wait for reply
debug3: receive packet: type 52
Authenticated to serveo.net. ([138.68.79.95]:22) using "keyboard-interactive".

Basically, keyboard-interactive authentication is not restricted to a single password prompt, but allows the server to tell the client what thing or things to prompt from the user. This allows things like one-time access codes (RSA SecurID, TOTP) and other challenge/response type authentications. But the server just announces the authentication method as successfully completed as soon as the client tells it wants to use it. In human terms, the conversation would look like this:

 Server: "I support the following authentication types: public keys, and keyboard-interactive authentication."
 Client: "Let's try public keys. I have keys with public key hashes X, Y and Z, do you want to try any of those?"
 Server: "I don't think so, sorry."
 Client: "Then I'm out of authentication keys, let's try keyboard-interactive authentication. Tell me what to prompt from the user."
 Server: "No need to prompt anything, authentication is successfully completed."
 Client: "HUH??? Well, if you say so..."

Then the client requests TCP forwarding according to the -R option specified to the ssh command:

debug1: Remote connections from LOCALHOST:80 forwarded to local address localhost:3000
<... a lot of shell session setup stuff being ignored...>
debug1: remote forward success for: listen 80, connect localhost:3000
debug2: forwarding_success: -1 expected forwarding replies remaining
Forwarding HTTP traffic from https://c01ba6042e029dfd24f48504c8ccad61.serveo.net

So the remote server claims to have set up the forwarding exactly as requested, but the message sent by the server to the user indicates the server actually listens for incoming traffic at the specified HTTPS URL, which includes a connection-specific hostname part. It also says it's forwarding the traffic as HTTP (only).

As a result, port 80 is not actually used at all: the incoming connections should be directed at the HTTPS port, which is 443. The serveo.net server will terminate the TLS encryption of HTTPS at their end, and pass the data to their sshd which will transfer it within the encrypted SSH connection (initiated by you to port 22 of serveo.net) to your SSH client, which will then forward the decrypted data to port 3000 on your local host.

By restricting the forwarded traffic to HTTP(S) only, the server gets one more piece of information to tell the incoming connections apart: every HTTP/HTTPS request needs to include the Host: <destination host name> header in the request. With HTTPS, the same information can (and in practice must) be encoded in the TLS protocol level, with the Server Name Indication (SNI), so that the destination hostname information will be available in the TLS certificate negotiation phase too.

This is exactly the same technique that allows any modern web hosting provider to serve any number of websites with different hostnames using one IP address and one incoming TCP port.

Note that an outbound TCP connection will normally reserve the entire port for a single connection at the connecting side, but a single port that is configured to listen for inbound connections can serve as many connections as the server can handle. This is a basic feature of the TCP protocol. This is necessary to make the standardization of destination port numbers feasible, like 22 for SSH, 80 for HTTP, 443 for HTTPS, and so on...

By definition, a TCP connection is identified by four pieces of information:

  • IP address of party A
  • port number of party A
  • IP address of party B
  • port number of party B

Note that there are two port numbers, not just one. Normally, the port number at the side initiating the connection (source port number, if you will) is left for the operating system to determine, and it will just allocate the first free port in some OS-specific range of port numbers.

Sometimes we humans forget that there are two port numbers involved in each TCP connection, because everyone just pays attention to the destination port number.

2
  • 2
    It's not wrong, but I don't really understand what the kbd-interactive auth mechanism has to do with any of it? Commented May 30 at 8:16
  • @u1686_grawity It handles the second question in the original post
    – Ferrybig
    Commented May 30 at 9:50

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .