GeezerVison is showing github.com — dgram: skip dns.lookup() for literal IP addresses · nodejs/node@9e50bb0
File treeExpand file treeCollapse file tree Open diff view settings Expand file treeCollapse file tree Open diff view settings Collapse file Copy file name to clipboard +43Lines changed: 43 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
| | 1 | +// Measure the send rate to a literal IP destination. The destination needs no | | 2 | +// name resolution, so this isolates the per-send overhead the default lookup | | 3 | +// pays before the packet reaches the socket. | | 4 | +'use strict'; | | 5 | + | | 6 | +const common = require('../common.js'); | | 7 | +const dgram = require('dgram'); | | 8 | +const PORT = common.PORT; | | 9 | + | | 10 | +// `n` is the number of send requests queued each round. Keep it high (>10) so | | 11 | +// the measurement reflects send overhead rather than event loop cycles. | | 12 | +const bench = common.createBenchmark(main, { | | 13 | + n: [100], | | 14 | + dur: [5], | | 15 | +}); | | 16 | + | | 17 | +function main({ dur, n }) { | | 18 | + const chunk = Buffer.allocUnsafe(1); | | 19 | + let sent = 0; | | 20 | + const socket = dgram.createSocket('udp4'); | | 21 | + | | 22 | + function onsend() { | | 23 | + if (sent++ % n === 0) { | | 24 | + setImmediate(() => { | | 25 | + for (let i = 0; i < n; i++) { | | 26 | + socket.send(chunk, PORT, '127.0.0.1', onsend); | | 27 | + } | | 28 | + }); | | 29 | + } | | 30 | + } | | 31 | + | | 32 | + socket.on('listening', () => { | | 33 | + bench.start(); | | 34 | + onsend(); | | 35 | + | | 36 | + setTimeout(() => { | | 37 | + bench.end(sent); | | 38 | + process.exit(0); | | 39 | + }, dur * 1000); | | 40 | + }); | | 41 | + | | 42 | + socket.bind(PORT); | | 43 | +} |
Collapse file Copy file name to clipboardExpand all lines: doc/api/dgram.md +2Lines changed: 2 additions & 0 deletions - Display the source diff
- Display the rich diff
| Original file line number | Diff line number | Diff line change |
|---|
@@ -1039,6 +1039,8 @@ changes: | 1039 | 1039 | * `recvBufferSize` {number} Sets the `SO_RCVBUF` socket value. | 1040 | 1040 | * `sendBufferSize` {number} Sets the `SO_SNDBUF` socket value. | 1041 | 1041 | * `lookup` {Function} Custom lookup function. **Default:** [`dns.lookup()`][]. | | 1042 | + When the default is used, a literal IP address of the socket's family | | 1043 | + resolves to itself without calling [`dns.lookup()`][]. | 1042 | 1044 | * `signal` {AbortSignal} An AbortSignal that may be used to close a socket. | 1043 | 1045 | * `receiveBlockList` {net.BlockList} `receiveBlockList` can be used for discarding | 1044 | 1046 | inbound datagram to specific IP addresses, IP ranges, or IP subnets. This does not | |
Collapse file Copy file name to clipboardExpand all lines: lib/internal/dgram.js +15-5Lines changed: 15 additions & 5 deletions | Original file line number | Diff line number | Diff line change |
|---|
| 8 | 8 | const { codes: { | 9 | 9 | ERR_SOCKET_BAD_TYPE, | 10 | 10 | } } = require('internal/errors'); | | 11 | +const { isIP } = require('internal/net'); | 11 | 12 | const { UDP } = internalBinding('udp_wrap'); | 12 | 13 | const { guessHandleType } = require('internal/util'); | 13 | 14 | const { | @@ -28,13 +29,22 @@ function lookup6(lookup, address, callback) { | 28 | 29 | return lookup(address || '::1', 6, callback); | 29 | 30 | } | 30 | 31 |
| | 32 | +// A literal IP of the socket's family resolves to itself, so skip dns.lookup(). | | 33 | +// Defer with nextTick to keep the callback async (e.g. bind()'s 'listening'). | | 34 | +function defaultLookup(address, family, callback) { | | 35 | + if (isIP(address) === family) { | | 36 | + process.nextTick(callback, null, address, family); | | 37 | + return; | | 38 | + } | | 39 | + if (dns === undefined) { | | 40 | + dns = require('dns'); | | 41 | + } | | 42 | + return dns.lookup(address, family, callback); | | 43 | +} | | 44 | + | 31 | 45 | function newHandle(type, lookup) { | 32 | 46 | if (lookup === undefined) { | 33 | | - if (dns === undefined) { | 34 | | - dns = require('dns'); | 35 | | - } | 36 | | - | 37 | | - lookup = dns.lookup; | | 47 | + lookup = defaultLookup; | 38 | 48 | } else { | 39 | 49 | validateFunction(lookup, 'lookup'); | 40 | 50 | } | |
Collapse file Copy file name to clipboardExpand all lines: test/parallel/test-dgram-custom-lookup.js +8-6Lines changed: 8 additions & 6 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -4,10 +4,12 @@ const assert = require('assert'); | 4 | 4 | const dgram = require('dgram'); | 5 | 5 | const dns = require('dns'); | 6 | 6 |
| | 7 | +const originalLookup = dns.lookup; | | 8 | + | 7 | 9 | { | 8 | 10 | // Verify that the provided lookup function is called. | 9 | 11 | const lookup = common.mustCall((host, family, callback) => { | 10 | | - dns.lookup(host, family, callback); | | 12 | + originalLookup(host, family, callback); | 11 | 13 | }); | 12 | 14 |
| 13 | 15 | const socket = dgram.createSocket({ type: 'udp4', lookup }); | @@ -18,17 +20,17 @@ const dns = require('dns'); | 18 | 20 | } | 19 | 21 |
| 20 | 22 | { | 21 | | - // Verify that lookup defaults to dns.lookup(). | 22 | | - const originalLookup = dns.lookup; | 23 | | - | | 23 | + // Verify that the default lookup forwards host names to dns.lookup(). | 24 | 24 | dns.lookup = common.mustCall((host, family, callback) => { | 25 | 25 | dns.lookup = originalLookup; | 26 | | - originalLookup(host, family, callback); | | 26 | + assert.strictEqual(host, 'example.invalid'); | | 27 | + assert.strictEqual(family, 4); | | 28 | + callback(null, '127.0.0.1', 4); | 27 | 29 | }); | 28 | 30 |
| 29 | 31 | const socket = dgram.createSocket({ type: 'udp4' }); | 30 | 32 |
| 31 | | - socket.bind(common.mustCall(() => { | | 33 | + socket.bind(0, 'example.invalid', common.mustCall(() => { | 32 | 34 | socket.close(); | 33 | 35 | })); | 34 | 36 | } | |
Collapse file Copy file name to clipboard +80Lines changed: 80 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
| | 1 | +'use strict'; | | 2 | + | | 3 | +// The default dgram lookup resolves a literal IP address of the socket's own | | 4 | +// family to itself, without calling dns.lookup(). Each case below stubs the | | 5 | +// process-global dns.lookup(), so they run sequentially to keep one case from | | 6 | +// observing another's stub. | | 7 | + | | 8 | +const common = require('../common'); | | 9 | +const assert = require('assert'); | | 10 | +const dgram = require('dgram'); | | 11 | +const dns = require('dns'); | | 12 | + | | 13 | +const originalLookup = dns.lookup; | | 14 | + | | 15 | +function ipv4SendSkipsLookup(next) { | | 16 | + dns.lookup = common.mustNotCall('dns.lookup() ran for an IPv4 literal'); | | 17 | + | | 18 | + const receiver = dgram.createSocket('udp4'); | | 19 | + const sender = dgram.createSocket('udp4'); | | 20 | + | | 21 | + receiver.on('message', common.mustCall((msg) => { | | 22 | + assert.strictEqual(msg.toString(), 'payload'); | | 23 | + dns.lookup = originalLookup; | | 24 | + receiver.close(); | | 25 | + sender.close(); | | 26 | + next(); | | 27 | + })); | | 28 | + | | 29 | + receiver.bind(0, '127.0.0.1', common.mustCall(() => { | | 30 | + sender.send('payload', receiver.address().port, '127.0.0.1', common.mustCall()); | | 31 | + })); | | 32 | +} | | 33 | + | | 34 | +function ipv6BindSkipsLookup(next) { | | 35 | + if (!common.hasIPv6) { | | 36 | + next(); | | 37 | + return; | | 38 | + } | | 39 | + | | 40 | + dns.lookup = common.mustNotCall('dns.lookup() ran for an IPv6 literal'); | | 41 | + | | 42 | + const socket = dgram.createSocket('udp6'); | | 43 | + | | 44 | + socket.bind(0, '::1', common.mustCall(() => { | | 45 | + dns.lookup = originalLookup; | | 46 | + socket.close(); | | 47 | + next(); | | 48 | + })); | | 49 | +} | | 50 | + | | 51 | +function mismatchedFamilyFallsThrough(next) { | | 52 | + // '::1' is not an IPv4 literal, so a udp4 socket still resolves it via | | 53 | + // dns.lookup() rather than short-circuiting. | | 54 | + dns.lookup = common.mustCall((host, family, callback) => { | | 55 | + dns.lookup = originalLookup; | | 56 | + assert.strictEqual(host, '::1'); | | 57 | + assert.strictEqual(family, 4); | | 58 | + callback(null, '127.0.0.1', 4); | | 59 | + }); | | 60 | + | | 61 | + const socket = dgram.createSocket('udp4'); | | 62 | + | | 63 | + socket.bind(0, '::1', common.mustCall(() => { | | 64 | + socket.close(); | | 65 | + next(); | | 66 | + })); | | 67 | +} | | 68 | + | | 69 | +const cases = [ | | 70 | + ipv4SendSkipsLookup, | | 71 | + ipv6BindSkipsLookup, | | 72 | + mismatchedFamilyFallsThrough, | | 73 | +]; | | 74 | + | | 75 | +(function runNext() { | | 76 | + const testCase = cases.shift(); | | 77 | + if (testCase !== undefined) { | | 78 | + testCase(runNext); | | 79 | + } | | 80 | +})(); |
Collapse file Copy file name to clipboardExpand all lines: test/sequential/test-dgram-implicit-bind-failure.js +7-6Lines changed: 7 additions & 6 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -4,19 +4,20 @@ const common = require('../common'); | 4 | 4 | const assert = require('assert'); | 5 | 5 | const EventEmitter = require('events'); | 6 | 6 | const dgram = require('dgram'); | 7 | | -const dns = require('dns'); | 8 | 7 | const { kStateSymbol } = require('internal/dgram'); | 9 | 8 | const mockError = new Error('fake DNS'); | 10 | 9 |
| 11 | | -// Monkey patch dns.lookup() so that it always fails. | 12 | | -dns.lookup = function(address, family, callback) { | | 10 | +const socket = dgram.createSocket('udp4'); | | 11 | + | | 12 | +// Fail the implicit bind by making the handle's address resolution fail. A | | 13 | +// literal bind address is not passed to dns.lookup(), so patching dns.lookup() | | 14 | +// would not be observed here. | | 15 | +socket[kStateSymbol].handle.lookup = function(address, callback) { | 13 | 16 | process.nextTick(() => { callback(mockError); }); | 14 | 17 | }; | 15 | 18 |
| 16 | | -const socket = dgram.createSocket('udp4'); | 17 | | - | 18 | 19 | socket.on(EventEmitter.errorMonitor, common.mustCall((err) => { | 19 | | - // The DNS lookup should fail since it is monkey patched. At that point in | | 20 | + // The bind should fail since the lookup is monkey patched. At that point in | 20 | 21 | // time, the send queue should be populated with the send() operation. | 21 | 22 | assert.strictEqual(err, mockError); | 22 | 23 | assert(Array.isArray(socket[kStateSymbol].queue)); | |
|