Situation:

Local windows machine runs a very simple HTTP web-server listening to 0.0.0.0:8080. Remote windows machine has OpenSSH server running and I have ssh-credentials but that's all I assume. It may not have internet, I do not have prior access or permission to reconfigure or install things, and/or it may only be capable of communicating through port 22. I want the remote machine to access my local web-server and only it.

Plan:

I call the remote machine with ssh myremote -R 8080:localhost:8080 opening a reverse tunnel and expect to reach my local webserver by issuing an HTTP, GET request, using powershell like so Invoke-WebRequest -Uri "http://localhost:8080" inside the session I have established.

Results:

Invoke-WebRequest : The underlying connection was closed: The connection was closed unexpectedly.
At line:1 char:1
+ Invoke-WebRequest -Uri "http://localhost:8080"
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidOperation: (System.Net.HttpWebRequest:HttpWebRequest) [Invoke-WebRequest], WebException
    + FullyQualifiedErrorId : WebCmdletWebResponseException,Microsoft.PowerShell.Commands.InvokeWebRequestCommand

If I instead establish a tunnel to local port 22, ssh myremote -R 8080:localhost:22, I can successfully use the tunnel with an inner ssh back to my local machine, like ssh localhost -p 8080, assuming I have preconfigured access from the remote public key. With a local network, the remote can also directly call the webserver. That is not what I want to do, but it's clear that the tunnel is established correctly. Additionally, if the remote machine is the same as local, mapping perhaps ssh localhost -R 8081:localhost:8080, then calling Invoke-WebRequest -Uri "http://localhost:8081" has the exact same effect. Other methods, like using browsers to send http request has the same effect. Using forward tunneling from the remote (-L flag) has the same effect. Using any other means of listening to TCP traffic, Like System.Net.Sockets.TCPListener has the same effect (traffic never reaches it). Using PuTTY to establish equivalent tunnels has the same effect.

Single computer powershell procedure to replicate issue:

$port1 = 8080;
$port2 = 8081;
$Listener = [System.Net.Sockets.TcpListener]$port1;
$Listener.Start();
ssh localhost -R "${port2}:localhost:${port1}" powershell Invoke-Webrequest -Uri "http://localhost:$port2"
$Listener.Stop();

Questions:

Why does SSH succeed through the tunnel but anything else I try does not? How do I establish an HTTP or just a TCP connection back to my local server application through the reverse tunnel?


Solution 1:

Windows Vista and newer resolves the loopback domain localhost to ::1 (IPv6) by default and the listening application must expect this. This was not the case for me with all of my software listening to 0.0.0.0 or 127.0.0.1 without support for both protocols.

Solution #1:

Use and configure a library or application that listens to both IPv4 and IPv6 sockets.

Solution #2:

Create the ssh tunnel using IPv4 explicitly either adding the -4 flag or using 127.0.0.1 instead of localhost.

Solution #3:

Apply a prefix policy to the network interface to prioritize 127.0.0.1 or disable IPv6 preference completely. Read this answer for details on this solution.

If you have similar problems and this did not solve the issue, consider the address scope being listened to by both the ssh tunnel entry-point and the server. Don't listen to localhost, ::1 or 127.0.0.1 if these sockets must be reachable remotely. Specify the source IP range explicitly or use ::, 0.0.0.0, * or whatever the software documents as "any" source.