Open KazWolfe opened 1 year ago
So, finally came back to this and think I figured out the problem. When binding to http://localhost
as a URL prefix, EndPointManager will only grab the first resolution from Dns.GetHostAddresses()
. On most systems, this will be the IPv6 loopback address of ::1
. The actual resolution for 127.0.0.1
goes completely ignored. The EmbedIO listener will then dutifully set up its listener on ::1
, as we expect.
Because, however, we are also registering to http://127.0.0.1
, EmbedIO binds to both the IPv4 and IPv6 interfaces. When running curl --ipv4 http://localhost:45454/
, we connect to the IPv4-only EndPointListener
which is not aware of the localhost
prefix:
As a result, the server steps through SearchListener
, fails to find an appropriate match, and BindContext
returns false
, killing the connection:
(Of note, while exploring this, I came across issue #165 which had a similar comment - binding to 0.0.0.0
actually creates something that listens on all interfaces, but because the EndPointListener
is not aware of 0.0.0.0
being "special", it also gives up. This is doubly made interesting by the fact that EndPointManager.cs#L96 actually has something in place for this edge case, allowing it to bind to [::]
instead, but I'm on a tangent so I'll get back to the issue at hand.)
As an experiment, I wrote a (very quick and dirty) patch that changed the behavior of GetEpListener
to instead return a list of eligible listeners (assuming more than one is returned by the aforementioned DNS query). This, to my surprise, actually seems to work:
The localhost
prefix is added to both the IPv4 and IPv6 listeners, and cURL can connect to either one and receive the expected response. In addition, the actual IP addresses also defined also can connect and get routed appropriately, bringing everything back to what I would have expected. I'm more than happy to formally submit this patch as a PR, but I'm unsure what else would be needed before then (and what other things are lurking in EndPointListener that should be looked at).
(Edit: The above patch diverges from Mono's behavior, which I suspect is not a desired change. Would it make sense to open this issue with the Mono team directly? As far as I can tell from Mono's code, they'd have the same problem - setting prefixes to 127.0.0.1
and [::1]
would work, but connects with Host: localhost
would be rejected. All this to say EmbedIO v4 can't come soon enough!)
As for why all this came up in the first place: browsers are problematic. Despite the fact that Windows is usually pretty consistent (ha) about returning DNS records for localhost
in the same order, browsers will sometimes decide that they want to swap things around or prefer IPv4 to IPv6 at times. This causes the browser to resolve localhost
to 127.0.0.1
and hit the EndPointListener
with no knowledge that it should be responding to localhost
, because all it knows is IPv6.
Describe the bug When using
HttpListenerMode.EmbedIO
and explicitly listening on both IPv4 (127.0.0.1
) and IPv6 ([::1]
) interfaces in addition tolocalhost
proper, HTTP calls tolocalhost
may fail. However, appropriately restoringHost
may cause connections to succeed again.It appears as though when
localhost
is resolved to127.0.0.1
by the system's DNS, EmbedIO is unable to properly send back a response.To Reproduce
The following code was sufficient as an MCVE on my system:
With the above code, various
curl
commands were executed, each with different successes:When using cURL's verbose mode:
Notably, if resolution is forced to IPv6, everything works as intended:
Expected behavior EmbedIO should behave properly when a request is made to
localhost
when IPv4 is forced.Screenshots
Wireshark logs from a failed request to![image](https://user-images.githubusercontent.com/5192145/218246582-bf18ea8b-b90a-4a85-91a2-f82715445529.png)
localhost
:Desktop:
Additional context
If
WithUrlPrefixes
is set to solelyhttp://localhost
, EmbedIO doesn't even listen on127.0.0.1
, which may be related (and honestly an easier way to tackle this problem?). My intent for listening to the trifecta was to handle dual-stack systems as easily as possible and not have to rely on systems behaving in odd ways. This may additionally be caused by some odd configuration specific to my system, but I have had a few of my own users confirm this issue in the past as well so I suspect it may be a bit more endemic.This problem does not exist when using
HttpListenerMode.Microsoft
, and binding tohttp://localhost
is enough to handle both IPv4 and IPv6 traffic. Ideally, however, I would not need to use the Microsoft listener, as it has caused other issues in the past.