-
Notifications
You must be signed in to change notification settings - Fork 30.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Issue 14736 tls ipv6 address #14772
Issue 14736 tls ipv6 address #14772
Conversation
lib/_http_agent.js
Outdated
// abc:123 => abc | ||
// [::1] => ::1 | ||
// [::1]:123 => ::1 | ||
options.servername = hostHeader.replace(/:[^\]]+$/, '') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not combine both regexps for efficiency?:
options.servername = hostHeader.replace(/^(?:\[([^\]]*)\].*)|(?:([^:]*).*)$/, '$1$2');
I tried a few different variations on the above regexp, and that one seemed to execute the fastest, at least on node v8.3.0.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I ran a benchmark on your regexp (regexp1), my regexp (regexp2) and a variant that doesn't use regexps (t3). See https://gist.github.com/mattiash/f19f7d862dce54db955267e03319b2d2
The result from my benchmark was that the two regexp-solution was actually faster than your one-regexp-solution, but the fastest was my new variant without any regexp.
$ node --version
v8.3.0
$ node index.js
regexp1 x 360,816 ops/sec ±1.15% (88 runs sampled)
regexp2 x 514,166 ops/sec ±1.21% (87 runs sampled)
t3 x 1,195,766 ops/sec ±1.38% (87 runs sampled)
Fastest is t3
$ node --version
v6.3.1
$ node index.js
regexp1 x 592,158 ops/sec ±0.95% (84 runs sampled)
regexp2 x 758,064 ops/sec ±1.21% (87 runs sampled)
t3 x 829,918 ops/sec ±1.18% (85 runs sampled)
Fastest is t3
I'll update the PR to use the t3 variant
lib/tls.js
Outdated
@@ -163,6 +163,33 @@ function check(hostParts, pattern, wildcards) { | |||
return true; | |||
} | |||
|
|||
exports._canonicalIp = function(address) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd generally prefer not introducing new _
-prefixed methods. Non-public methods should be moved into lib/internal/
and used from there.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
Is there anything else I need to do before this can be merged? We are using TLS with certificates that sign IPv4 addresses today, and we are very eager to add support for IPv6 which requires support for TLS with certificates that sign IPv6 addresses. |
It needs a bit of additional review. I've added a couple of folks to the "Reviewers" list. |
lib/internal/tls.js
Outdated
const b = ['0', '0', '0', '0', '0', '0', '0', '0']; | ||
|
||
const s = address.split('::'); | ||
if (s.length === 2) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there actually any IPv6 without the two dots?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes. The :: notation is just an abbreviation for any number of zero-bytes. There can very well be an IPv6-address without any zero-bytes. And the address comes from the Host-header, so it might be that the http-client requested an address with the IPv6 address spelled out, e.g. https://[0.0.0.0.0.0.0.1] for localhost.
lib/internal/tls.js
Outdated
} | ||
} | ||
|
||
return b.join(':'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you want to improve the performance you could do the following to improve it further.
- use string concatenation and not use the
b
array at all. And at the end add"0:".repeat(restLen)
. - replace the RegExp with a loop that checks something like
var n = 0; while (str.charCodeAt(n++) === 48);
so you know what part you want to slice out. - use
address.indexOf('::')
andaddress.split(':')
directly. You know until where the first part and until where the second part will go because of the indexOf and you only have to split a single time instead of three times (you could optimize this the same as the RegExp by using a loop and not splitting at all. It should be even faster). - The
if (s1[n])
is not necessary as far as I see it. The current test cases have no entry that does not match but in case e.g.:fe80::0000:
is a valid entry, just check the very first/last entry if it is entry and count down the length.
But this is out of my perspective not that important as a first draft and should not hold off the PR from being merged.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This code is run once per https-request, and only if the host is specified with an IPv6 address. The code is already quite complex as it is and I would not like to complicate it further right now.
The if (s1[n])
is needed for '::1'
assert.strictEqual(tls.canonicalIp('fe80::'), 'fe80:0:0:0:0:0:0:0'); | ||
assert.strictEqual( | ||
tls.canonicalIp('fe80::0000:0010:0001'), | ||
'fe80:0:0:0:0:0:10:1'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be good to have some more test cases if there are any more out of the ordinary ones. It has been a long time that I checked how a valid IPv6 looks like.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Your comment made me realize that I didn't test IPv6-addresses without double colons. And of course that didn't work. Fix coming up.
PTAL @nodejs/http @nodejs/crypto |
This needs some reviews @nodejs/collaborators |
// of a TLS certificate | ||
|
||
const assert = require('assert'); | ||
const tls = require('internal/tls'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this test work without // Flags: --expose-internals
? (See the testing guide)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're right, it actually doesn't. I have added that flag now. While debugging this I looked at the other tests in the test/internet/ directory and realised that they all needed internet access (which my test doesn't). I therefore moved the test to test/parallel which should make sure that it gets run by make test
@nodejs/tsc PTAL. @mattiash this needs a rebase. |
@BridgeAR Should I rebase it now before the review is done, or will that interrupt the review process? |
@mattiash it is always best to rebase and to have a clean state to review. Otherwise changed could come in be missed afterwards. |
ddcd49c
to
de4fa4c
Compare
Branch rebased as requested. The only conflict was that someone had created internal/tls.js (which I also had) so I did a trivial merge. make -j4 test still passes for me locally. |
FWIW it looks like there's something up with the author part of the commits' metadata. Perhaps there is a difference in author and committer email addresses or something? |
Yes, I wrote the patch at work and rebased it from home, that's why the author/committer don't match. I'll rebase again to avoid any confusion. Sorry for the trouble. |
de4fa4c
to
347af99
Compare
@nodejs/http @nodejs/crypto PTAL. This needs some LGs |
Note to reviewers: https://github.com/mattiash/test-checkServerIdentity explains what the problem is and contains a test-case that fails with node 8.5.0 but succeeds with the code from this PR. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This PR adds a fourth IPv6 address parser. We currently have:
net.isIPv6()
(which calls out touv_inet_pton()
),- the
--inspect=[::0]:1234
parser insrc/node_debug_options.cc
and - the URL parser (which also parses IPv6 addresses) in
src/node_url.cc
.
Rather than adding yet another parser, there is probably a way to share code with one of them.
Failing that, a binding method could be added that calls uv_inet_pton()
and uv_inet_ntop()
to normalize an address. That would ensure consistency with other parts of node.
uv_inet_ntop()
will canonicalize them to short form, not long form. I don't think that matters but if it does, you could always replace it with a snprintf()
that prints the octets to string.
I have to agree with @bnoordhuis. Reusing one of the existing ipv6 parsing routines is best. |
Thanks for the review. I looked at the different IPv6 parsing routines already in node, but I couldn't find anything that I could use out of the box to convert IPv6 addresses to a canonical format. I used @bnoordhuis suggestion about uv_inet_ntop/pton which works for my purposes. |
src/cares_wrap.cc
Outdated
|
||
args.GetReturnValue().Set(String::NewFromUtf8(isolate, canonical_ip)); | ||
} else { | ||
args.GetReturnValue().Set(Undefined(isolate)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Default return value is undefined
. No reason to set it here if it hasn't be set prior.
Fixed. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Left some comments but nearly there.
lib/_http_agent.js
Outdated
// [::1] => ::1 | ||
// [::1]:123 => ::1 | ||
if (hostHeader.startsWith('[')) { | ||
options.servername = hostHeader.split(']')[0].substr(1); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
.split(']', 1)
is more efficient.
For bonus performance points, if you're going to slice the string anyway, call .indexOf()
and then .substr(1, index)
(and default index
to hostHeader.length
if it's -1.)
Lastly, I would say that it should pass on the string untouched if it doesn't contain a matching ]
.
src/cares_wrap.cc
Outdated
@@ -1935,6 +1935,30 @@ void IsIPv6(const FunctionCallbackInfo<Value>& args) { | |||
} | |||
} | |||
|
|||
void canonicalIP(const FunctionCallbackInfo<Value>& args) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Style: CanonicalIP
As well, I'd call it CanonicalizeIP
for two reasons:
- It makes it clear it's an imperative, not a predicate (i.e., it transforms, it doesn't just check.)
lI
is almost indistinguishable fromll
,II
, etc. with some fonts. (Minor thing. Still.)
src/cares_wrap.cc
Outdated
char canonical_ip[INET_ADDRSTRLEN]; | ||
|
||
int err = uv_inet_ntop(AF_INET, address_buffer, canonical_ip, | ||
sizeof(canonical_ip)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Style: please line up the argument.
src/cares_wrap.cc
Outdated
|
||
args.GetReturnValue().Set(String::NewFromUtf8(isolate, canonical_ip)); | ||
} else if (uv_inet_pton(AF_INET6, *ip, &address_buffer) == 0) { | ||
char canonical_ip[INET6_ADDRSTRLEN]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd just move this up a scope, it's big enough to hold IPv4 and IPv6 address strings. Shaves off a few lines of code.
assert.strictEqual(canonicalIP('fe80:0:0:0:0:0:0:0'), 'fe80::'); | ||
assert.strictEqual( | ||
canonicalIP('fe80::0000:0010:0001'), | ||
'fe80::10:1'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This fits on one line. If it didn't, house style is to write it like this:
assert.strictEqual(canonicalIP('fe80::0000:0010:0001'),
'fe80::10:1');
Only if that wouldn't fit in 80 columns would you use one-argument-one-line style but then please indent by four spaces. (Truth be told, I would have expected the linter to complain about that.)
Addressed all comments from @bnoordhuis. |
Landed in b6df87e Congrats on becoming a Contributor @mattiasholmlund! |
- Properly handle IPv6 in Host header when setting servername. - When comparing IP addresses against addresses in the subjectAltName field of a certificate, format the address correctly before doing the string comparison. PR-URL: nodejs#14772 Fixes: nodejs#14736 Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl> Reviewed-By: Anatoli Papirovski <apapirovski@mac.com> Reviewed-By: James M Snell <jasnell@gmail.com>
Letting this bake in Current a bit longer before landing into LTS |
- Properly handle IPv6 in Host header when setting servername. - When comparing IP addresses against addresses in the subjectAltName field of a certificate, format the address correctly before doing the string comparison. PR-URL: #14772 Fixes: #14736 Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl> Reviewed-By: Anatoli Papirovski <apapirovski@mac.com> Reviewed-By: James M Snell <jasnell@gmail.com>
Has this baked in master for long enough now to be considered for the v8 lts? The PR was included in Version 9.1.0 2017-11-07, so it has been out in the wild for over two months now. |
- Properly handle IPv6 in Host header when setting servername. - When comparing IP addresses against addresses in the subjectAltName field of a certificate, format the address correctly before doing the string comparison. PR-URL: nodejs#14772 Fixes: nodejs#14736 Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl> Reviewed-By: Anatoli Papirovski <apapirovski@mac.com> Reviewed-By: James M Snell <jasnell@gmail.com>
- Properly handle IPv6 in Host header when setting servername. - When comparing IP addresses against addresses in the subjectAltName field of a certificate, format the address correctly before doing the string comparison. PR-URL: #14772 Fixes: #14736 Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl> Reviewed-By: Anatoli Papirovski <apapirovski@mac.com> Reviewed-By: James M Snell <jasnell@gmail.com>
I think so, I've landed it on v8.x-staging. |
- Properly handle IPv6 in Host header when setting servername. - When comparing IP addresses against addresses in the subjectAltName field of a certificate, format the address correctly before doing the string comparison. PR-URL: #14772 Fixes: #14736 Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl> Reviewed-By: Anatoli Papirovski <apapirovski@mac.com> Reviewed-By: James M Snell <jasnell@gmail.com>
backport to v6.x-staging in 8e7ac25 |
Notable changes: * deps: * update V8 to 6.2.414.46 (Michaël Zasso) [#16413](#16413) * revert ABI breaking changes in V8 6.2 (Anna Henningsen) [#16413](#16413) * upgrade libuv to 1.19.1 (cjihrig) [#18260](#18260) * re land npm 5.6.0 (Myles Borins) [#18625](#18625) * ICU 60 bump (Steven R. Loomis) [#16876](#16876) * crypto: * Support both OpenSSL 1.1.0 and 1.0.2 (David Benjamin) [#16130](#16130) * warn on invalid authentication tag length (Tobias Nießen) [#17566](#17566) * async_hooks: * update defaultTriggerAsyncIdScope for perf (Anatoli Papirovski) [#18004](#18004) * use typed array stack as fast path (Anna Henningsen) [#17780](#17780) * use scope for defaultTriggerAsyncId (Andreas Madsen) [#17273](#17273) * separate missing from default context (Andreas Madsen) [#17273](#17273) * rename initTriggerId (Andreas Madsen) [#17273](#17273) * deprecate undocumented API (Andreas Madsen) [#16972](#16972) * add destroy event for gced AsyncResources (Sebastian Mayr) [#16998](#16998) * add trace events to async_hooks (Andreas Madsen) [#15538](#15538) * set HTTPParser trigger to socket (Andreas Madsen) [#18003](#18003) * add provider types for net server (Andreas Madsen) [#17157](#17157) * n-api: * add helper for addons to get the event loop (Anna Henningsen) [#17109](#17109) * cli: * add --stack-trace-limit to NODE_OPTIONS (Anna Henningsen) [#16495](#16495) * console: * add support for console.debug (Benjamin Zaslavsky) [#17033](#17033) * module: * add builtinModules (Jon Moss) [#16386](#16386) * replace default paths in require.resolve() (cjihrig) [#17113](#17113) * src: * add helper for addons to get the event loop (Anna Henningsen) [#17109](#17109) * add process.ppid (cjihrig) [#16839](#16839) * http: * support generic `Duplex` streams (Anna Henningsen) [#16267](#16267) * add rawPacket in err of `clientError` event (XadillaX) [#17672](#17672) * better support for IPv6 addresses (Mattias Holmlund) [#14772](#14772) * net: * remove ADDRCONFIG DNS hint on Windows (Bartosz Sosnowski) [#17662](#17662) * process: * fix reading zero-length env vars on win32 (Anna Henningsen) [#18463](#18463) * tls: * unconsume stream on destroy (Anna Henningsen) [#17478](#17478) * process: * improve unhandled rejection message (Madara Uchiha) [#17158](#17158) * stream: * remove usage of *State.highWaterMark (Calvin Metcalf) [#12860](#12860) * trace_events: * add executionAsyncId to init events (Andreas Madsen) [#17196](#17196) PR-URL: #18336
Notable changes: * deps: * update V8 to 6.2.414.46 (Michaël Zasso) [#16413](#16413) * revert ABI breaking changes in V8 6.2 (Anna Henningsen) [#16413](#16413) * upgrade libuv to 1.19.1 (cjihrig) [#18260](#18260) * re land npm 5.6.0 (Myles Borins) [#18625](#18625) * ICU 60 bump (Steven R. Loomis) [#16876](#16876) * crypto: * Support both OpenSSL 1.1.0 and 1.0.2 (David Benjamin) [#16130](#16130) * warn on invalid authentication tag length (Tobias Nießen) [#17566](#17566) * async_hooks: * update defaultTriggerAsyncIdScope for perf (Anatoli Papirovski) [#18004](#18004) * use typed array stack as fast path (Anna Henningsen) [#17780](#17780) * use scope for defaultTriggerAsyncId (Andreas Madsen) [#17273](#17273) * separate missing from default context (Andreas Madsen) [#17273](#17273) * rename initTriggerId (Andreas Madsen) [#17273](#17273) * deprecate undocumented API (Andreas Madsen) [#16972](#16972) * add destroy event for gced AsyncResources (Sebastian Mayr) [#16998](#16998) * add trace events to async_hooks (Andreas Madsen) [#15538](#15538) * set HTTPParser trigger to socket (Andreas Madsen) [#18003](#18003) * add provider types for net server (Andreas Madsen) [#17157](#17157) * n-api: * add helper for addons to get the event loop (Anna Henningsen) [#17109](#17109) * cli: * add --stack-trace-limit to NODE_OPTIONS (Anna Henningsen) [#16495](#16495) * console: * add support for console.debug (Benjamin Zaslavsky) [#17033](#17033) * module: * add builtinModules (Jon Moss) [#16386](#16386) * replace default paths in require.resolve() (cjihrig) [#17113](#17113) * src: * add helper for addons to get the event loop (Anna Henningsen) [#17109](#17109) * add process.ppid (cjihrig) [#16839](#16839) * http: * support generic `Duplex` streams (Anna Henningsen) [#16267](#16267) * add rawPacket in err of `clientError` event (XadillaX) [#17672](#17672) * better support for IPv6 addresses (Mattias Holmlund) [#14772](#14772) * net: * remove ADDRCONFIG DNS hint on Windows (Bartosz Sosnowski) [#17662](#17662) * process: * fix reading zero-length env vars on win32 (Anna Henningsen) [#18463](#18463) * tls: * unconsume stream on destroy (Anna Henningsen) [#17478](#17478) * process: * improve unhandled rejection message (Madara Uchiha) [#17158](#17158) * stream: * remove usage of *State.highWaterMark (Calvin Metcalf) [#12860](#12860) * trace_events: * add executionAsyncId to init events (Andreas Madsen) [#17196](#17196) PR-URL: #18336
Notable Changes: * http, tls: - better support for IPv6 addresses (Mattias Holmlund) nodejs#14772 PR-URL: nodejs#19027
Notable changes: * deps: * update V8 to 6.2.414.46 (Michaël Zasso) [nodejs#16413](nodejs#16413) * revert ABI breaking changes in V8 6.2 (Anna Henningsen) [nodejs#16413](nodejs#16413) * upgrade libuv to 1.19.1 (cjihrig) [nodejs#18260](nodejs#18260) * re land npm 5.6.0 (Myles Borins) [nodejs#18625](nodejs#18625) * ICU 60 bump (Steven R. Loomis) [nodejs#16876](nodejs#16876) * crypto: * Support both OpenSSL 1.1.0 and 1.0.2 (David Benjamin) [nodejs#16130](nodejs#16130) * warn on invalid authentication tag length (Tobias Nießen) [nodejs#17566](nodejs#17566) * async_hooks: * update defaultTriggerAsyncIdScope for perf (Anatoli Papirovski) [nodejs#18004](nodejs#18004) * use typed array stack as fast path (Anna Henningsen) [nodejs#17780](nodejs#17780) * use scope for defaultTriggerAsyncId (Andreas Madsen) [nodejs#17273](nodejs#17273) * separate missing from default context (Andreas Madsen) [nodejs#17273](nodejs#17273) * rename initTriggerId (Andreas Madsen) [nodejs#17273](nodejs#17273) * deprecate undocumented API (Andreas Madsen) [nodejs#16972](nodejs#16972) * add destroy event for gced AsyncResources (Sebastian Mayr) [nodejs#16998](nodejs#16998) * add trace events to async_hooks (Andreas Madsen) [nodejs#15538](nodejs#15538) * set HTTPParser trigger to socket (Andreas Madsen) [nodejs#18003](nodejs#18003) * add provider types for net server (Andreas Madsen) [nodejs#17157](nodejs#17157) * n-api: * add helper for addons to get the event loop (Anna Henningsen) [nodejs#17109](nodejs#17109) * cli: * add --stack-trace-limit to NODE_OPTIONS (Anna Henningsen) [nodejs#16495](nodejs#16495) * console: * add support for console.debug (Benjamin Zaslavsky) [nodejs#17033](nodejs#17033) * module: * add builtinModules (Jon Moss) [nodejs#16386](nodejs#16386) * replace default paths in require.resolve() (cjihrig) [nodejs#17113](nodejs#17113) * src: * add helper for addons to get the event loop (Anna Henningsen) [nodejs#17109](nodejs#17109) * add process.ppid (cjihrig) [nodejs#16839](nodejs#16839) * http: * support generic `Duplex` streams (Anna Henningsen) [nodejs#16267](nodejs#16267) * add rawPacket in err of `clientError` event (XadillaX) [nodejs#17672](nodejs#17672) * better support for IPv6 addresses (Mattias Holmlund) [nodejs#14772](nodejs#14772) * net: * remove ADDRCONFIG DNS hint on Windows (Bartosz Sosnowski) [nodejs#17662](nodejs#17662) * process: * fix reading zero-length env vars on win32 (Anna Henningsen) [nodejs#18463](nodejs#18463) * tls: * unconsume stream on destroy (Anna Henningsen) [nodejs#17478](nodejs#17478) * process: * improve unhandled rejection message (Madara Uchiha) [nodejs#17158](nodejs#17158) * stream: * remove usage of *State.highWaterMark (Calvin Metcalf) [nodejs#12860](nodejs#12860) * trace_events: * add executionAsyncId to init events (Andreas Madsen) [nodejs#17196](nodejs#17196) PR-URL: nodejs#18336
Resolves issue #14736.
Checklist
make -j4 test
(UNIX), orvcbuild test
(Windows) passesAffected core subsystem(s)
tls