Connection track mark based DNS query filtering.

This extends query filtering support beyond what is currently possible
with the `--ipset` configuration option, by adding support for:
1) Specifying allowlists on a per-client basis, based on their
   associated Linux connection track mark.
2) Dynamic configuration of allowlists via Ubus.
3) Reporting when a DNS query resolves or is rejected via Ubus.
4) DNS name patterns containing wildcards.

Disallowed queries are not forwarded; they are rejected
with a REFUSED error code.

Signed-off-by: Etan Kissling <etan_kissling@apple.com>
(addressed reviewer feedback)
Signed-off-by: Etan Kissling <etan.kissling@gmail.com>
This commit is contained in:
Etan Kissling
2021-06-16 21:56:17 +00:00
committed by Simon Kelley
parent cbd76447fd
commit 627056febb
8 changed files with 979 additions and 13 deletions

View File

@@ -28,10 +28,38 @@ 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 =
@@ -163,6 +191,122 @@ static int ubus_handle_metrics(struct ubus_context *ctx, struct ubus_object *obj
return ubus_send_reply(ctx, req, b.head);
}
#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
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;
@@ -182,9 +326,47 @@ void ubus_event_bcast(const char *type, const char *mac, const char *ip, const c
blobmsg_add_string(&b, "interface", interface);
ret = ubus_notify(ubus, &ubus_object, type, b.head, -1);
if (!ret)
if (ret)
my_syslog(LOG_ERR, _("Failed to send UBus event: %s"), ubus_strerror(ret));
}
#ifdef HAVE_CONNTRACK
void ubus_event_bcast_connmark_allowlist_refused(u32 mark, const char *name)
{
struct ubus_context *ubus = (struct ubus_context *)daemon->ubus;
int ret;
if (!ubus || !notify)
return;
blob_buf_init(&b, 0);
blobmsg_add_u32(&b, "mark", mark);
blobmsg_add_string(&b, "name", name);
ret = ubus_notify(ubus, &ubus_object, "connmark-allowlist.refused", b.head, -1);
if (ret)
my_syslog(LOG_ERR, _("Failed to send UBus event: %s"), ubus_strerror(ret));
}
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;
int ret;
if (!ubus || !notify)
return;
blob_buf_init(&b, 0);
blobmsg_add_u32(&b, "mark", mark);
blobmsg_add_string(&b, "name", name);
blobmsg_add_string(&b, "value", value);
blobmsg_add_u32(&b, "ttl", ttl);
ret = ubus_notify(ubus, &ubus_object, "connmark-allowlist.resolved", b.head, /* timeout: */ 1000);
if (ret)
my_syslog(LOG_ERR, _("Failed to send UBus event: %s"), ubus_strerror(ret));
}
#endif
#endif /* HAVE_UBUS */