The node-dns-sd is a Node.js module which is a pure javascript implementation of mDNS/DNS-SD (Apple Bonjour) browser and packet parser. It allows you to discover IPv4 addresses in the local network specifying a service name such as _http._tcp.local
. Besides, it allows you to monitor mDNS/DNS-SD packets.
This module focuses on discovering IPv4 addresses from a service name. It is not a full implementation of mDNS/DNS-SD. If you want to announce services or send custom query packets or discover IPv6 addresses, you should choice another one from mDNS/DNS-SD nodejs implementations.
- Node.js 18 +
- Though the node-dns-sd works on older version of Node for now, it is strongly recommended to use the latest version of Node.
$ cd ~
$ npm install node-dns-sd
The node-dns-sd supports a Promise-based method for discovering devices in the local network. The sample code below shows how to discover devices from a service name. In the sample code, _googlecast._tcp.local
is specified for a service name.
const mDnsSd = require('node-dns-sd');
mDnsSd.discover({
name: '_googlecast._tcp.local'
}).then((device_list) =>{
console.log(JSON.stringify(device_list, null, ' '));
}).catch((error) => {
console.error(error);
});
The sample code above will output the result as follows:
[
{
"address": "192.168.11.20",
"fqdn": "BRAVIA-4K-GB-0000043926ff4f7fed3bf248db400000._googlecast._tcp.local",
"modelName": "BRAVIA 4K GB",
"familyName": "KJ-43X8300D",
"service": {
"port": 8009,
"protocol": "tcp",
"type": "googlecast"
},
"packet": {...}
},
{
"address": "192.168.11.12",
"fqdn": "Google-Home-000001eda257d1f8ea765acd79500000._googlecast._tcp.local",
"modelName": "Google Home",
"familyName": "Google Home in living room",
"service": {
"port": 8009,
"protocol": "tcp",
"type": "googlecast"
},
"packet": {...}
}
]
As you can see, you can obtain information of devices which support the service specified to the discover()
method.
The value of the packet
property in the response is a DnsSdPacket
object which represents a DNS-SD response packet. You can find more information in the object.
The node-dns-sd has a mDNS/DNS-SD packet parser. It can watch mDNS/DNS-SD packets in the local network and reports the packets as human-readable information.
const mDnsSd = require('node-dns-sd');
mDnsSd.ondata = (packet) => {
console.log(JSON.stringify(packet, null, ' '));
};
mDnsSd.startMonitoring().then(() => {
console.log('Started.');
}).catch((error) => {
console.error(error);
});
The sample code above will output the result as follows:
...
{
"header": {
"id": 0,
"qr": 0,
"op": 0,
"aa": 0,
"tc": 0,
"rd": 0,
"ra": 0,
"z": 0,
"ad": 0,
"cd": 0,
"rc": 0,
"questions": 3,
"answers": 2,
"authorities": 0,
"additionals": 1
},
"questions": [
{
"name": "_homekit._tcp.local",
"type": "PTR",
"class": "IN"
},
{
"name": "A6A1A463-D197-53EA-892B-FFFFFFFFFFFF._homekit._tcp.local",
"type": "TXT",
"class": "IN"
},
{
"name": "_sleep-proxy._udp.local",
"type": "PTR",
"class": "IN"
}
],
"answers": [
{
"name": "_homekit._tcp.local",
"type": "PTR",
"class": "IN",
"flash": false,
"ttl": 4500,
"rdata": "A6A1A463-D197-53EA-892B-FFFFFFFFFFFF._homekit._tcp.local"
},
{
"name": "_sleep-proxy._udp.local",
"type": "PTR",
"class": "IN",
"flash": false,
"ttl": 4500,
"rdata": "70-00-00-00.1 Apple TV._sleep-proxy._udp.local"
}
],
"authorities": [],
"additionals": [
{
"name": "",
"type": "OPT",
"class": "",
"flash": false,
"ttl": 4500,
"rdata": "00 04 00 0e 00 24 c2 a5 3e 4c 7c b6 c0 a5 3e 4c 7c b4"
}
],
"address": "192.168.11.24"
}
...
The object above is a DnsSdPacket
object which represents a mDNS/DNS-SD response packet.
In order to use the node-dns-sd, you have to load the node-dns-sd module as follows:
const DnsSd = require('node-dns-sd');
In the code snippet above, the variable DnsSd
is a DnsSd
object. The DnsSd
object has methods as described in sections below.
The discover()
method discovers devices supporting the service specified to this method in the local network. This method returns a Promise
object.
This method takes a hash object containing the properties as follows:
Property | Type | Required | Description |
---|---|---|---|
name |
String | Required | Service name.(e.g., "_googlecast._tcp.local" ) |
type |
String | Optional | Query Type (e.g., "PTR" ). The default value is "*" . |
key |
String | Optional | This value must be "address" (default) or "fqdn" . This property indicates how to fold multiple DNS-SD query responses. See the description below for details. |
wait |
Integer | Optional | Duration of monitoring (sec). The default value is 3 sec. |
quick |
Boolean | Optional | If true , this method returns immediately after a device was found ignoring the value of the wait . The default value is false . |
filter |
String | Optional | If a string is specified to the filter , this method discovers only devices which the specified string is found in the fqdn , address , modelName or familyName . |
filter |
Function | Optional | If a function is specified to the filter , this method discovers only devices for which the function returns true . See the sample code below for details. |
If you want to discover all services in the local netowrk, you can set the name
property to _services._dns-sd._udp.local'
.
mDnsSd.discover({
name: '_services._dns-sd._udp.local',
type: 'PTR',
key: 'fqdn'
}).then((device_list) =>{
console.log(JSON.stringify(device_list, null, ' '));
}).catch((error) => {
console.error(error);
});
The type
property indicates the query type, such as "PTR"
. This value must be a (Q)TYPE value defined in the RFC 1035 and RFC 2782. If this property is not specified, the wildcard "*"
will be applied.
The key
property indicates how to fold multiple DNS-SD query responses. If the value is set to "address"
or this property is not specified, the last response form an IP address will be reported. If you want to discover IP addresses rather than services, this mode is appropriate.
If the value of the key
property is set to "fqdn"
, responses will be folded by each FQDN. In this mode, multiple responses with the same IP address could be included. If you want to discover services rather than IP address, this mode is appropriate.
Basically you don't need to pass the wait
property to this method. In most cases, the default value 3
(sec) works well.
The code blow would find Google devices (Google Home, Google TV, etc.):
mDnsSd.discover({
name: '_googlecast._tcp.local'
}).then((device_list) =>{
console.log(JSON.stringify(device_list, null, ' '));
}).catch((error) => {
console.error(error);
});
The code above will output the result as follows:
[
{
"address": "192.168.11.20",
"fqdn": "BRAVIA-4K-GB-0000043926ff4f7fed3bf248db400000._googlecast._tcp.local",
"modelName": "BRAVIA 4K GB",
"familyName": "KJ-43X8300D",
"service": {
"port": 8009,
"protocol": "tcp",
"type": "googlecast"
},
"packet": {...}
},
{
"address": "192.168.11.12",
"fqdn": "Google-Home-000001eda257d1f8ea765acd79500000._googlecast._tcp.local",
"modelName": "Google Home",
"familyName": "Google Home in living room",
"service": {
"port": 8009,
"protocol": "tcp",
"type": "googlecast"
},
"packet": {...}
}
]
A string is set to the filter
parameter, this method limits to devices whose fqdn
, address
, modelName
or familyName
includes the string.
mDnsSd.discover({
name: '_googlecast._tcp.local',
filter: 'Google Home',
quick: true
}).then((device_list) =>{
console.log(JSON.stringify(device_list, null, ' '));
}).catch((error) => {
console.error(error);
});
A function is set to the filter
parameter, this method limits to devices for which the function returns true
. The function must return true
or false
.
mDnsSd.discover({
name: '_googlecast._tcp.local',
filter: (devcie) => {
return (device['modelName'] === 'Google Home' && /Living room/.test(device['familyName']));
},
quick: true
}).then((device_list) =>{
console.log(JSON.stringify(device_list, null, ' '));
}).catch((error) => {
console.error(error);
});
As you can see from the code above, an object representing a found device is passed to the function. You can evaluate the device information and limit to devices you want.
The discover()
method will pass a information list of the found devices to the callback function. Each device information in the list contains the properties as follows:
Property | Type | Description |
---|---|---|
address |
String | IPv4 address |
fqdn |
String | Fully Qualified Domain Name |
modelName |
String | Model Name |
familyName |
String | Family Name |
service |
Object | |
+port |
Integer | Port number (e.g., 8009 ) |
+protocol |
String | Protocol (e.g., "tcp" ) |
+type |
String | Service type (e.g., "googlecast ") |
packet |
DnsSdPacket |
An object representing the response packet |
Note that the values of properties other than the address
are not necessarily set in this object. If the values are not obtained from the response packet, they will be set to null
.
Here are some examples:
mDnsSd.discover({
name: '_airplay._tcp.local'
})
[
{
"address": "192.168.11.29",
"fqdn": "Apple TV._airplay._tcp.local",
"modelName": "Apple TV J42dAP",
"familyName": null,
"service": {
"port": 7000,
"protocol": "tcp",
"type": "airplay"
},
"packet": {...}
}
]
mDnsSd.discover({
name: '_printer._tcp.local'
})
[
{
"address": "192.168.11.99",
"fqdn": "Canon MF720C Series._ipp._tcp.local",
"modelName": "Canon MF720C Series",
"familyName": null,
"service": {
"port": 80,
"protocol": "tcp",
"type": "ipp"
},
"packet": {...}
}
]
mDnsSd.discover({
name: '_hap._tcp.local'
})
[
{
"address": "192.168.11.18",
"fqdn": "Philips hue - 123ABC._hap._tcp.local",
"modelName": "Philips hue BSB002",
"familyName": null,
"service": {
"port": 8080,
"protocol": "tcp",
"type": "hap"
},
"packet": {...}
}
]
mDnsSd.discover({
name: 'raspberrypi.local'
})
[
{
"address": "192.168.11.34",
"fqdn": null,
"productName": null,
"modelName": null,
"familyName": null,
"service": null,
"packet": {...}
}
]
The startMonitoring()
method starts the monitoring mode and listens to mDNS/DNS-SD packets. This method returns a Promise
object.
You can catch incoming packets setting a callback function to the ondata
event handler.
mDnsSd.ondata = (packet) => {
console.log(JSON.stringify(packet, null, ' '));
};
mDnsSd.startMonitoring().then(() => {
console.log('Started.');
}).catch((error) => {
console.error(error);
});
Whenever a mDNS/DNS-SD packet is received, a DnsSdPacket
object will be passed to the callback function. See the section "DnsSdPacket
object" for more details.
The stopMonitoring()
method stops the monitoring mode started by the startMonitoring()
method. This method returns a Promise
object.
mDnsSd.stopMonitoring().then(() => {
console.log('Stopped.');
}).catch((error) => {
console.error(error);
});
The ondata
event handler will be called whenever a mDNS/DNS-SD packet is received. Note that this event handler works only if the monitoring mode is active.
See the section "startMonitoring()
method" for more details.
The DnsSdPacket
object represents a mDNS/DNS-SD packet. It is a hash object containing the properties as follows:
{
"header": {
"id": 0,
"qr": 1,
"op": 0,
"aa": 1,
"tc": 0,
"rd": 0,
"ra": 0,
"z": 0,
"ad": 0,
"cd": 0,
"rc": 0,
"questions": 0,
"answers": 1,
"authorities": 0,
"additionals": 3
},
"questions": [],
"answers": [
{
"name": "_googlecast._tcp.local",
"type": "PTR",
"class": "IN",
"flash": false,
"ttl": 120,
"rdata": "Google-Home-0ae0c1eda257d1f8ea765acd00000000._googlecast._tcp.local"
}
],
"authorities": [],
"additionals": [
{
"name": "Google-Home-0ae0c1eda257d1f8ea765acd00000000._googlecast._tcp.local",
"type": "TXT",
"class": "IN",
"flash": true,
"ttl": 4500,
"rdata": {
"id": "0ae0c1eda257d1f8ea765acd00000000",
"cd": "A4030CC6FEF4C94DFCD31B0500000000",
"rm": "2CE3F99700000000",
"ve": "05",
"md": "Google Home",
"ic": "/setup/icon.png",
"fn": "Google Home in living room",
"ca": "2052",
"st": "0",
"bs": "FA8F00000000",
"nf": "1",
"rs": ""
},
"rdata_buffer": {
"id": {Buffer object},
"cd": {Buffer object},
"rm": {Buffer object},
"ve": {Buffer object},
"md": {Buffer object},
"ic": {Buffer object},
"fn": {Buffer object},
"ca": {Buffer object},
"st": {Buffer object},
"bs": {Buffer object},
"nf": {Buffer object},
"rs": {Buffer object}
}
},
{
"name": "Google-Home-0ae0c1eda257d1f8ea765acd00000000._googlecast._tcp.local",
"type": "SRV",
"class": "IN",
"flash": true,
"ttl": 120,
"rdata": {
"priority": 0,
"weight": 0,
"port": 8009,
"target": "0ae0c1ed-a257-d1f8-ea76-000000000000.local"
}
},
{
"name": "0ae0c1ed-a257-d1f8-ea76-000000000000.local",
"type": "A",
"class": "IN",
"flash": true,
"ttl": 120,
"rdata": "192.168.11.12"
}
],
"address": "192.168.11.12"
}
Note that the rdata_buffer
property is added only if the type is "TXT"
. Each value in the rdata_buffer
is a Buffer
object. Some devices set a binary data to each value. You can parse the binary data using this property.
See the section "References" for more details.
- v1.0.1 (2023-04-05)
- Fixed the constant variable issue (thanks to @cybercode)
- v1.0.0 (2023-03-11)
- Rewrote all codes in modern coding style using
class
,async
,await
, etc. - Supported multi-homed environment.
- Rewrote all codes in modern coding style using
- v0.4.2 (2020-09-30)
- Catch dropMembership error (thanks to @bwp91)
- v0.4.1 (2020-04-09)
- Fix of address already in use on udp.addMembership method (thanks to @SlyAndrew)
- v0.4.0 (2019-02-24)
- Added the
rdata_buffer
property in theDnsSdPacket
object.
- Added the
- v0.3.0 (2018-10-25)
- Added the
key
andtype
parameters to thediscover()
method.
- Added the
- v0.2.1 (2018-10-24)
- Improved the device discovery. In this version, all available IPv4 network interfaces are joined to a multicast group, so that all devices in the local network are sure to be discovered.
- Fixed a bug that some event listeners did not be removed when the discovery process is finished.
- v0.2.0 (2018-08-02)
- Supported a function-based filtering mechanism in the
discover()
method. Now you can specify your custom filter as a function to thefilter
paramter of thediscover()
method. (thanks to @dayflower)
- Supported a function-based filtering mechanism in the
- v0.1.2 (2018-01-06)
- Fixed a bug that an exeption was thrown if the
filter
was specified to thediscover()
method.
- Fixed a bug that an exeption was thrown if the
- v0.1.0 (2018-01-06)
- Added the parameter
quick
andfilter
to thediscover()
method. - Fixed a bug that a UDP socket was not closed properly.
- Added the parameter
- v0.0.1 (2018-01-05)
- First public release
- RFC 1035 (DOMAIN NAMES - IMPLEMENTATION AND SPECIFICATION)
- RFC 6762 (Multicast DNS)
- RFC 6763 (DNS-Based Service Discovery)
- RFC 2782 (A DNS RR for specifying the location of services (DNS SRV))
The MIT License (MIT)
Copyright (c) 2018-2023 Futomi Hatano
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.