1 let ubus, global, last_refresh;
2 let rtnl = require("rtnl");
4 const homekit_types = {
7 "4": "Garage Door Opener",
14 "11": "Security System",
17 "14": "Window Covering",
18 "15": "Programmable Switch",
20 "18": "Video Doorbell",
23 "21": "Air Conditioner",
28 "30": "Shower System",
30 "32": "Remote Control",
32 "34": "Audio Receiver",
33 "35": "TV Set Top Box",
37 function get_arp_cache() {
40 let cache = rtnl.request(rtnl.const.RTM_GETNEIGH, rtnl.const.NLM_F_DUMP, {});
41 for (let data in cache) {
42 if (!data.lladdr || !data.dst)
45 ret[data.dst] = data.lladdr;
51 function find_arp_entry(arp, addrs)
53 for (let addr in addrs) {
61 function get_host_addr_cache()
63 let arp = get_arp_cache();
64 let host_cache = ubus.call("umdns", "hosts", { array: true });
66 for (let host in host_cache) {
67 let host_data = host_cache[host];
68 host_addr[host] = find_arp_entry(arp, host_data.ipv4) ??
69 find_arp_entry(arp, host_data.ipv6);
75 function convert_txt_array(data) {
78 for (let rec in data) {
79 rec = split(rec, "=", 2);
87 function handle_apple(txt, name)
90 let model = txt.model ?? txt.rpMd ?? txt.am;
92 push(ret, `apple_model|${model}`);
94 push(ret, `%device_name|mdns_implicit_device_name|${name}`);
99 function handle_homekit(txt)
103 let type = homekit_types[txt.ci];
105 push(ret, `%class|homekit_class|${type}`);
109 push(ret, `%device|mdns_model_string|${txt.md}`);
114 function handle_googlecast_model(txt)
117 let model = txt.model ?? txt.rpMd ?? txt.md;
119 push(ret, `%device|mdns_model_string|${model}`);
121 push(ret, `%device_name|mdns_device_name|${txt.fn}`);
123 push(ret, "%class|mdns_tv|TV");
127 function handle_printer(txt)
130 let vendor = txt.usb_MFG;
131 let model = txt.usb_MDL ?? txt.ty ?? replace(txt.product, /^\((.*)\)$/, "$1");
132 let weight = (txt.usb_MFG && txt.usb_MDL) ? "mdns_vendor_model_string" : "mdns_model_string";
134 push(ret, `%vendor|${weight}|${vendor}`);
136 push(ret, `%device|${weight}|${model}`);
137 push(ret, "%class|mdns_printer|Printer");
142 function handle_scanner(txt)
145 let vendor = txt.mfg;
146 let model = txt.mdl ?? txt.ty;
147 let weight = (txt.mfg && txt.mdl) ? "mdns_vendor_model_string" : "mdns_model_string";
149 push(ret, `%vendor|${weight}|${vendor}`);
151 push(ret, `%device|${weight}|${model}`);
152 push(ret, "%class|mdns_scanner|Scanner");
157 function handle_hue(txt, name)
160 push(ret, `%vendor|mdns_service|Philips`);
161 push(ret, `%device|mdns_service|Hue`);
163 push(ret, `%device_name|mdns_implicit_device_name|${name}`);
168 function handle_fritzbox(txt)
171 push(ret, `%vendor|mdns_service|AVM`);
172 push(ret, `%device|mdns_service|FRITZ!Box`);
177 const service_handler = {
178 "_airplay._tcp": handle_apple,
179 "_companion-link._tcp": handle_apple,
180 "_raop._tcp": (txt) => handle_apple(txt), // skip name
181 "_googlecast._tcp": handle_googlecast_model,
182 "_ipp._tcp": handle_printer,
183 "_scanner._tcp": handle_scanner,
184 "_hap._tcp": handle_homekit,
185 "_hap._udp": handle_homekit,
186 "_hue._tcp": handle_hue,
187 "_fbox._tcp": handle_fritzbox,
188 "_avmnexus._tcp": handle_fritzbox,
191 function arp_resolve(list)
193 let host_cache = ubus.call("umdns", "hosts", { array: true });
194 for (let entry in list) {
195 let iface = entry[0];
197 if (!host_cache[host])
199 for (let addr in host_cache[host].ipv4)
200 rtnl.request(rtnl.const.RTM_NEWNEIGH,
201 rtnl.const.NLM_F_CREATE | rtnl.const.NLM_F_REPLACE,
202 { dev: iface, state: rtnl.const.NUD_INCOMPLETE,
203 flags: rtnl.const.NTF_USE, family: rtnl.const.AF_INET,
209 if (last_refresh == time())
212 let host_cache = get_host_addr_cache();
214 let arp_pending = [];
217 for (let i = 0; i < 2; i++) {
218 mdns_data = ubus.call("umdns", "browse", { array: true, address: false });
219 for (let service in mdns_data) {
220 if (!service_handler[service])
222 let service_data = mdns_data[service];
223 for (let host in service_data) {
224 let host_entry = service_data[host].host;
225 let iface = service_data[host].iface;
229 push(query_list, [ `${host}.${service}.local`, iface ]);
230 else if (!host_cache[host_entry])
231 push(arp_pending, [ iface, host_entry ]);
235 if (!length(arp_pending) && !length(query_list))
238 if (length(arp_pending) > 0)
239 arp_resolve(arp_pending);
241 if (length(query_list) > 0) {
242 for (let query in query_list)
243 ubus.call("umdns", "query", { question: query[0], interface: query[1] });
248 host_cache = get_host_addr_cache();
249 mdns_data = ubus.call("umdns", "browse", { array: true, address: false });
252 for (let service in mdns_data) {
253 if (!service_handler[service])
256 let service_data = mdns_data[service];
257 for (let host in service_data) {
258 let txt = service_data[host].txt;
262 let host_entry = service_data[host].host;
266 let mac = host_cache[host_entry];
270 txt = convert_txt_array(txt);
271 let match = service_handler[service](txt, host);
272 for (let info in match)
273 global.device_add_data(mac, info);
275 global.device_refresh(mac);
286 mdns_device_name: 10.0,
287 mdns_implicit_device_name: 5.0,
288 mdns_vendor_model_string: 10.0,
291 mdns_model_string: 5.0,
298 return { init, refresh };