11const udp = require ( 'dgram' ) ;
2+ const net = require ( 'net' ) ;
3+ const crypto = require ( 'crypto' ) ;
24const Packet = require ( '../packet' ) ;
3- const { equal } = require ( 'assert' ) ;
45const { debuglog } = require ( 'util' ) ;
56
67const debug = debuglog ( 'dns2' ) ;
78
8- module . exports = ( { dns = '8.8.8.8' , port = 53 , socketType = 'udp4' } = { } ) => {
9+ module . exports = ( {
10+ dns = '8.8.8.8' ,
11+ port = 53 ,
12+ socketType = 'udp4' ,
13+ timeout = 10000 ,
14+ } = { } ) => {
915 return ( name , type = 'A' , cls = Packet . CLASS . IN , options = { } ) => {
1016 const { clientIp, recursive = true } = options ;
1117 const query = new Packet ( ) ;
12- query . header . id = ( Math . random ( ) * 1e4 ) | 0 ;
18+ query . header . id = crypto . randomInt ( 0x10000 ) ;
1319 // see https://github.com/song940/node-dns/issues/29
1420 if ( recursive ) {
1521 query . header . rd = 1 ;
@@ -25,15 +31,66 @@ module.exports = ({ dns = '8.8.8.8', port = 53, socketType = 'udp4' } = {}) => {
2531 type : Packet . TYPE [ type ] ,
2632 } ) ;
2733 const client = new udp . Socket ( socketType ) ;
34+ // Only enforce a strict source-address check when `dns` is an IP literal;
35+ // hostnames would require an extra resolve to compare against.
36+ const expectedAddress = net . isIP ( dns ) ? dns : null ;
2837 return new Promise ( ( resolve , reject ) => {
29- client . once ( 'message' , function onMessage ( message ) {
30- client . close ( ) ;
31- const response = Packet . parse ( message ) ;
32- equal ( response . header . id , query . header . id ) ;
38+ let settled = false ;
39+ let timer ;
40+ const cleanup = ( ) => {
41+ if ( settled ) return ;
42+ settled = true ;
43+ if ( timer ) clearTimeout ( timer ) ;
44+ client . removeListener ( 'message' , onMessage ) ;
45+ client . removeListener ( 'error' , onError ) ;
46+ try { client . close ( ) ; } catch ( _ ) { /* already closed */ }
47+ } ;
48+ function onMessage ( message , rinfo ) {
49+ // Drop packets that didn't come from the configured resolver.
50+ if ( rinfo . port !== port || ( expectedAddress && rinfo . address !== expectedAddress ) ) {
51+ debug ( 'udp: dropping packet from unexpected sender %s:%d' , rinfo . address , rinfo . port ) ;
52+ return ;
53+ }
54+ let response ;
55+ try {
56+ response = Packet . parse ( message ) ;
57+ } catch ( e ) {
58+ debug ( 'udp: dropping unparseable packet: %s' , e . message ) ;
59+ return ;
60+ }
61+ // Stray / late reply from a reused ephemeral port — keep listening.
62+ if ( response . header . id !== query . header . id ) {
63+ debug ( 'udp: dropping response with mismatched id %d (expected %d)' ,
64+ response . header . id , query . header . id ) ;
65+ return ;
66+ }
67+ cleanup ( ) ;
3368 resolve ( response ) ;
34- } ) ;
69+ }
70+ function onError ( err ) {
71+ cleanup ( ) ;
72+ reject ( err ) ;
73+ }
74+ client . on ( 'message' , onMessage ) ;
75+ client . on ( 'error' , onError ) ;
76+
77+ if ( timeout > 0 ) {
78+ timer = setTimeout ( ( ) => {
79+ cleanup ( ) ;
80+ const err = new Error ( `DNS query timed out after ${ timeout } ms` ) ;
81+ err . code = 'ETIMEDOUT' ;
82+ reject ( err ) ;
83+ } , timeout ) ;
84+ timer . unref ( ) ;
85+ }
86+
3587 debug ( 'send' , dns , query . toBuffer ( ) ) ;
36- client . send ( query . toBuffer ( ) , port , dns , err => err && reject ( err ) ) ;
88+ client . send ( query . toBuffer ( ) , port , dns , err => {
89+ if ( err ) {
90+ cleanup ( ) ;
91+ reject ( err ) ;
92+ }
93+ } ) ;
3794 } ) ;
3895 } ;
3996} ;
0 commit comments