umdns: add cli module
This makes it easy to browse mDNS services and hosts on the local network Signed-off-by: Felix Fietkau <nbd@nbd.name>
This commit is contained in:
parent
f7c5a2b7d4
commit
562ced3d8f
@ -37,7 +37,12 @@ define Package/umdns/conffiles
|
||||
endef
|
||||
|
||||
define Package/umdns/install
|
||||
$(INSTALL_DIR) $(1)/usr/sbin $(1)/etc/init.d $(1)/etc/config
|
||||
$(INSTALL_DIR) \
|
||||
$(1)/usr/sbin \
|
||||
$(1)/etc/init.d \
|
||||
$(1)/etc/config \
|
||||
$(1)/usr/share/ucode/cli/modules
|
||||
$(INSTALL_DATA) ./files/mdns.uc $(1)/usr/share/ucode/cli/modules
|
||||
$(INSTALL_BIN) $(PKG_BUILD_DIR)/umdns $(1)/usr/sbin/
|
||||
$(INSTALL_BIN) ./files/umdns.init $(1)/etc/init.d/umdns
|
||||
$(INSTALL_CONF) ./files/umdns.config $(1)/etc/config/umdns
|
||||
|
267
package/network/services/umdns/files/mdns.uc
Normal file
267
package/network/services/umdns/files/mdns.uc
Normal file
@ -0,0 +1,267 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
// Copyright (C) 2025 Felix Fietkau <nbd@nbd.name>
|
||||
'use strict';
|
||||
|
||||
function mdns_data(model)
|
||||
{
|
||||
return model.ubus.call("umdns", "browse", { array: true, address: false });
|
||||
}
|
||||
|
||||
function refresh_timer(model)
|
||||
{
|
||||
model.ubus.call("umdns", "update");
|
||||
|
||||
model.mdns.refresh_count++;
|
||||
if (model.mdns.refresh_count < 3)
|
||||
model.mdns.timer.set(500);
|
||||
|
||||
if (model.mdns.refresh_count < 2)
|
||||
return;
|
||||
|
||||
let data = mdns_data(model);
|
||||
for (let service_name, service_data in data) {
|
||||
for (let host_name, host_data in service_data) {
|
||||
let interface = host_data.iface;
|
||||
if (!interface)
|
||||
continue;
|
||||
if (host_data.host)
|
||||
continue;
|
||||
|
||||
let question = host_name + "." + service_name + ".local";
|
||||
model.ubus.call("umdns", "query", { question, interface });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function refresh_start(model)
|
||||
{
|
||||
model.mdns.refresh_count = 0;
|
||||
model.mdns.timer ??= model.uloop.timer(500, () => refresh_timer(model));
|
||||
}
|
||||
|
||||
function strip_local(name)
|
||||
{
|
||||
if (substr(name, -6) == ".local")
|
||||
name = substr(name, 0, -6);
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
function get_hosts(model)
|
||||
{
|
||||
let data = model.ubus.call("umdns", "hosts", { array: true });
|
||||
let ret = {};
|
||||
|
||||
for (let name, val in data) {
|
||||
name = strip_local(name);
|
||||
ret[lc(name)] = val;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
function get_host_names(model)
|
||||
{
|
||||
let data = model.ubus.call("umdns", "hosts", { array: true });
|
||||
let ret = {};
|
||||
|
||||
for (let name, val in data) {
|
||||
name = strip_local(name);
|
||||
ret[lc(name)] = name;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
function get_host_servicenames(model)
|
||||
{
|
||||
let hosts = {};
|
||||
let data = mdns_data(model);
|
||||
for (let service_name, service_data in data) {
|
||||
for (let host_name, host_data in service_data) {
|
||||
let name = strip_local(host_data.host);
|
||||
|
||||
hosts[lc(name)] ??= [];
|
||||
if (index(hosts[lc(name)], host_name) < 0)
|
||||
push(hosts[lc(name)], host_name);
|
||||
}
|
||||
}
|
||||
return hosts;
|
||||
}
|
||||
|
||||
function get_service_hosts(model, name)
|
||||
{
|
||||
name = lc(name);
|
||||
|
||||
let data = mdns_data(model);
|
||||
for (let cur_name, hosts in data)
|
||||
if (lc(cur_name) == name)
|
||||
return hosts;
|
||||
}
|
||||
|
||||
function get_host_services(model)
|
||||
{
|
||||
let hosts = {};
|
||||
let data = mdns_data(model);
|
||||
for (let service_name, service_data in data) {
|
||||
for (let host_name, host_data in service_data) {
|
||||
host_data.name = host_name;
|
||||
host_name = lc(strip_local(host_data.host));
|
||||
hosts[host_name] ??= {};
|
||||
hosts[host_name][service_name] = host_data;
|
||||
}
|
||||
}
|
||||
return hosts;
|
||||
}
|
||||
|
||||
function host_info(host)
|
||||
{
|
||||
let ret = {};
|
||||
if (host.ipv4)
|
||||
ret.IPv4 = host.ipv4;
|
||||
if (host.ipv6)
|
||||
ret.IPv6 = host.ipv6;
|
||||
return ret;
|
||||
}
|
||||
|
||||
const host_arg = {
|
||||
name: "host",
|
||||
help: "host name",
|
||||
type: "enum",
|
||||
ignore_case: true,
|
||||
value: () => values(get_host_names(model)),
|
||||
};
|
||||
|
||||
const service_arg = {
|
||||
name: "service",
|
||||
help: "service name",
|
||||
type: "enum",
|
||||
ignore_case: true,
|
||||
value: () => keys(mdns_data(model)),
|
||||
};
|
||||
|
||||
function add_field(ret, name, val)
|
||||
{
|
||||
if (val == null)
|
||||
return;
|
||||
if (type(ret) == "array")
|
||||
push(ret, [ name, val ]);
|
||||
else
|
||||
ret[name] = val;
|
||||
}
|
||||
|
||||
function service_info(data)
|
||||
{
|
||||
let info = [];
|
||||
|
||||
add_field(info, "Name", data.name);
|
||||
add_field(info, "Interface", data.iface);
|
||||
add_field(info, "Port", data.port);
|
||||
add_field(info, "Text", data.txt);
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
const MDNS = {
|
||||
refresh: {
|
||||
help: "Refresh service list by sending queries",
|
||||
call: function(ctx, argv, named) {
|
||||
refresh_start(model);
|
||||
return ctx.ok("Querying hosts");
|
||||
}
|
||||
},
|
||||
service: {
|
||||
help: "Show service info",
|
||||
args: [ service_arg ],
|
||||
call: function (ctx, argv, named) {
|
||||
let name = argv[0];
|
||||
if (name != null) {
|
||||
let data = get_service_hosts(model, name);
|
||||
if (!data)
|
||||
return ctx.not_found("Service not found: %s", name);
|
||||
|
||||
let ret = {};
|
||||
for (let name, host in data) {
|
||||
if (!host.host)
|
||||
continue;
|
||||
let host_name = strip_local(host.host);
|
||||
host.name = name;
|
||||
ret["Host " + host_name] = service_info(host);
|
||||
}
|
||||
|
||||
return ctx.multi_table("Service " + name, ret);
|
||||
}
|
||||
|
||||
let data = mdns_data(model);
|
||||
let services = {};
|
||||
for (let service_name, service_data in data) {
|
||||
let hosts = [];
|
||||
for (let name, host in service_data)
|
||||
if (host.host)
|
||||
push(hosts, `${name}(${strip_local(host.host)})`);
|
||||
if (length(hosts))
|
||||
services[service_name] = sort(hosts);
|
||||
}
|
||||
|
||||
return ctx.table("Services", services);
|
||||
}
|
||||
},
|
||||
host: {
|
||||
help: "Host information",
|
||||
args: [ host_arg ],
|
||||
call: function (ctx, argv, named) {
|
||||
let hosts_svc = get_host_services(model);
|
||||
let hosts = get_hosts(model);
|
||||
let host = argv[0];
|
||||
|
||||
if (host == null) {
|
||||
let host_names = get_host_names(model);
|
||||
let ret = {};
|
||||
|
||||
for (let lc_name, name in host_names) {
|
||||
let data = hosts[lc_name];
|
||||
if (!data)
|
||||
continue;
|
||||
|
||||
let title = "Host " + name;
|
||||
ret[title] = host_info(data);
|
||||
let svc = hosts_svc[lc_name];
|
||||
if (svc)
|
||||
ret[title].services = keys(svc);
|
||||
}
|
||||
return ctx.multi_table("Hosts", ret);
|
||||
}
|
||||
|
||||
let lc_host = lc(host);
|
||||
let data = hosts[lc_host];
|
||||
if (!data)
|
||||
return ctx.not_found("Host not found: " + host);
|
||||
|
||||
let ret = {};
|
||||
ret.Info = host_info(data);
|
||||
|
||||
for (let service_name, sdata in hosts_svc[lc_host])
|
||||
ret["Service " + service_name] = service_info(sdata);
|
||||
|
||||
return ctx.multi_table("Host " + host, ret);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
const Root = {
|
||||
mdns: {
|
||||
help: "Browse mdns hosts",
|
||||
select_node: "MDNS",
|
||||
select: function(ctx, argv) {
|
||||
try {
|
||||
refresh_start(model);
|
||||
} catch (e) {
|
||||
ctx.model.exception(e);
|
||||
}
|
||||
return true;
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
model.add_nodes({ Root, MDNS });
|
||||
model.mdns = {};
|
Loading…
x
Reference in New Issue
Block a user