/* dnsmasq is Copyright (c) 2000-2024 Simon Kelley This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; version 2 dated June, 1991, or (at your option) version 3 dated 29 June, 2007. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "dnsmasq.h" #ifdef HAVE_UBUS #include static struct blob_buf b; static int error_logged = 0; static int ubus_handle_metrics(struct ubus_context *ctx, struct ubus_object *obj, struct ubus_request_data *req, const char *method, struct blob_attr *msg); #ifdef HAVE_CONNTRACK enum { SET_CONNMARK_ALLOWLIST_MARK, SET_CONNMARK_ALLOWLIST_MASK, SET_CONNMARK_ALLOWLIST_PATTERNS }; static const struct blobmsg_policy set_connmark_allowlist_policy[] = { [SET_CONNMARK_ALLOWLIST_MARK] = { .name = "mark", .type = BLOBMSG_TYPE_INT32 }, [SET_CONNMARK_ALLOWLIST_MASK] = { .name = "mask", .type = BLOBMSG_TYPE_INT32 }, [SET_CONNMARK_ALLOWLIST_PATTERNS] = { .name = "patterns", .type = BLOBMSG_TYPE_ARRAY } }; static int ubus_handle_set_connmark_allowlist(struct ubus_context *ctx, struct ubus_object *obj, struct ubus_request_data *req, const char *method, struct blob_attr *msg); #endif static void ubus_subscribe_cb(struct ubus_context *ctx, struct ubus_object *obj); static const struct ubus_method ubus_object_methods[] = { UBUS_METHOD_NOARG("metrics", ubus_handle_metrics), #ifdef HAVE_CONNTRACK UBUS_METHOD("set_connmark_allowlist", ubus_handle_set_connmark_allowlist, set_connmark_allowlist_policy), #endif }; static struct ubus_object_type ubus_object_type = UBUS_OBJECT_TYPE("dnsmasq", ubus_object_methods); static struct ubus_object ubus_object = { .name = NULL, .type = &ubus_object_type, .methods = ubus_object_methods, .n_methods = ARRAY_SIZE(ubus_object_methods), .subscribe_cb = ubus_subscribe_cb, }; static struct ubus_object_type ubus_dns_object_type = { .name = "dnsmasq.dns" }; static struct ubus_object ubus_dns_object = { .type = &ubus_dns_object_type, }; static void ubus_subscribe_cb(struct ubus_context *ctx, struct ubus_object *obj) { (void)ctx; my_syslog(LOG_DEBUG, _("UBus subscription callback: %s subscriber(s)"), obj->has_subscribers ? "1" : "0"); } static void ubus_destroy(struct ubus_context *ubus) { ubus_free(ubus); daemon->ubus = NULL; /* Forces re-initialization when we're reusing the same definitions later on. */ ubus_object.id = 0; ubus_object_type.id = 0; } static void ubus_disconnect_cb(struct ubus_context *ubus) { int ret; ret = ubus_reconnect(ubus, NULL); if (ret) { my_syslog(LOG_ERR, _("Cannot reconnect to UBus: %s"), ubus_strerror(ret)); ubus_destroy(ubus); } } char *ubus_init() { struct ubus_context *ubus = NULL; char *dns_name; int ret = 0; if (!(ubus = ubus_connect(NULL))) return NULL; dns_name = whine_malloc(strlen(daemon->ubus_name) + 5); sprintf(dns_name, "%s.dns", daemon->ubus_name); ubus_object.name = daemon->ubus_name; ubus_dns_object.name = dns_name; ret = ubus_add_object(ubus, &ubus_object); if (!ret) ret = ubus_add_object(ubus, &ubus_dns_object); if (ret) { ubus_destroy(ubus); return (char *)ubus_strerror(ret); } ubus->connection_lost = ubus_disconnect_cb; daemon->ubus = ubus; error_logged = 0; return NULL; } void set_ubus_listeners() { struct ubus_context *ubus = (struct ubus_context *)daemon->ubus; if (!ubus) { if (!error_logged) { my_syslog(LOG_ERR, _("Cannot set UBus listeners: no connection")); error_logged = 1; } return; } error_logged = 0; poll_listen(ubus->sock.fd, POLLIN); poll_listen(ubus->sock.fd, POLLERR); poll_listen(ubus->sock.fd, POLLHUP); } void check_ubus_listeners() { struct ubus_context *ubus = (struct ubus_context *)daemon->ubus; if (!ubus) { if (!error_logged) { my_syslog(LOG_ERR, _("Cannot poll UBus listeners: no connection")); error_logged = 1; } return; } error_logged = 0; if (poll_check(ubus->sock.fd, POLLIN)) ubus_handle_event(ubus); if (poll_check(ubus->sock.fd, POLLHUP | POLLERR)) { my_syslog(LOG_INFO, _("Disconnecting from UBus")); ubus_destroy(ubus); } } #define CHECK(stmt) \ do { \ int e = (stmt); \ if (e) \ { \ my_syslog(LOG_ERR, _("UBus command failed: %d (%s)"), e, #stmt); \ return (UBUS_STATUS_UNKNOWN_ERROR); \ } \ } while (0) void drop_ubus_listeners() { struct ubus_context *ubus = (struct ubus_context *)daemon->ubus; if (!ubus) return; ubus_free(ubus); daemon->ubus = NULL; } static int ubus_handle_metrics(struct ubus_context *ctx, struct ubus_object *obj, struct ubus_request_data *req, const char *method, struct blob_attr *msg) { int i; (void)obj; (void)method; (void)msg; CHECK(blob_buf_init(&b, BLOBMSG_TYPE_TABLE)); for (i=0; i < __METRIC_MAX; i++) CHECK(blobmsg_add_u32(&b, get_metric_name(i), daemon->metrics[i])); CHECK(ubus_send_reply(ctx, req, b.head)); return UBUS_STATUS_OK; } #ifdef HAVE_CONNTRACK static int ubus_handle_set_connmark_allowlist(struct ubus_context *ctx, struct ubus_object *obj, struct ubus_request_data *req, const char *method, struct blob_attr *msg) { const struct blobmsg_policy *policy = set_connmark_allowlist_policy; size_t policy_len = countof(set_connmark_allowlist_policy); struct allowlist *allowlists = NULL, **allowlists_pos; char **patterns = NULL, **patterns_pos; u32 mark, mask = UINT32_MAX; size_t num_patterns = 0; struct blob_attr *tb[policy_len]; struct blob_attr *attr; if (blobmsg_parse(policy, policy_len, tb, blob_data(msg), blob_len(msg))) return UBUS_STATUS_INVALID_ARGUMENT; if (!tb[SET_CONNMARK_ALLOWLIST_MARK]) return UBUS_STATUS_INVALID_ARGUMENT; mark = blobmsg_get_u32(tb[SET_CONNMARK_ALLOWLIST_MARK]); if (!mark) return UBUS_STATUS_INVALID_ARGUMENT; if (tb[SET_CONNMARK_ALLOWLIST_MASK]) { mask = blobmsg_get_u32(tb[SET_CONNMARK_ALLOWLIST_MASK]); if (!mask || (mark & ~mask)) return UBUS_STATUS_INVALID_ARGUMENT; } if (tb[SET_CONNMARK_ALLOWLIST_PATTERNS]) { struct blob_attr *head = blobmsg_data(tb[SET_CONNMARK_ALLOWLIST_PATTERNS]); size_t len = blobmsg_data_len(tb[SET_CONNMARK_ALLOWLIST_PATTERNS]); __blob_for_each_attr(attr, head, len) { char *pattern; if (blob_id(attr) != BLOBMSG_TYPE_STRING) return UBUS_STATUS_INVALID_ARGUMENT; if (!(pattern = blobmsg_get_string(attr))) return UBUS_STATUS_INVALID_ARGUMENT; if (strcmp(pattern, "*") && !is_valid_dns_name_pattern(pattern)) return UBUS_STATUS_INVALID_ARGUMENT; num_patterns++; } } for (allowlists_pos = &daemon->allowlists; *allowlists_pos; allowlists_pos = &(*allowlists_pos)->next) if ((*allowlists_pos)->mark == mark && (*allowlists_pos)->mask == mask) { struct allowlist *allowlists_next = (*allowlists_pos)->next; for (patterns_pos = (*allowlists_pos)->patterns; *patterns_pos; patterns_pos++) { free(*patterns_pos); *patterns_pos = NULL; } free((*allowlists_pos)->patterns); (*allowlists_pos)->patterns = NULL; free(*allowlists_pos); *allowlists_pos = allowlists_next; break; } if (!num_patterns) return UBUS_STATUS_OK; patterns = whine_malloc((num_patterns + 1) * sizeof(char *)); if (!patterns) goto fail; patterns_pos = patterns; if (tb[SET_CONNMARK_ALLOWLIST_PATTERNS]) { struct blob_attr *head = blobmsg_data(tb[SET_CONNMARK_ALLOWLIST_PATTERNS]); size_t len = blobmsg_data_len(tb[SET_CONNMARK_ALLOWLIST_PATTERNS]); __blob_for_each_attr(attr, head, len) { char *pattern; if (!(pattern = blobmsg_get_string(attr))) goto fail; if (!(*patterns_pos = whine_malloc(strlen(pattern) + 1))) goto fail; strcpy(*patterns_pos++, pattern); } } allowlists = whine_malloc(sizeof(struct allowlist)); if (!allowlists) goto fail; memset(allowlists, 0, sizeof(struct allowlist)); allowlists->mark = mark; allowlists->mask = mask; allowlists->patterns = patterns; allowlists->next = daemon->allowlists; daemon->allowlists = allowlists; return UBUS_STATUS_OK; fail: if (patterns) { for (patterns_pos = patterns; *patterns_pos; patterns_pos++) { free(*patterns_pos); *patterns_pos = NULL; } free(patterns); patterns = NULL; } if (allowlists) { free(allowlists); allowlists = NULL; } return UBUS_STATUS_UNKNOWN_ERROR; } #endif #undef CHECK #define CHECK(stmt) \ do { \ int e = (stmt); \ if (e) \ { \ my_syslog(LOG_ERR, _("UBus command failed: %d (%s)"), e, #stmt); \ return; \ } \ } while (0) int ubus_dns_notify_has_subscribers(void) { return (daemon->ubus && ubus_dns_object.has_subscribers); } struct blob_buf *ubus_dns_notify_prepare(void) { if (!ubus_dns_notify_has_subscribers()) return NULL; blob_buf_init(&b, 0); return &b; } struct ubus_dns_notify_req { struct ubus_notify_request req; ubus_dns_notify_cb cb; void *priv; }; static void dns_notify_cb(struct ubus_notify_request *req, int type, struct blob_attr *msg) { struct ubus_dns_notify_req *dreq = container_of(req, struct ubus_dns_notify_req, req); dreq->cb(msg, dreq->priv); } int ubus_dns_notify(const char *type, ubus_dns_notify_cb cb, void *priv) { struct ubus_context *ubus = (struct ubus_context *)daemon->ubus; struct ubus_dns_notify_req dreq; int ret; if (!ubus || !ubus_dns_object.has_subscribers) return 0; ret = ubus_notify_async(ubus, &ubus_dns_object, type, b.head, &dreq.req); if (ret) return ret; dreq.req.data_cb = dns_notify_cb; dreq.cb = cb; dreq.priv = priv; return ubus_complete_request(ubus, &dreq.req.req, 100); } void ubus_event_bcast(const char *type, const char *mac, const char *ip, const char *name, const char *interface) { struct ubus_context *ubus = (struct ubus_context *)daemon->ubus; if (!ubus || !ubus_object.has_subscribers) return; CHECK(blob_buf_init(&b, BLOBMSG_TYPE_TABLE)); if (mac) CHECK(blobmsg_add_string(&b, "mac", mac)); if (ip) CHECK(blobmsg_add_string(&b, "ip", ip)); if (name) CHECK(blobmsg_add_string(&b, "name", name)); if (interface) CHECK(blobmsg_add_string(&b, "interface", interface)); CHECK(ubus_notify(ubus, &ubus_object, type, b.head, -1)); } #ifdef HAVE_CONNTRACK void ubus_event_bcast_connmark_allowlist_refused(u32 mark, const char *name) { struct ubus_context *ubus = (struct ubus_context *)daemon->ubus; if (!ubus || !ubus_object.has_subscribers) return; CHECK(blob_buf_init(&b, 0)); CHECK(blobmsg_add_u32(&b, "mark", mark)); CHECK(blobmsg_add_string(&b, "name", name)); CHECK(ubus_notify(ubus, &ubus_object, "connmark-allowlist.refused", b.head, -1)); } void ubus_event_bcast_connmark_allowlist_resolved(u32 mark, const char *name, const char *value, u32 ttl) { struct ubus_context *ubus = (struct ubus_context *)daemon->ubus; if (!ubus || !ubus_object.has_subscribers) return; CHECK(blob_buf_init(&b, 0)); CHECK(blobmsg_add_u32(&b, "mark", mark)); CHECK(blobmsg_add_string(&b, "name", name)); CHECK(blobmsg_add_string(&b, "value", value)); CHECK(blobmsg_add_u32(&b, "ttl", ttl)); /* Set timeout to allow UBus subscriber to configure firewall rules before returning. */ CHECK(ubus_notify(ubus, &ubus_object, "connmark-allowlist.resolved", b.head, /* timeout: */ 1000)); } #endif #undef CHECK #endif /* HAVE_UBUS */