2 * luci - LuCI core functions plugin for rpcd
4 * Copyright (C) 2019 Jo-Philipp Wich <jo@mein.io>
6 * Permission to use, copy, modify, and/or distribute this software for any
7 * purpose with or without fee is hereby granted, provided that the above
8 * copyright notice and this permission notice appear in all copies.
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
32 #include <arpa/inet.h>
33 #include <sys/types.h>
34 #include <netinet/ether.h>
35 #include <linux/rtnetlink.h>
36 #include <linux/if_packet.h>
38 #include <netlink/msg.h>
39 #include <netlink/attr.h>
40 #include <netlink/socket.h>
43 #include <libubox/avl.h>
44 #include <libubox/avl-cmp.h>
45 #include <libubox/usock.h>
46 #include <libubox/uloop.h>
52 #include <rpcd/plugin.h>
55 static struct blob_buf blob
;
57 struct reply_context
{
58 struct ubus_context
*context
;
59 struct ubus_request_data request
;
60 struct uloop_timeout timeout
;
66 struct invoke_context
{
67 struct ubus_request request
;
68 struct uloop_timeout timeout
;
69 struct ubus_context
*context
;
70 void (*cb
)(struct ubus_request
*, int, struct blob_attr
*);
74 static const char **iw_modenames
;
75 static struct iwinfo_ops
*(*iw_backend
)(const char *);
76 static void (*iw_close
)(void);
79 invoke_data_cb(struct ubus_request
*req
, int type
, struct blob_attr
*msg
)
81 struct invoke_context
*ictx
=
82 container_of(req
, struct invoke_context
, request
);
85 ictx
->cb(req
, type
, msg
);
91 invoke_done_cb(struct ubus_request
*req
, int ret
)
93 struct invoke_context
*ictx
=
94 container_of(req
, struct invoke_context
, request
);
97 ictx
->cb(req
, -1, NULL
);
99 uloop_timeout_cancel(&ictx
->timeout
);
104 invoke_timeout_cb(struct uloop_timeout
*timeout
)
106 struct invoke_context
*ictx
=
107 container_of(timeout
, struct invoke_context
, timeout
);
109 if (ictx
->cb
!= NULL
)
110 ictx
->cb(&ictx
->request
, -1, NULL
);
112 ubus_abort_request(ictx
->context
, &ictx
->request
);
116 static struct reply_context
*
117 defer_request(struct ubus_context
*ctx
, struct ubus_request_data
*req
)
119 struct reply_context
*rctx
;
121 rctx
= calloc(1, sizeof(*rctx
));
127 blob_buf_init(&rctx
->blob
, 0);
128 ubus_defer_request(ctx
, req
, &rctx
->request
);
134 finish_request(struct reply_context
*rctx
, int status
)
136 if (status
== UBUS_STATUS_OK
)
137 ubus_send_reply(rctx
->context
, &rctx
->request
, rctx
->blob
.head
);
139 ubus_complete_deferred_request(rctx
->context
, &rctx
->request
, status
);
140 blob_buf_free(&rctx
->blob
);
147 invoke_ubus(struct ubus_context
*ctx
, const char *object
, const char *method
,
148 struct blob_buf
*req
,
149 void (*cb
)(struct ubus_request
*, int, struct blob_attr
*),
152 struct invoke_context
*ictx
;
153 struct blob_buf empty
= {};
157 if (ubus_lookup_id(ctx
, object
, &id
))
161 blob_buf_init(&empty
, 0);
165 ictx
= calloc(1, sizeof(*ictx
));
171 rv
= !ubus_invoke_async(ctx
, id
, method
, req
->head
, &ictx
->request
);
175 ictx
->request
.priv
= priv
;
176 ictx
->request
.data_cb
= invoke_data_cb
;
177 ictx
->request
.complete_cb
= invoke_done_cb
;
178 ubus_complete_request_async(ctx
, &ictx
->request
);
180 ictx
->timeout
.cb
= invoke_timeout_cb
;
181 uloop_timeout_set(&ictx
->timeout
, 2000);
185 cb(&ictx
->request
, -1, NULL
);
197 readstr(const char *fmt
, ...)
199 static char data
[128];
206 vsnprintf(path
, sizeof(path
), fmt
, ap
);
210 f
= fopen(path
, "r");
213 n
= fread(data
, 1, sizeof(data
) - 1, f
);
216 while (n
> 0 && isspace(data
[n
-1]))
226 ea2str(struct ether_addr
*ea
)
233 snprintf(mac
, sizeof(mac
), "%02X:%02X:%02X:%02X:%02X:%02X",
234 ea
->ether_addr_octet
[0], ea
->ether_addr_octet
[1],
235 ea
->ether_addr_octet
[2], ea
->ether_addr_octet
[3],
236 ea
->ether_addr_octet
[4], ea
->ether_addr_octet
[5]);
242 sa2str(struct sockaddr
*sa
)
244 static char buf
[INET6_ADDRSTRLEN
];
246 struct sockaddr_in6
*in6
;
247 struct sockaddr_in
*in
;
248 struct sockaddr_ll
*ll
;
254 if (s
.sa
->sa_family
== AF_INET
)
255 inet_ntop(sa
->sa_family
, &s
.in
->sin_addr
, buf
, sizeof(buf
));
256 else if (s
.sa
->sa_family
== AF_INET6
)
257 inet_ntop(sa
->sa_family
, &s
.in6
->sin6_addr
, buf
, sizeof(buf
));
258 else if (s
.sa
->sa_family
== AF_PACKET
)
259 strcpy(buf
, ea2str((struct ether_addr
*)s
.ll
->sll_addr
));
266 static struct ether_addr
*
267 duid2ea(const char *duid
)
269 static struct ether_addr ea
;
270 const char *p
= NULL
;
276 for (len
= 0; duid
[len
]; len
++)
277 if (!isxdigit(duid
[len
]))
281 (((x) <= '9') ? ((x) - '0') : \
282 (((x) <= 'F') ? ((x) - 'A' + 10) : \
287 if (!strncmp(duid
, "00010001", 8))
293 if (!strncmp(duid
, "00030001", 8))
306 ea
.ether_addr_octet
[0] = hex(p
[0]) * 16 + hex(p
[1]);
307 ea
.ether_addr_octet
[1] = hex(p
[2]) * 16 + hex(p
[3]);
308 ea
.ether_addr_octet
[2] = hex(p
[4]) * 16 + hex(p
[5]);
309 ea
.ether_addr_octet
[3] = hex(p
[6]) * 16 + hex(p
[7]);
310 ea
.ether_addr_octet
[4] = hex(p
[8]) * 16 + hex(p
[9]);
311 ea
.ether_addr_octet
[5] = hex(p
[10]) * 16 + hex(p
[11]);
327 struct ether_addr mac
;
337 find_leasefile(struct uci_context
*uci
, const char *section
)
339 struct uci_ptr ptr
= { .package
= "dhcp" };
340 struct uci_package
*pkg
= NULL
;
341 struct uci_section
*s
;
342 struct uci_element
*e
;
344 pkg
= uci_lookup_package(uci
, ptr
.package
);
347 uci_load(uci
, ptr
.package
, &pkg
);
353 uci_foreach_element(&pkg
->sections
, e
) {
354 s
= uci_to_section(e
);
356 if (strcmp(s
->type
, section
))
361 ptr
.section
= s
->e
.name
;
364 ptr
.option
= "leasefile";
367 if (uci_lookup_ptr(uci
, &ptr
, NULL
, true) || ptr
.o
== NULL
)
370 if (ptr
.o
->type
!= UCI_TYPE_STRING
)
373 return ptr
.o
->v
.string
;
382 if (lease_state
.dnsmasq_file
) {
383 fclose(lease_state
.dnsmasq_file
);
384 lease_state
.dnsmasq_file
= NULL
;
387 if (lease_state
.odhcpd_file
) {
388 fclose(lease_state
.odhcpd_file
);
389 lease_state
.odhcpd_file
= NULL
;
396 struct uci_context
*uci
;
401 uci
= uci_alloc_context();
406 lease_state
.now
= time(NULL
);
408 p
= find_leasefile(uci
, "dnsmasq");
409 lease_state
.dnsmasq_file
= fopen(p
? p
: "/tmp/dhcp.leases", "r");
411 p
= find_leasefile(uci
, "odhcpd");
412 lease_state
.odhcpd_file
= fopen(p
? p
: "/tmp/hosts/odhcpd", "r");
414 uci_free_context(uci
);
417 static struct lease_entry
*
420 static struct lease_entry e
;
421 struct ether_addr
*ea
;
424 memset(&e
, 0, sizeof(e
));
426 if (lease_state
.dnsmasq_file
) {
427 while (fgets(e
.buf
, sizeof(e
.buf
), lease_state
.dnsmasq_file
)) {
428 p
= strtok(e
.buf
, " \t\n");
433 e
.expire
= strtol(p
, NULL
, 10);
434 e
.expire
= (e
.expire
>= 0) ? e
.expire
- lease_state
.now
: 0;
436 p
= strtok(NULL
, " \t\n");
443 p
= strtok(NULL
, " \t\n");
445 if (p
&& inet_pton(AF_INET6
, p
, &e
.addr
.in6
))
447 else if (p
&& inet_pton(AF_INET
, p
, &e
.addr
.in
))
452 if (!ea
&& e
.af
!= AF_INET6
)
455 e
.hostname
= strtok(NULL
, " \t\n");
456 e
.duid
= strtok(NULL
, " \t\n");
458 if (!e
.hostname
|| !e
.duid
)
461 if (!strcmp(e
.hostname
, "*"))
464 if (!strcmp(e
.duid
, "*"))
468 ea
= duid2ea(e
.duid
);
476 fclose(lease_state
.dnsmasq_file
);
477 lease_state
.dnsmasq_file
= NULL
;
480 if (lease_state
.odhcpd_file
) {
481 while (fgets(e
.buf
, sizeof(e
.buf
), lease_state
.odhcpd_file
)) {
482 strtok(e
.buf
, " \t\n"); /* # */
483 strtok(NULL
, " \t\n"); /* iface */
485 e
.duid
= strtok(NULL
, " \t\n"); /* duid */
490 p
= strtok(NULL
, " \t\n"); /* iaid */
493 e
.af
= strcmp(p
, "ipv4") ? AF_INET6
: AF_INET
;
497 e
.hostname
= strtok(NULL
, " \t\n"); /* name */
502 p
= strtok(NULL
, " \t\n"); /* ts */
507 e
.expire
= strtol(p
, NULL
, 10);
508 e
.expire
= (e
.expire
> 0) ? e
.expire
- lease_state
.now
: e
.expire
;
510 strtok(NULL
, " \t\n"); /* id */
511 strtok(NULL
, " \t\n"); /* length */
513 p
= strtok(NULL
, "/ \t\n"); /* ip */
515 if (!p
|| !inet_pton(e
.af
, p
, &e
.addr
.in6
))
518 ea
= duid2ea(e
.duid
);
523 if (!strcmp(e
.hostname
, "-"))
526 if (!strcmp(e
.duid
, "-"))
532 fclose(lease_state
.odhcpd_file
);
533 lease_state
.odhcpd_file
= NULL
;
541 rpc_luci_parse_network_device_sys(const char *name
, struct ifaddrs
*ifaddr
)
543 char link
[64], buf
[512], *p
;
544 unsigned int ifa_flags
= 0;
545 struct sockaddr_ll
*sll
;
554 const char *stats
[] = {
555 "rx_bytes", "tx_bytes", "tx_errors", "rx_errors", "tx_packets",
556 "rx_packets", "multicast", "collisions", "rx_dropped", "tx_dropped"
559 o
= blobmsg_open_table(&blob
, name
);
561 blobmsg_add_string(&blob
, "name", name
);
563 snprintf(buf
, sizeof(buf
), "/sys/class/net/%s/brif", name
);
568 blobmsg_add_u8(&blob
, "bridge", 1);
570 a
= blobmsg_open_array(&blob
, "ports");
578 if (strcmp(e
->d_name
, ".") && strcmp(e
->d_name
, ".."))
579 blobmsg_add_string(&blob
, NULL
, e
->d_name
);
582 blobmsg_close_array(&blob
, a
);
586 p
= readstr("/sys/class/net/%s/bridge/bridge_id", name
);
587 blobmsg_add_string(&blob
, "id", p
);
589 p
= readstr("/sys/class/net/%s/bridge/stp_state", name
);
590 blobmsg_add_u8(&blob
, "stp", strcmp(p
, "0") ? 1 : 0);
593 snprintf(buf
, sizeof(buf
), "/sys/class/net/%s/master", name
);
594 len
= readlink(buf
, link
, sizeof(link
) - 1);
598 blobmsg_add_string(&blob
, "master", basename(link
));
601 p
= readstr("/sys/class/net/%s/phy80211/index", name
);
602 blobmsg_add_u8(&blob
, "wireless", *p
? 1 : 0);
604 p
= readstr("/sys/class/net/%s/operstate", name
);
605 blobmsg_add_u8(&blob
, "up", !strcmp(p
, "up") || !strcmp(p
, "unknown"));
607 n
= atoi(readstr("/sys/class/net/%s/mtu", name
));
609 blobmsg_add_u32(&blob
, "mtu", n
);
611 n
= atoi(readstr("/sys/class/net/%s/tx_queue_len", name
));
613 blobmsg_add_u32(&blob
, "qlen", n
);
615 p
= readstr("/sys/class/net/%s/master", name
);
617 blobmsg_add_string(&blob
, "master", p
);
619 for (af
= AF_INET
; af
!= 0; af
= (af
== AF_INET
) ? AF_INET6
: 0) {
620 a
= blobmsg_open_array(&blob
,
621 (af
== AF_INET
) ? "ipaddrs" : "ip6addrs");
623 for (ifa
= ifaddr
; ifa
!= NULL
; ifa
= ifa
->ifa_next
) {
624 if (ifa
->ifa_addr
== NULL
|| ifa
->ifa_addr
->sa_family
!= af
)
627 if (strcmp(ifa
->ifa_name
, name
))
630 o2
= blobmsg_open_table(&blob
, NULL
);
632 blobmsg_add_string(&blob
, "address",
633 sa2str(ifa
->ifa_addr
));
635 blobmsg_add_string(&blob
, "netmask",
636 sa2str(ifa
->ifa_netmask
));
638 if (ifa
->ifa_dstaddr
&& (ifa
->ifa_flags
& IFF_POINTOPOINT
))
639 blobmsg_add_string(&blob
, "remote",
640 sa2str(ifa
->ifa_dstaddr
));
641 else if (ifa
->ifa_broadaddr
&& (ifa
->ifa_flags
& IFF_BROADCAST
))
642 blobmsg_add_string(&blob
, "broadcast",
643 sa2str(ifa
->ifa_broadaddr
));
645 blobmsg_close_table(&blob
, o2
);
647 ifa_flags
|= ifa
->ifa_flags
;
650 blobmsg_close_array(&blob
, a
);
653 for (ifa
= ifaddr
; ifa
!= NULL
; ifa
= ifa
->ifa_next
) {
654 if (ifa
->ifa_addr
== NULL
|| ifa
->ifa_addr
->sa_family
!= AF_PACKET
)
657 if (strcmp(ifa
->ifa_name
, name
))
660 sll
= (struct sockaddr_ll
*)ifa
->ifa_addr
;
662 if (sll
->sll_hatype
== 1)
663 blobmsg_add_string(&blob
, "mac", sa2str(ifa
->ifa_addr
));
665 blobmsg_add_u32(&blob
, "type", sll
->sll_hatype
);
666 blobmsg_add_u32(&blob
, "ifindex", sll
->sll_ifindex
);
668 ifa_flags
|= ifa
->ifa_flags
;
672 o2
= blobmsg_open_table(&blob
, "stats");
674 for (n
= 0; n
< ARRAY_SIZE(stats
); n
++) {
675 v
= strtoull(readstr("/sys/class/net/%s/statistics/%s",
676 name
, stats
[n
]), NULL
, 10);
678 blobmsg_add_u64(&blob
, stats
[n
], v
);
681 blobmsg_close_table(&blob
, o2
);
683 o2
= blobmsg_open_table(&blob
, "flags");
684 blobmsg_add_u8(&blob
, "up", ifa_flags
& IFF_UP
);
685 blobmsg_add_u8(&blob
, "broadcast", ifa_flags
& IFF_BROADCAST
);
686 blobmsg_add_u8(&blob
, "promisc", ifa_flags
& IFF_PROMISC
);
687 blobmsg_add_u8(&blob
, "loopback", ifa_flags
& IFF_LOOPBACK
);
688 blobmsg_add_u8(&blob
, "noarp", ifa_flags
& IFF_NOARP
);
689 blobmsg_add_u8(&blob
, "multicast", ifa_flags
& IFF_MULTICAST
);
690 blobmsg_add_u8(&blob
, "pointtopoint", ifa_flags
& IFF_POINTOPOINT
);
691 blobmsg_close_table(&blob
, o2
);
693 blobmsg_close_table(&blob
, o
);
697 rpc_luci_get_network_devices(struct ubus_context
*ctx
,
698 struct ubus_object
*obj
,
699 struct ubus_request_data
*req
,
701 struct blob_attr
*msg
)
703 struct ifaddrs
*ifaddr
;
707 blob_buf_init(&blob
, 0);
709 d
= opendir("/sys/class/net");
712 if (getifaddrs(&ifaddr
) == 1)
721 if (strcmp(e
->d_name
, ".") && strcmp(e
->d_name
, ".."))
722 rpc_luci_parse_network_device_sys(e
->d_name
, ifaddr
);
731 ubus_send_reply(ctx
, req
, blob
.head
);
737 iw_call_str(int (*method
)(const char *, char *), const char *dev
,
738 struct blob_buf
*blob
, const char *field
)
740 char buf
[IWINFO_BUFSIZE
] = {};
742 if (method(dev
, buf
) == 0)
743 blobmsg_add_string(blob
, field
, buf
);
747 iw_call_num(int (*method
)(const char *, int *), const char *dev
,
748 struct blob_buf
*blob
, const char *field
)
752 if (method(dev
, &val
) == 0)
753 blobmsg_add_u32(blob
, field
, val
);
756 static bool rpc_luci_get_iwinfo(struct blob_buf
*buf
, const char *devname
,
759 struct iwinfo_crypto_entry crypto
= {};
760 struct iwinfo_hardware_id ids
= {};
761 const struct iwinfo_ops
*iw
;
762 void *iwlib
, *o
, *o2
, *a
;
765 if (!iw_backend
|| !iw_close
|| !iw_modenames
) {
766 iwlib
= dlopen("libiwinfo.so", RTLD_LOCAL
);
771 iw_backend
= dlsym(iwlib
, "iwinfo_backend");
772 iw_close
= dlsym(iwlib
, "iwinfo_close");
773 iw_modenames
= dlsym(iwlib
, "IWINFO_OPMODE_NAMES");
775 if (!iw_backend
|| !iw_close
|| !iw_modenames
)
779 iw
= iw_backend(devname
);
784 o
= blobmsg_open_table(buf
, "iwinfo");
786 iw_call_num(iw
->signal
, devname
, buf
, "signal");
787 iw_call_num(iw
->noise
, devname
, buf
, "noise");
788 iw_call_num(iw
->channel
, devname
, buf
, "channel");
789 iw_call_str(iw
->country
, devname
, buf
, "country");
790 iw_call_str(iw
->phyname
, devname
, buf
, "phy");
791 iw_call_num(iw
->txpower
, devname
, buf
, "txpower");
792 iw_call_num(iw
->txpower_offset
, devname
, buf
, "txpower_offset");
793 iw_call_num(iw
->frequency
, devname
, buf
, "frequency");
794 iw_call_num(iw
->frequency_offset
, devname
, buf
, "frequency_offset");
796 if (!iw
->hwmodelist(devname
, &nret
)) {
797 a
= blobmsg_open_array(buf
, "hwmodes");
799 if (nret
& IWINFO_80211_AC
)
800 blobmsg_add_string(buf
, NULL
, "ac");
802 if (nret
& IWINFO_80211_A
)
803 blobmsg_add_string(buf
, NULL
, "a");
805 if (nret
& IWINFO_80211_B
)
806 blobmsg_add_string(buf
, NULL
, "b");
808 if (nret
& IWINFO_80211_G
)
809 blobmsg_add_string(buf
, NULL
, "g");
811 if (nret
& IWINFO_80211_N
)
812 blobmsg_add_string(buf
, NULL
, "n");
814 blobmsg_close_array(buf
, a
);
817 if (!iw
->htmodelist(devname
, &nret
)) {
818 a
= blobmsg_open_array(buf
, "htmodes");
820 if (nret
& IWINFO_HTMODE_HT20
)
821 blobmsg_add_string(buf
, NULL
, "HT20");
823 if (nret
& IWINFO_HTMODE_HT40
)
824 blobmsg_add_string(buf
, NULL
, "HT40");
826 if (nret
& IWINFO_HTMODE_VHT20
)
827 blobmsg_add_string(buf
, NULL
, "VHT20");
829 if (nret
& IWINFO_HTMODE_VHT40
)
830 blobmsg_add_string(buf
, NULL
, "VHT40");
832 if (nret
& IWINFO_HTMODE_VHT80
)
833 blobmsg_add_string(buf
, NULL
, "VHT80");
835 if (nret
& IWINFO_HTMODE_VHT80_80
)
836 blobmsg_add_string(buf
, NULL
, "VHT80+80");
838 if (nret
& IWINFO_HTMODE_VHT160
)
839 blobmsg_add_string(buf
, NULL
, "VHT160");
841 blobmsg_close_array(buf
, a
);
844 if (!iw
->hardware_id(devname
, (char *)&ids
)) {
845 o2
= blobmsg_open_table(buf
, "hardware");
847 a
= blobmsg_open_array(buf
, "id");
848 blobmsg_add_u32(buf
, NULL
, ids
.vendor_id
);
849 blobmsg_add_u32(buf
, NULL
, ids
.device_id
);
850 blobmsg_add_u32(buf
, NULL
, ids
.subsystem_vendor_id
);
851 blobmsg_add_u32(buf
, NULL
, ids
.subsystem_device_id
);
852 blobmsg_close_array(buf
, a
);
854 iw_call_str(iw
->hardware_name
, devname
, buf
, "name");
856 blobmsg_close_table(buf
, o2
);
860 iw_call_num(iw
->quality
, devname
, buf
, "quality");
861 iw_call_num(iw
->quality_max
, devname
, buf
, "quality_max");
862 iw_call_num(iw
->bitrate
, devname
, buf
, "bitrate");
864 if (!iw
->mode(devname
, &nret
))
865 blobmsg_add_string(buf
, "mode", iw_modenames
[nret
]);
867 iw_call_str(iw
->ssid
, devname
, buf
, "ssid");
868 iw_call_str(iw
->bssid
, devname
, buf
, "bssid");
870 if (!iw
->encryption(devname
, (char *)&crypto
)) {
871 o2
= blobmsg_open_table(buf
, "encryption");
873 blobmsg_add_u8(buf
, "enabled", crypto
.enabled
);
875 if (crypto
.enabled
) {
876 if (!crypto
.wpa_version
) {
877 a
= blobmsg_open_array(buf
, "wep");
879 if (crypto
.auth_algs
& IWINFO_AUTH_OPEN
)
880 blobmsg_add_string(buf
, NULL
, "open");
882 if (crypto
.auth_algs
& IWINFO_AUTH_SHARED
)
883 blobmsg_add_string(buf
, NULL
, "shared");
885 blobmsg_close_array(buf
, a
);
888 a
= blobmsg_open_array(buf
, "wpa");
890 for (nret
= 1; nret
<= 3; nret
++)
891 if (crypto
.wpa_version
& (1 << (nret
- 1)))
892 blobmsg_add_u32(buf
, NULL
, nret
);
894 blobmsg_close_array(buf
, a
);
896 a
= blobmsg_open_array(buf
, "authentication");
898 if (crypto
.auth_suites
& IWINFO_KMGMT_PSK
)
899 blobmsg_add_string(buf
, NULL
, "psk");
901 if (crypto
.auth_suites
& IWINFO_KMGMT_8021x
)
902 blobmsg_add_string(buf
, NULL
, "802.1x");
904 if (crypto
.auth_suites
& IWINFO_KMGMT_SAE
)
905 blobmsg_add_string(buf
, NULL
, "sae");
907 if (crypto
.auth_suites
& IWINFO_KMGMT_OWE
)
908 blobmsg_add_string(buf
, NULL
, "owe");
910 if (!crypto
.auth_suites
||
911 (crypto
.auth_suites
& IWINFO_KMGMT_NONE
))
912 blobmsg_add_string(buf
, NULL
, "none");
914 blobmsg_close_array(buf
, a
);
917 a
= blobmsg_open_array(buf
, "ciphers");
918 nret
= crypto
.pair_ciphers
| crypto
.group_ciphers
;
920 if (nret
& IWINFO_CIPHER_WEP40
)
921 blobmsg_add_string(buf
, NULL
, "wep-40");
923 if (nret
& IWINFO_CIPHER_WEP104
)
924 blobmsg_add_string(buf
, NULL
, "wep-104");
926 if (nret
& IWINFO_CIPHER_TKIP
)
927 blobmsg_add_string(buf
, NULL
, "tkip");
929 if (nret
& IWINFO_CIPHER_CCMP
)
930 blobmsg_add_string(buf
, NULL
, "ccmp");
932 if (nret
& IWINFO_CIPHER_WRAP
)
933 blobmsg_add_string(buf
, NULL
, "wrap");
935 if (nret
& IWINFO_CIPHER_AESOCB
)
936 blobmsg_add_string(buf
, NULL
, "aes-ocb");
938 if (nret
& IWINFO_CIPHER_CKIP
)
939 blobmsg_add_string(buf
, NULL
, "ckip");
941 if (!nret
|| (nret
& IWINFO_CIPHER_NONE
))
942 blobmsg_add_string(buf
, NULL
, "none");
944 blobmsg_close_array(buf
, a
);
947 blobmsg_close_table(buf
, o2
);
951 blobmsg_close_table(buf
, o
);
958 static void rpc_luci_get_wireless_devices_cb(struct ubus_request
*req
,
959 int type
, struct blob_attr
*msg
)
961 struct blob_attr
*wifi
, *cur
, *iface
, *cur2
;
962 struct reply_context
*rctx
= req
->priv
;
963 const char *name
, *first_ifname
;
964 int rem
, rem2
, rem3
, rem4
;
967 blob_for_each_attr(wifi
, msg
, rem
) {
968 if (blobmsg_type(wifi
) != BLOBMSG_TYPE_TABLE
||
969 blobmsg_name(wifi
) == NULL
)
972 o
= blobmsg_open_table(&rctx
->blob
, blobmsg_name(wifi
));
974 rem2
= blobmsg_data_len(wifi
);
977 __blob_for_each_attr(cur
, blobmsg_data(wifi
), rem2
) {
978 name
= blobmsg_name(cur
);
980 if (!name
|| !strcmp(name
, "iwinfo")) {
983 else if (!strcmp(name
, "interfaces")) {
984 if (blobmsg_type(cur
) != BLOBMSG_TYPE_ARRAY
)
987 a
= blobmsg_open_array(&rctx
->blob
, "interfaces");
989 rem3
= blobmsg_data_len(cur
);
991 __blob_for_each_attr(iface
, blobmsg_data(cur
), rem3
) {
992 if (blobmsg_type(iface
) != BLOBMSG_TYPE_TABLE
)
995 o2
= blobmsg_open_table(&rctx
->blob
, NULL
);
997 rem4
= blobmsg_data_len(iface
);
1000 __blob_for_each_attr(cur2
, blobmsg_data(iface
), rem4
) {
1001 if (!strcmp(blobmsg_name(cur2
), "ifname"))
1002 name
= blobmsg_get_string(cur2
);
1003 else if (!strcmp(blobmsg_name(cur2
), "iwinfo"))
1006 blobmsg_add_blob(&rctx
->blob
, cur2
);
1010 if (rpc_luci_get_iwinfo(&rctx
->blob
, name
, false))
1011 first_ifname
= first_ifname
? first_ifname
: name
;
1013 blobmsg_close_table(&rctx
->blob
, o2
);
1016 blobmsg_close_array(&rctx
->blob
, a
);
1019 blobmsg_add_blob(&rctx
->blob
, cur
);
1023 rpc_luci_get_iwinfo(&rctx
->blob
,
1024 first_ifname
? first_ifname
: blobmsg_name(wifi
),
1027 blobmsg_close_table(&rctx
->blob
, o
);
1030 finish_request(rctx
, UBUS_STATUS_OK
);
1034 rpc_luci_get_wireless_devices(struct ubus_context
*ctx
,
1035 struct ubus_object
*obj
,
1036 struct ubus_request_data
*req
,
1038 struct blob_attr
*msg
)
1040 struct reply_context
*rctx
= defer_request(ctx
, req
);
1043 return UBUS_STATUS_UNKNOWN_ERROR
;
1045 if (!invoke_ubus(ctx
, "network.wireless", "status", NULL
,
1046 rpc_luci_get_wireless_devices_cb
, rctx
))
1047 return finish_request(rctx
, UBUS_STATUS_NOT_FOUND
);
1049 return UBUS_STATUS_OK
;
1053 struct avl_node avl
;
1056 struct in6_addr ip6
;
1060 nl_cb_done(struct nl_msg
*msg
, void *arg
)
1062 struct reply_context
*rctx
= arg
;
1068 nl_cb_error(struct sockaddr_nl
*nla
, struct nlmsgerr
*err
, void *arg
)
1070 struct reply_context
*rctx
= arg
;
1075 static struct host_hint
*
1076 rpc_luci_get_host_hint(struct reply_context
*rctx
, struct ether_addr
*ea
)
1078 struct host_hint
*hint
;
1085 hint
= avl_find_element(&rctx
->avl
, mac
, hint
, avl
);
1088 hint
= calloc_a(sizeof(*hint
), &p
, strlen(mac
) + 1);
1093 hint
->avl
.key
= strcpy(p
, mac
);
1094 avl_insert(&rctx
->avl
, &hint
->avl
);
1100 static int nl_cb_dump_neigh(struct nl_msg
*msg
, void *arg
)
1102 struct reply_context
*rctx
= arg
;
1103 struct ether_addr
*mac
;
1104 struct in6_addr
*dst
;
1105 struct nlmsghdr
*hdr
= nlmsg_hdr(msg
);
1106 struct ndmsg
*nd
= NLMSG_DATA(hdr
);
1107 struct nlattr
*tb
[NDA_MAX
+1];
1108 struct host_hint
*hint
;
1110 rctx
->pending
= !!(hdr
->nlmsg_flags
& NLM_F_MULTI
);
1112 if (hdr
->nlmsg_type
!= RTM_NEWNEIGH
||
1113 (nd
->ndm_family
!= AF_INET
&& nd
->ndm_family
!= AF_INET6
))
1116 if (!(nd
->ndm_state
& (0xFF & ~NUD_NOARP
)))
1119 nlmsg_parse(hdr
, sizeof(*nd
), tb
, NDA_MAX
, NULL
);
1121 mac
= tb
[NDA_LLADDR
] ? RTA_DATA(tb
[NDA_LLADDR
]) : NULL
;
1122 dst
= tb
[NDA_DST
] ? RTA_DATA(tb
[NDA_DST
]) : NULL
;
1127 hint
= rpc_luci_get_host_hint(rctx
, mac
);
1132 if (nd
->ndm_family
== AF_INET
)
1133 hint
->ip
= *(struct in_addr
*)dst
;
1135 hint
->ip6
= *(struct in6_addr
*)dst
;
1141 rpc_luci_get_host_hints_nl(struct reply_context
*rctx
)
1143 struct nl_sock
*sock
= NULL
;
1144 struct nl_msg
*msg
= NULL
;
1145 struct nl_cb
*cb
= NULL
;
1146 struct ndmsg ndm
= {};
1148 sock
= nl_socket_alloc();
1153 if (nl_connect(sock
, NETLINK_ROUTE
))
1156 cb
= nl_cb_alloc(NL_CB_DEFAULT
);
1161 msg
= nlmsg_alloc_simple(RTM_GETNEIGH
, NLM_F_REQUEST
| NLM_F_DUMP
);
1166 nlmsg_append(msg
, &ndm
, sizeof(ndm
), 0);
1168 nl_cb_set(cb
, NL_CB_VALID
, NL_CB_CUSTOM
, nl_cb_dump_neigh
, rctx
);
1169 nl_cb_set(cb
, NL_CB_FINISH
, NL_CB_CUSTOM
, nl_cb_done
, rctx
);
1170 nl_cb_err(cb
, NL_CB_CUSTOM
, nl_cb_error
, rctx
);
1172 avl_init(&rctx
->avl
, avl_strcmp
, false, NULL
);
1176 nl_send_auto_complete(sock
, msg
);
1178 while (rctx
->pending
)
1179 nl_recvmsgs(sock
, cb
);
1183 nl_socket_free(sock
);
1193 rpc_luci_get_host_hints_ether(struct reply_context
*rctx
)
1195 struct host_hint
*hint
;
1200 f
= fopen("/etc/ethers", "r");
1205 while (fgets(buf
, sizeof(buf
), f
)) {
1206 p
= strtok(buf
, " \t\n");
1207 hint
= rpc_luci_get_host_hint(rctx
, p
? ether_aton(p
) : NULL
);
1212 p
= strtok(NULL
, " \t\n");
1217 if (inet_pton(AF_INET
, p
, &in
) == 1) {
1218 if (hint
->ip
.s_addr
== 0)
1221 else if (*p
&& !hint
->hostname
) {
1222 hint
->hostname
= strdup(p
);
1230 rpc_luci_get_host_hints_uci(struct reply_context
*rctx
)
1232 struct uci_ptr ptr
= { .package
= "dhcp" };
1233 struct uci_context
*uci
= NULL
;
1234 struct uci_package
*pkg
= NULL
;
1235 struct in6_addr empty
= {};
1236 struct lease_entry
*lease
;
1237 struct host_hint
*hint
;
1238 struct uci_element
*e
, *l
;
1239 struct uci_section
*s
;
1243 uci
= uci_alloc_context();
1248 uci_load(uci
, ptr
.package
, &pkg
);
1253 uci_foreach_element(&pkg
->sections
, e
)
1255 s
= uci_to_section(e
);
1257 if (strcmp(s
->type
, "host"))
1260 ptr
.section
= s
->e
.name
;
1266 if (uci_lookup_ptr(uci
, &ptr
, NULL
, true) || ptr
.o
== NULL
)
1269 if (ptr
.o
->type
!= UCI_TYPE_STRING
)
1272 if (inet_pton(AF_INET
, ptr
.o
->v
.string
, &in
) != 1)
1275 ptr
.option
= "name";
1278 if (!uci_lookup_ptr(uci
, &ptr
, NULL
, true) && ptr
.o
!= NULL
&&
1279 ptr
.o
->type
== UCI_TYPE_STRING
)
1280 n
= ptr
.o
->v
.string
;
1287 if (uci_lookup_ptr(uci
, &ptr
, NULL
, true) || ptr
.o
== NULL
)
1290 if (ptr
.o
->type
== UCI_TYPE_STRING
) {
1291 for (p
= strtok(ptr
.o
->v
.string
, " \t");
1293 p
= strtok(NULL
, " \t")) {
1294 hint
= rpc_luci_get_host_hint(rctx
, ether_aton(p
));
1299 if (hint
->ip
.s_addr
== 0)
1302 if (n
&& !hint
->hostname
)
1303 hint
->hostname
= strdup(n
);
1306 else if (ptr
.o
->type
== UCI_TYPE_LIST
) {
1307 uci_foreach_element(&ptr
.o
->v
.list
, l
) {
1308 hint
= rpc_luci_get_host_hint(rctx
, ether_aton(l
->name
));
1313 if (hint
->ip
.s_addr
== 0)
1316 if (n
&& !hint
->hostname
)
1317 hint
->hostname
= strdup(n
);
1324 while ((lease
= lease_next()) != NULL
) {
1325 hint
= rpc_luci_get_host_hint(rctx
, &lease
->mac
);
1330 if (lease
->af
== AF_INET
&& hint
->ip
.s_addr
== 0)
1331 hint
->ip
= lease
->addr
.in
;
1332 else if (lease
->af
== AF_INET6
&&
1333 !memcmp(&hint
->ip6
, &empty
, sizeof(empty
)))
1334 hint
->ip6
= lease
->addr
.in6
;
1336 if (lease
->hostname
&& !hint
->hostname
)
1337 hint
->hostname
= strdup(lease
->hostname
);
1344 uci_free_context(uci
);
1348 rpc_luci_get_host_hints_ifaddrs(struct reply_context
*rctx
)
1350 struct ether_addr empty_ea
= {};
1351 struct in6_addr empty_in6
= {};
1352 struct ifaddrs
*ifaddr
, *ifa
;
1353 struct sockaddr_ll
*sll
;
1354 struct avl_tree devices
;
1355 struct host_hint
*hint
;
1357 struct avl_node avl
;
1358 struct ether_addr ea
;
1359 struct in6_addr in6
;
1361 } *device
, *nextdevice
;
1364 avl_init(&devices
, avl_strcmp
, false, NULL
);
1366 if (getifaddrs(&ifaddr
) == -1)
1369 for (ifa
= ifaddr
; ifa
!= NULL
; ifa
= ifa
->ifa_next
) {
1373 device
= avl_find_element(&devices
, ifa
->ifa_name
, device
, avl
);
1376 device
= calloc_a(sizeof(*device
), &p
, strlen(ifa
->ifa_name
) + 1);
1381 device
->avl
.key
= strcpy(p
, ifa
->ifa_name
);
1382 avl_insert(&devices
, &device
->avl
);
1385 switch (ifa
->ifa_addr
->sa_family
) {
1387 sll
= (struct sockaddr_ll
*)ifa
->ifa_addr
;
1389 if (sll
->sll_halen
== 6)
1390 memcpy(&device
->ea
, sll
->sll_addr
, 6);
1395 device
->in6
= ((struct sockaddr_in6
*)ifa
->ifa_addr
)->sin6_addr
;
1399 device
->in
= ((struct sockaddr_in
*)ifa
->ifa_addr
)->sin_addr
;
1404 freeifaddrs(ifaddr
);
1406 avl_for_each_element_safe(&devices
, device
, avl
, nextdevice
) {
1407 if (memcmp(&device
->ea
, &empty_ea
, sizeof(empty_ea
)) &&
1408 (memcmp(&device
->in6
, &empty_in6
, sizeof(empty_in6
)) ||
1409 device
->in
.s_addr
!= 0)) {
1410 hint
= rpc_luci_get_host_hint(rctx
, &device
->ea
);
1413 if (hint
->ip
.s_addr
== 0 && device
->in
.s_addr
!= 0)
1414 hint
->ip
= device
->in
;
1416 if (memcmp(&hint
->ip6
, &empty_in6
, sizeof(empty_in6
)) == 0 &&
1417 memcmp(&device
->in6
, &empty_in6
, sizeof(empty_in6
)) != 0)
1418 hint
->ip6
= device
->in6
;
1422 avl_delete(&devices
, &device
->avl
);
1428 rpc_luci_get_host_hints_finish(struct reply_context
*rctx
);
1431 rpc_luci_get_host_hints_rrdns_cb(struct ubus_request
*req
, int type
,
1432 struct blob_attr
*msg
)
1434 struct reply_context
*rctx
= req
->priv
;
1435 struct host_hint
*hint
;
1436 struct blob_attr
*cur
;
1437 struct in6_addr in6
;
1442 blob_for_each_attr(cur
, msg
, rem
) {
1443 if (blobmsg_type(cur
) != BLOBMSG_TYPE_STRING
)
1446 if (inet_pton(AF_INET6
, blobmsg_name(cur
), &in6
) == 1) {
1447 avl_for_each_element(&rctx
->avl
, hint
, avl
) {
1448 if (!memcmp(&hint
->ip6
, &in6
, sizeof(in6
))) {
1450 free(hint
->hostname
);
1452 hint
->hostname
= strdup(blobmsg_get_string(cur
));
1457 else if (inet_pton(AF_INET
, blobmsg_name(cur
), &in
) == 1) {
1458 avl_for_each_element(&rctx
->avl
, hint
, avl
) {
1459 if (!memcmp(&hint
->ip
, &in
, sizeof(in
))) {
1461 free(hint
->hostname
);
1463 hint
->hostname
= strdup(blobmsg_get_string(cur
));
1471 rpc_luci_get_host_hints_finish(rctx
);
1475 rpc_luci_get_host_hints_rrdns(struct reply_context
*rctx
)
1477 struct in6_addr empty_in6
= {};
1478 char buf
[INET6_ADDRSTRLEN
];
1479 struct blob_buf req
= {};
1480 struct host_hint
*hint
;
1484 blob_buf_init(&req
, 0);
1486 a
= blobmsg_open_array(&req
, "addrs");
1488 avl_for_each_element(&rctx
->avl
, hint
, avl
) {
1489 if (hint
->ip
.s_addr
!= 0) {
1490 inet_ntop(AF_INET
, &hint
->ip
, buf
, sizeof(buf
));
1491 blobmsg_add_string(&req
, NULL
, buf
);
1494 else if (memcmp(&hint
->ip6
, &empty_in6
, sizeof(empty_in6
))) {
1495 inet_ntop(AF_INET6
, &hint
->ip6
, buf
, sizeof(buf
));
1496 blobmsg_add_string(&req
, NULL
, buf
);
1501 blobmsg_close_array(&req
, a
);
1504 blobmsg_add_u32(&req
, "timeout", 250);
1505 blobmsg_add_u32(&req
, "limit", n
);
1507 if (!invoke_ubus(rctx
->context
, "network.rrdns", "lookup", &req
,
1508 rpc_luci_get_host_hints_rrdns_cb
, rctx
))
1509 rpc_luci_get_host_hints_finish(rctx
);
1512 rpc_luci_get_host_hints_finish(rctx
);
1515 blob_buf_free(&req
);
1519 rpc_luci_get_host_hints_finish(struct reply_context
*rctx
)
1521 struct host_hint
*hint
, *nexthint
;
1522 char buf
[INET6_ADDRSTRLEN
];
1523 struct in6_addr in6
= {};
1524 struct in_addr in
= {};
1527 avl_for_each_element_safe(&rctx
->avl
, hint
, avl
, nexthint
) {
1528 o
= blobmsg_open_table(&rctx
->blob
, hint
->avl
.key
);
1530 if (memcmp(&hint
->ip
, &in
, sizeof(in
))) {
1531 inet_ntop(AF_INET
, &hint
->ip
, buf
, sizeof(buf
));
1532 blobmsg_add_string(&rctx
->blob
, "ipv4", buf
);
1535 if (memcmp(&hint
->ip6
, &in6
, sizeof(in6
))) {
1536 inet_ntop(AF_INET6
, &hint
->ip6
, buf
, sizeof(buf
));
1537 blobmsg_add_string(&rctx
->blob
, "ipv6", buf
);
1541 blobmsg_add_string(&rctx
->blob
, "name", hint
->hostname
);
1543 blobmsg_close_table(&rctx
->blob
, o
);
1545 avl_delete(&rctx
->avl
, &hint
->avl
);
1548 free(hint
->hostname
);
1553 return finish_request(rctx
, UBUS_STATUS_OK
);
1557 rpc_luci_get_host_hints(struct ubus_context
*ctx
, struct ubus_object
*obj
,
1558 struct ubus_request_data
*req
, const char *method
,
1559 struct blob_attr
*msg
)
1561 struct reply_context
*rctx
= defer_request(ctx
, req
);
1564 return UBUS_STATUS_UNKNOWN_ERROR
;
1566 rpc_luci_get_host_hints_nl(rctx
);
1567 rpc_luci_get_host_hints_uci(rctx
);
1568 rpc_luci_get_host_hints_ether(rctx
);
1569 rpc_luci_get_host_hints_ifaddrs(rctx
);
1570 rpc_luci_get_host_hints_rrdns(rctx
);
1572 return UBUS_STATUS_OK
;
1576 rpc_luci_get_board_json(struct ubus_context
*ctx
, struct ubus_object
*obj
,
1577 struct ubus_request_data
*req
, const char *method
,
1578 struct blob_attr
*msg
)
1580 blob_buf_init(&blob
, 0);
1582 if (!blobmsg_add_json_from_file(&blob
, "/etc/board.json"))
1583 return UBUS_STATUS_UNKNOWN_ERROR
;
1585 ubus_send_reply(ctx
, req
, blob
.head
);
1586 return UBUS_STATUS_OK
;
1590 rpc_luci_get_dsl_status(struct ubus_context
*ctx
, struct ubus_object
*obj
,
1591 struct ubus_request_data
*req
, const char *method
,
1592 struct blob_attr
*msg
)
1594 char line
[128], *p
, *s
;
1597 cmd
= popen("/etc/init.d/dsl_control lucistat", "r");
1600 return UBUS_STATUS_NOT_FOUND
;
1602 blob_buf_init(&blob
, 0);
1604 while (fgets(line
, sizeof(line
), cmd
)) {
1605 if (strncmp(line
, "dsl.", 4))
1608 p
= strchr(line
, '=');
1613 s
= p
+ strlen(p
) - 1;
1615 while (s
>= p
&& isspace(*s
))
1620 if (!strcmp(p
, "nil"))
1624 blobmsg_add_u32(&blob
, line
+ 4, strtoul(p
, NULL
, 0));
1626 else if (*p
== '"') {
1627 s
= p
+ strlen(p
) - 1;
1629 if (s
>= p
&& *s
== '"')
1632 blobmsg_add_string(&blob
, line
+ 4, p
+ 1);
1638 ubus_send_reply(ctx
, req
, blob
.head
);
1639 return UBUS_STATUS_OK
;
1648 static const struct blobmsg_policy rpc_get_leases_policy
[__RPC_L_MAX
] = {
1649 [RPC_L_FAMILY
] = { .name
= "family", .type
= BLOBMSG_TYPE_INT32
}
1653 rpc_luci_get_dhcp_leases(struct ubus_context
*ctx
, struct ubus_object
*obj
,
1654 struct ubus_request_data
*req
, const char *method
,
1655 struct blob_attr
*msg
)
1657 struct blob_attr
*tb
[__RPC_L_MAX
];
1658 struct ether_addr emptymac
= {};
1659 struct lease_entry
*lease
;
1660 char s
[INET6_ADDRSTRLEN
];
1661 struct uci_context
*uci
;
1665 blobmsg_parse(rpc_get_leases_policy
, __RPC_L_MAX
, tb
,
1666 blob_data(msg
), blob_len(msg
));
1668 switch (tb
[RPC_L_FAMILY
] ? blobmsg_get_u32(tb
[RPC_L_FAMILY
]) : 0) {
1682 return UBUS_STATUS_INVALID_ARGUMENT
;
1685 uci
= uci_alloc_context();
1688 return UBUS_STATUS_UNKNOWN_ERROR
;
1690 blob_buf_init(&blob
, 0);
1692 for (af
= family
? family
: AF_INET
;
1694 af
= (family
== 0) ? (af
== AF_INET
? AF_INET6
: 0) : 0) {
1696 a
= blobmsg_open_array(&blob
, (af
== AF_INET
) ? "dhcp_leases"
1701 while ((lease
= lease_next()) != NULL
) {
1702 if (lease
->af
!= af
)
1705 o
= blobmsg_open_table(&blob
, NULL
);
1707 if (lease
->expire
== -1)
1708 blobmsg_add_u8(&blob
, "expires", 0);
1710 blobmsg_add_u32(&blob
, "expires", lease
->expire
);
1712 if (lease
->hostname
)
1713 blobmsg_add_string(&blob
, "hostname", lease
->hostname
);
1715 if (memcmp(&lease
->mac
, &emptymac
, sizeof(emptymac
)))
1716 blobmsg_add_string(&blob
, "macaddr", ea2str(&lease
->mac
));
1719 blobmsg_add_string(&blob
, "duid", lease
->duid
);
1721 inet_ntop(lease
->af
, &lease
->addr
.in6
, s
, sizeof(s
));
1722 blobmsg_add_string(&blob
, (af
== AF_INET
) ? "ipaddr" : "ip6addr",
1725 blobmsg_close_table(&blob
, o
);
1730 blobmsg_close_array(&blob
, a
);
1733 uci_free_context(uci
);
1734 ubus_send_reply(ctx
, req
, blob
.head
);
1736 return UBUS_STATUS_OK
;
1740 rpc_luci_api_init(const struct rpc_daemon_ops
*o
, struct ubus_context
*ctx
)
1742 static const struct ubus_method luci_methods
[] = {
1743 UBUS_METHOD_NOARG("getNetworkDevices", rpc_luci_get_network_devices
),
1744 UBUS_METHOD_NOARG("getWirelessDevices", rpc_luci_get_wireless_devices
),
1745 UBUS_METHOD_NOARG("getHostHints", rpc_luci_get_host_hints
),
1746 UBUS_METHOD_NOARG("getBoardJSON", rpc_luci_get_board_json
),
1747 UBUS_METHOD_NOARG("getDSLStatus", rpc_luci_get_dsl_status
),
1748 UBUS_METHOD("getDHCPLeases", rpc_luci_get_dhcp_leases
, rpc_get_leases_policy
)
1751 static struct ubus_object_type luci_type
=
1752 UBUS_OBJECT_TYPE("rpcd-luci", luci_methods
);
1754 static struct ubus_object obj
= {
1757 .methods
= luci_methods
,
1758 .n_methods
= ARRAY_SIZE(luci_methods
),
1761 return ubus_add_object(ctx
, &obj
);
1764 struct rpc_plugin rpc_plugin
= {
1765 .init
= rpc_luci_api_init