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.
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?
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:
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.
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?
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.
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With