Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

HttpListener: The requested address is not valid in this context

Tags:

c#

http

mono

When creating an HttpListener object using

var server = new HttpListener();
server.Prefixes.Add("http://*:8080/");
server.Start();

everything works fine. However, when I use

var server = new HttpListener();
server.Prefixes.Add("http://demindiro.com:8080/");
server.Start();

it throws System.Net.Sockets.SocketException (0x80004005): The requested address is not valid in this context (full stacktrace below).

After googling the exception, it was rather clear it had something to do with the address used. After digging in the Mono source code for HttpListener and EndPointManager I determined the issue probably lies within this section of code (at GetEPListener):

static EndPointListener GetEPListener (string host, int port, HttpListener listener, bool secure)
{
    IPAddress addr;
    if (host == "*")
        addr = IPAddress.Any;
    else if (IPAddress.TryParse(host, out addr) == false){
        try {
#pragma warning disable 618
            IPHostEntry iphost = Dns.GetHostByName(host);
#pragma warning restore 618
            if (iphost != null)
                addr = iphost.AddressList[0]; // <---
            else
                addr = IPAddress.Any;
        } catch {
            addr = IPAddress.Any;
        }
        // ...    
    }

It occurs to me that in almost all cases ÌPAddress.Any is used except when it can associate an external IP address with the given host name.

However, I can only assume this was intentional because it is rather explicitly written and it seems HttpListener works just fine for other developers, so at this point, I'm clueless as to what is going wrong here.


Stacktrace:
Unhandled Exception:
System.Net.Sockets.SocketException (0x80004005): The requested address is not valid in this context
  at System.Net.Sockets.Socket.Bind (System.Net.EndPoint localEP) [0x00043] in <50d80b08c1a5449282b22aedf03ce925>:0 
  at System.Net.EndPointListener..ctor (System.Net.HttpListener listener, System.Net.IPAddress addr, System.Int32 port, System.Boolean secure) [0x00047] in <50d80b08c1a5449282b22aedf03ce925>:0 
  at System.Net.EndPointManager.GetEPListener (System.String host, System.Int32 port, System.Net.HttpListener listener, System.Boolean secure) [0x0009d] in <50d80b08c1a5449282b22aedf03ce925>:0 
  at System.Net.EndPointManager.AddPrefixInternal (System.String p, System.Net.HttpListener listener) [0x0005e] in <50d80b08c1a5449282b22aedf03ce925>:0 
  at System.Net.EndPointManager.AddListener (System.Net.HttpListener listener) [0x0009c] in <50d80b08c1a5449282b22aedf03ce925>:0 
  at System.Net.HttpListener.Start () [0x0000f] in <50d80b08c1a5449282b22aedf03ce925>:0 
  at Playground.Playground.StartHttpListener () [0x00016] in <d51cc6c047ee47c9a05c5e174876cbec>:0 
  at Playground.Playground.Main (System.String[] args) [0x00000] in <d51cc6c047ee47c9a05c5e174876cbec>:0 
[ERROR] FATAL UNHANDLED EXCEPTION: System.Net.Sockets.SocketException (0x80004005): The requested address is not valid in this context
  at System.Net.Sockets.Socket.Bind (System.Net.EndPoint localEP) [0x00043] in <50d80b08c1a5449282b22aedf03ce925>:0 
  at System.Net.EndPointListener..ctor (System.Net.HttpListener listener, System.Net.IPAddress addr, System.Int32 port, System.Boolean secure) [0x00047] in <50d80b08c1a5449282b22aedf03ce925>:0 
  at System.Net.EndPointManager.GetEPListener (System.String host, System.Int32 port, System.Net.HttpListener listener, System.Boolean secure) [0x0009d] in <50d80b08c1a5449282b22aedf03ce925>:0 
  at System.Net.EndPointManager.AddPrefixInternal (System.String p, System.Net.HttpListener listener) [0x0005e] in <50d80b08c1a5449282b22aedf03ce925>:0 
  at System.Net.EndPointManager.AddListener (System.Net.HttpListener listener) [0x0009c] in <50d80b08c1a5449282b22aedf03ce925>:0 
  at System.Net.HttpListener.Start () [0x0000f] in <50d80b08c1a5449282b22aedf03ce925>:0 
  at Playground.Playground.StartHttpListener () [0x00016] in <d51cc6c047ee47c9a05c5e174876cbec>:0 
  at Playground.Playground.Main (System.String[] args) [0x00000] in <d51cc6c047ee47c9a05c5e174876cbec>:0

PS: I explicitly want to use demindiro.com, not *.

PS2: I know there are many topics concerning this particular exception, but none seem to concern HttpListener.Start() throwing this exception.


UPDATE I've googled my issue again but this time I looked specifically for TcpListener and I found this answer: https://stackoverflow.com/a/17092670/7327379

The TcpListener can only be bound to a local IP Address of the computer that runs it. So the IP you're specifying isn't an IP of the local machine. Your public IP isn't the same IP as your local machine, especially if you're using some kind of NAT.

If I recall correctly, it's common to just do IPAddress.Any as your IP to initialise the listener.

So if I get this straight, this means that to bind to my external IP, I somehow have to go from this

Server <-- local IP --> Modem <-- external IP --> The Internet

to this

Server <-- external IP --> The Internet

Is this correct? If yes, how do you connect a server to the internet without a modem? Or what else should I do?

like image 863
Demindiro Avatar asked Mar 13 '18 23:03

Demindiro


2 Answers

Considering that http://demindiro.com appears to work, this might be redundant. However...

There are a few things here that you need to be aware of:

Binding via host name is bad

In general binding by host name is A Bad Idea™. If the host name resolves to multiple addresses - localhost when you have IPv6 enabled for instance, or when you have multiple IPv4 addresses on the same machine, etc - you don't get to control which of those addresses it binds to. In the case of localhost you can be (sometimes unpleasantly) surprised to find that it binds to ::1 but not 127.0.0.1.

The problem here however is that demindiro.com is a name that doesn't resolve to an internal IP address that is present on the computer, so you can't bind to it directly. To be precise, demindiro.com is a host name that resolves (via DNS) to the IPv4 address 109.132.169.132 which is (almost certainly) not an IP address that your computer has.

Binding to a host name doesn't bind to a host name

Instead it binds to the first IP address that the host name resolves to on the machine. No more, no less.

Let's say you modify your host file to have demindiro.com resolve (on your machine only) to a valid local IPv4 address, like 192.168.0.123. If I am on your network and I make a connection to that address, I get your service. Doesn't matter what host name I am trying to get to, as long as it resolves to that address I'm going to get a connection. I could connect directly via IP, or any host name that resolves to that IP, and you will never know.

It's only after a connection has been made and the request has been parsed that you can (sometimes) detect which host name was the target of the request.

In the case of HTTP(S) requests the host name is generally sent as a part of the request. This allows web servers to host more than a single site on the same address. The web server receives the request, figures out where it should be routed among the local sites, then sends the request on to the resolved site. It has been a bit more difficult to do in HTTPS, but that problem is mostly solved by SNI.

But if I make an HTTP 1.0 request to your service using an absolute path with no hostname... then what?

Bindings can only be local

In other words, you can't bind to an address that isn't present on the machine running your code. More to the point, you can't directly bind to the public address of your internet connection unless it terminates directly on your machine. Since not many of us are running dial-up or bridging PPPoE to our desktops, the address you're trying to bind to is probably not available.

In general what you need to do is set up some sort of NAT redirect on your internet connection to translate incoming requests on port 8080 to the public address 109.132.169.132 and redirect them to your internal address (192.168.0.123 or whatever).

That's assuming that demindiro.com resolves to your public IP address. If not then you've got all sorts of other issues. NAT across a public network is... weird. Every NAT you add in the chain adds more complexity, more points of failure, etc.

like image 98
Corey Avatar answered Nov 15 '22 00:11

Corey


You can visualise your network something like below

local machine -> local interface 
              -> interface 1 (Private IP) <-> External IP
              -> interface 2 (Private IP2) <-> External IP

Now in most cases your VM in the cloud will have 1 network interface and one local interface. Local interfaces is the loopback interface and the interface 1 will have a Private IP.

Now when you use below in your code

server.Prefixes.Add("http://*:8080/");

It means bind to all interfaces on your machine. Which means if you do

curl http://localhost:8080

or

curl http://<privateIP>:8080

Both would work.

Now if you map example.com to the externalIP run below command it will work as well

curl http://example.com:8080

This is because your example.com maps to <extrenalIP>, which will forward all traffic to the <privateIP>. Now when you use below in your . code

server.Prefixes.Add("http://example.com:8080/");

It translate to

server.Prefixes.Add("http://<externalIP>:8080/");

And this not a valid IP in the context, only the 127.0.0.1 and the <PrivateIP> of the machine are valid. So if you have multiple interfaces then only you should worry about not using *. Now if you have multiple interfaces and your a specific interface to listen for your domain then you will make a entry of the same in your /etc/hosts file like below

<PrivateIP> example.com

And then you can use

server.Prefixes.Add("http://example.com:8080/");

Now if your concern is that someone can point to your machine with a different host name and you don't want to server to respond in such a case. Then you will have to check for the Host header in your code and reject the request based on the same. Or you can use a webserver like nginx and only respond to request directed towards example.com

like image 21
Tarun Lalwani Avatar answered Nov 15 '22 00:11

Tarun Lalwani