Skip to content
This repository has been archived by the owner on Apr 22, 2023. It is now read-only.

Node v0.12: HTTP server with implicit address binding reports addresses in IPv6 format? #9195

Closed
mildmojo opened this issue Feb 11, 2015 · 32 comments

Comments

@mildmojo
Copy link

I'm not completely sure what I'm seeing. I first noticed that I was getting different results for http.IncomingMessage.connection.remoteAddress between v0.10.36 and v0.12.0 on OSX 10.8.5:

var http = require('http');

var server = http.createServer(function(req, res) {
    res.write(req.connection.remoteAddress + '\n');
    res.write(server.address().address + '\n');
    res.end();
});
server.listen(8080);

/*
$ curl localhost:8080 # v0.10.36
127.0.0.1
127.0.0.1

$ curl localhost:8080 # v0.12.0
::ffff:127.0.0.1
::
*/

If I explicitly bind the server to 127.0.0.1, v0.12 matches v0.10.36. The docs for http.Server.listen still say "If the hostname is omitted, the server will accept connections directed to any IPv4 address (INADDR_ANY)." I expected to get an IPv4-formatted address from .remoteAddress.

Is this an intended API change? Thanks.

@trevnorris
Copy link

It's dual stack by default, hence why you see the ::ffff:127.0.0.1.

/cc @indutny

@pehlert
Copy link

pehlert commented Feb 12, 2015

I was hit by this as well after the upgrade. Does this depend on system configuration, or can we safely assume that from now on, IPv4 addresses will be represented this way?

@trevnorris
Copy link

@pehlert If the system supports IPv6 then yes.

@cjihrig
Copy link

cjihrig commented Feb 12, 2015

@trevnorris this code ignores the desired address family, even if one is present since no address was provided. Do you think this is worth updating?

@trevnorris
Copy link

@cjihrig You're right, that is a strange discrepancy between our documentation and how it operates. Docs say:

If the host is omitted, the server will accept connections directed to any IPv4 address.

Which means I'd expect an IPv4 address. So either a functional change, or a document change is necessary.

/cc @indutny

@mildmojo
Copy link
Author

Also, when started without an explicit address, http.Server.address().address used to return '127.0.0.1' and now returns '::'. I suppose you could look at that change either way; the old way didn't indicate the server was listening on all addresses and the new way doesn't report an address the at which the server can be reached.

The change sort of breaks my world, so for now I'll explicitly bind to '0.0.0.0' to coerce the server into IPv4 mode (per the code @cjihrig linked). This stuff would affect #7645's request for docs, too.

@cjihrig
Copy link

cjihrig commented Feb 13, 2015

@tjfontaine any opinion on whether this should be a documentation or behavior change?

@skoranga
Copy link

Seeing the same behavior. Any update on this issue?

yelworc added a commit to yelworc/eleven-server that referenced this issue Feb 27, 2015
yelworc added a commit to yelworc/eleven-server that referenced this issue Feb 27, 2015
yelworc added a commit to yelworc/eleven-server that referenced this issue Feb 27, 2015
yelworc added a commit to yelworc/eleven-server that referenced this issue Mar 1, 2015
@cjihrig
Copy link

cjihrig commented Mar 7, 2015

@joyent/node-coreteam do any of you have a preference on whether we should change the docs to reflect the current behavior (try to bind to IPv6 first), or change the current behavior back to that of 0.10?

@trevnorris
Copy link

@cjihrig we've been binding to IPv6 by default since the early v0.11 days, so I'm going to say that this should be a doc change.

@misterdjules
Copy link

@cjihrig @trevnorris @joyent/node-coreteam My opinion is that we might want to reconsider this change.

There's more background in a discussion that happened after the changed landed.

There are other issues created for the same problem:

I also fixed one in express and one in hapi a few months ago.

It's not clear if we have reached the point where we received enough bug reports about it to revert the change, but I think it's worth considering.

yelworc added a commit to ElevenGiants/eleven-server that referenced this issue Mar 11, 2015
@jasnell
Copy link
Member

jasnell commented Mar 12, 2015

Not sure if reverting is the right approach but refactoring a bit and documenting would be good.

@misterdjules misterdjules added this to the 0.12.1 milestone Mar 13, 2015
@misterdjules
Copy link

Adding to the 0.12.1 milestone to make sure we consider this before shipping node v0.12.1.

@MylesBorins
Copy link
Member

@jasnell @cjihrig @trevnorris @misterdjules I just ran into this today. Was going through the express intro docs and was expecting and ipv4 address and received an ipv6 one. Seems to me like this might have fallen by the way side (at least updating the tutorial in express)

@cjihrig
Copy link

cjihrig commented Nov 30, 2015

I think this can be closed now. The docs were updated, and now read:

Begin accepting connections on the specified port and hostname. If the hostname is omitted, the server will accept connections on any IPv6 address (::) when IPv6 is available, or any IPv4 address (0.0.0.0) otherwise. A port value of zero will assign a random port.

@cjihrig cjihrig closed this as completed Nov 30, 2015
@mildmojo
Copy link
Author

@cjihrig I'm still trying to figure out how I get ::ffff:127.0.0.1 from remoteAddress when I'm hitting the server through IPv4 (in OSX, requesting 127.0.0.1:8080, 192.168.x.x:8080, or through an IPv4 proxy).

When the updated http.Server.listen docs say "any IPv6 address when available or any IPv4 address otherwise", does that mean the defined behavior is to bind only to an IPv6 interface when available, excluding IPv4? Or should it say something like "any IPv4 address and any IPv6 address when IPv6 is available"?

And if defined behavior is to listen on both IPv4 and IPv6 by default (0.12 does this), how does my IPv4 request get "promoted" to IPv6 when it hits the node.js server, causing remoteAddress to report my IPv4 address in IPv6 format? Or is it just that node detected IPv6 on .listen() and therefore reports all addresses everywhere in IPv6 format? If that's the case, it would help to have a note on all address-reporting functions/attributes that the reporting format is linked to whether the server was allowed to bind to an IPv6 interface.

Looks like that doc change on listen didn't make it to the docs for 0.12.8 (latest).

@cjihrig
Copy link

cjihrig commented Nov 30, 2015

@mildmojo, I'll try to explain to the best of my understanding.

When your server calls listen(), this code gets executed. If you haven't specified an address, newer versions of Node attempt to bind to any IPv6 interface first. If that fails, it falls back to binding to any IPv4 interface.

As mentioned earlier in this thread, a dual stack is used, meaning that the IPv6 interface can also handle IPv4 connections. When an IPv4 address is received over IPv6, it is prefixed with ::ffff:, which is why you're seeing things like ::ffff:127.0.0.1.

Looks like that doc change on listen didn't make it to the docs for 0.12.8 (latest).

@jasnell should the documentation update be backported?

@MylesBorins
Copy link
Member

@cjihrig it definitely should imho.

@cjihrig
Copy link

cjihrig commented Nov 30, 2015

@thealphanerd looks like you want nodejs/node@5c7ab96

@MylesBorins
Copy link
Member

ok cool... that docfix is in the 4.x line, which is where I have been landing stuff.

I think that @rvagg has been taking lead on 0.12.x

@mildmojo
Copy link
Author

Thanks, @cjihrig. I think I might have it now. I didn't recognize "dual stack" as a term of art.

So somewhere upstream of Node, maybe at the OS level, the IPv6 interface is a dual stack and will transparently translate IPv4 traffic for IPv6 listeners. Node prefers to bind to IPv6 by default. On an IPv6 platform without a dual stack, IPv4 connections would just fail to connect. OSX happens to be a dual stack environment, so my IPv4 requests go through (translated). As far as Node is concerned, it's bound to IPv6 interfaces, so everything that comes in looks like IPv6 traffic.

If that's all true, I'm still a little fuzzy on why curl 127.0.0.1:8080 works when the server's bound to :: (explicitly or implicitly) but fails when bound to ::1, given that ::1 is another address for my loopback device.

@rvagg
Copy link
Member

rvagg commented Nov 30, 2015

@thealphanerd v0.12-staging is not something I really want sole responsibility for, if you're able, please help!

@MylesBorins
Copy link
Member

more than happy to 😄

@jasnell
Copy link
Member

jasnell commented Nov 30, 2015

Hey all... just getting back from vacation and catching up. On a quick scan:

  • @cjihrig, yes, doc updates are definitely something we want to get landed in v0.12-staging.
  • @rvagg : @thealphanerd and I can take the lead on v0.12-staging, I just need to catch back up to see where things are currently at.

@cjihrig
Copy link

cjihrig commented Nov 30, 2015

@mildmojo I believe (but I'm not sure) that ::1 and 127.0.0.1 only accept traffic from their specific protocols.

@mildmojo
Copy link
Author

I'll take it! Thanks for explaining this for me. Cheers! 🍹

@IgorTitov
Copy link

I bumped into the same issue too! Previously I could connect to Node app with IPv4 (another part of the application can work only with IPv4). Now it fails. If I specify 127.0.0.1 explicitly, then Node listens on port but another app can't establish connection - there is error 'EACCES' even if Node runs with administrative privileges and ports are in range 49152-65535. Is it possible to make a setting to programmatically choose the interface family so that it could work like in previous versions?

rousskov added a commit to measurement-factory/daft that referenced this issue Jan 4, 2016
The former is used as an "authority" part of HTTP URLs. The latter --
for listening and connecting. URLs want something like "localhost" but
sockets cannot listen on "localhost".

Discovered that we cannot use default (i.e., undefined) listening host
because older nodejs (e.g., v0.10.25) essentially interpret it as "any
IPv4 if possible" while the newer nodejs interpret undefined host as "::
if possible". We now specify "::" explicitly in hope to avoid this
difference.  Here is one reference explaining the newer behavior:

nodejs/node-v0.x-archive#9195 (comment)
@raybellis
Copy link

For those running on e.g. Fedora 23, check that it's not your firewalld.service getting in the way. With the default rules the automatic dual-stack ::ffff:a.b.c.d binding doesn't appear to work.

@IgorTitov
Copy link

Could you please reopen this issue? It's not solved.

@bnoordhuis
Copy link
Member

@IgorTitov What's not solved? The new behavior is documented and won't change.

jBarz pushed a commit to ibmruntimes/node that referenced this issue May 11, 2017
This makes it easier to see what header has an invalid value.

PR-URL: nodejs#9195
Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Sakthipriyan Vairamani <thechargingvolcano@gmail.com>
Reviewed-By: Brian White <mscdex@mscdex.net>
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests