summaryrefslogtreecommitdiffhomepage
path: root/iprule.c
diff options
context:
space:
mode:
Diffstat (limited to 'iprule.c')
-rw-r--r--iprule.c176
1 files changed, 155 insertions, 21 deletions
diff --git a/iprule.c b/iprule.c
index 7cf7422..105f469 100644
--- a/iprule.c
+++ b/iprule.c
@@ -2,6 +2,7 @@
* netifd - network interface daemon
* Copyright (C) 2012 Felix Fietkau <nbd@openwrt.org>
* Copyright (C) 2013 Jo-Philipp Wich <jow@openwrt.org>
+ * Copyright (C) 2018 Alexander Couzens <lynxis@fe80.eu>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2
@@ -12,6 +13,7 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
+#include <assert.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
@@ -66,6 +68,16 @@ const struct uci_blob_param_list rule_attr_list = {
.params = rule_attr,
};
+/* interface based rules are dynamic. */
+static bool rule_ready(struct iprule *rule) {
+ if (rule->flags & IPRULE_OUT && rule->out_dev == NULL)
+ return false;
+
+ if (rule->flags & IPRULE_IN && rule->in_dev == NULL)
+ return false;
+
+ return true;
+}
static bool
iprule_parse_mark(const char *mark, struct iprule *rule)
@@ -97,13 +109,102 @@ iprule_parse_mark(const char *mark, struct iprule *rule)
return true;
}
+/* called on interface changes of the incoming interface */
+static void rule_in_cb(
+ struct interface_user *dep,
+ struct interface *iface,
+ enum interface_event ev)
+{
+ struct iprule *rule = container_of(dep, struct iprule, in_iface_user);
+
+ switch (ev) {
+ case IFEV_UP:
+ if (!iface->l3_dev.dev)
+ break;
+ memcpy(rule->in_dev, iface->l3_dev.dev->ifname, sizeof(rule->in_dev));
+ if (rule_ready(rule))
+ system_add_iprule(rule);
+ break;
+ case IFEV_DOWN:
+ case IFEV_UP_FAILED:
+ case IFEV_FREE:
+ if (rule_ready(rule))
+ system_del_iprule(rule);
+ rule->in_dev[0] = 0;
+ break;
+ default:
+ break;
+ }
+}
+
+/* called on interface changes of the outgoing interface */
+static void rule_out_cb(
+ struct interface_user *dep,
+ struct interface *iface,
+ enum interface_event ev)
+{
+ struct iprule *rule = container_of(dep, struct iprule, out_iface_user);
+
+ switch (ev) {
+ case IFEV_UP:
+ if (!iface->l3_dev.dev)
+ break;
+ memcpy(rule->out_dev, iface->l3_dev.dev->ifname, sizeof(rule->out_dev));
+ if (rule_ready(rule))
+ system_add_iprule(rule);
+ break;
+ case IFEV_DOWN:
+ case IFEV_UP_FAILED:
+ case IFEV_FREE:
+ if (rule_ready(rule))
+ system_del_iprule(rule);
+ rule->out_dev[0] = 0;
+ break;
+ default:
+ break;
+ }
+}
+
+/* called on all interface events */
+static void generic_interface_cb(
+ struct interface_user *dep,
+ struct interface *iface,
+ enum interface_event ev)
+{
+ struct iprule *rule;
+
+ if (ev != IFEV_CREATE)
+ return;
+
+ /* add new interfaces to rules */
+ vlist_for_each_element(&iprules, rule, node) {
+ if (rule_ready(rule))
+ continue;
+
+ if (!strcmp(rule->out_iface, iface->name)) {
+ assert(!rule->out_dev);
+ memcpy(rule->out_dev, iface->l3_dev.dev->ifname, sizeof(rule->out_dev));
+ interface_add_user(&rule->out_iface_user, iface);
+ }
+
+ if (!strcmp(rule->in_iface, iface->name)) {
+ assert(!rule->in_dev);
+ memcpy(rule->in_dev, iface->l3_dev.dev->ifname, sizeof(rule->in_dev));
+ interface_add_user(&rule->in_iface_user, iface);
+ }
+ }
+}
+
+struct interface_user generic_listener = {
+ .cb = generic_interface_cb
+};
+
void
iprule_add(struct blob_attr *attr, bool v6)
{
- struct interface *iif = NULL, *oif = NULL;
struct blob_attr *tb[__RULE_MAX], *cur;
- struct interface *iface;
struct iprule *rule;
+ char *iface_name;
int af = v6 ? AF_INET6 : AF_INET;
blobmsg_parse(rule_attr, __RULE_MAX, tb, blobmsg_data(attr), blobmsg_data_len(attr));
@@ -119,26 +220,16 @@ iprule_add(struct blob_attr *attr, bool v6)
rule->invert = blobmsg_get_bool(cur);
if ((cur = tb[RULE_INTERFACE_IN]) != NULL) {
- iif = vlist_find(&interfaces, blobmsg_data(cur), iface, node);
-
- if (!iif || !iif->l3_dev.dev) {
- DPRINTF("Failed to resolve device of network: %s\n", (char *) blobmsg_data(cur));
- goto error;
- }
-
- memcpy(rule->in_dev, iif->l3_dev.dev->ifname, sizeof(rule->in_dev));
+ iface_name = calloc(1, strlen(blobmsg_data(cur)) + 1);
+ rule->in_iface = strcpy(iface_name, blobmsg_data(cur));
+ rule->in_iface_user.cb = &rule_in_cb;
rule->flags |= IPRULE_IN;
}
if ((cur = tb[RULE_INTERFACE_OUT]) != NULL) {
- oif = vlist_find(&interfaces, blobmsg_data(cur), iface, node);
-
- if (!oif || !oif->l3_dev.dev) {
- DPRINTF("Failed to resolve device of network: %s\n", (char *) blobmsg_data(cur));
- goto error;
- }
-
- memcpy(rule->out_dev, oif->l3_dev.dev->ifname, sizeof(rule->out_dev));
+ iface_name = calloc(1, strlen(blobmsg_data(cur)) + 1);
+ rule->out_iface = strcpy(iface_name, blobmsg_data(cur));
+ rule->out_iface_user.cb = &rule_out_cb;
rule->flags |= IPRULE_OUT;
}
@@ -238,6 +329,31 @@ rule_cmp(const void *k1, const void *k2, void *ptr)
return memcmp(k1, k2, sizeof(struct iprule)-offsetof(struct iprule, flags));
}
+static void deregister_interfaces(struct iprule *rule)
+{
+ if (rule->flags & IPRULE_IN && rule->in_iface_user.iface)
+ interface_remove_user(&rule->in_iface_user);
+
+ if (rule->flags & IPRULE_OUT && rule->out_iface_user.iface)
+ interface_remove_user(&rule->out_iface_user);
+}
+
+static void register_interfaces(struct iprule *rule)
+{
+ struct interface *iface, *tmp;
+
+ if (rule->flags & IPRULE_IN) {
+ tmp = vlist_find(&interfaces, rule->in_iface, iface, node);
+ if (tmp)
+ interface_add_user(&rule->in_iface_user, tmp);
+ }
+ if (rule->flags & IPRULE_OUT) {
+ tmp = vlist_find(&interfaces, rule->out_iface, iface, node);
+ if (tmp)
+ interface_add_user(&rule->out_iface_user, tmp);
+ }
+}
+
static void
iprule_update_rule(struct vlist_tree *tree,
struct vlist_node *node_new, struct vlist_node *node_old)
@@ -248,16 +364,34 @@ iprule_update_rule(struct vlist_tree *tree,
rule_new = container_of(node_new, struct iprule, node);
if (node_old) {
- system_del_iprule(rule_old);
+ if (rule_ready(rule_old))
+ system_del_iprule(rule_old);
+
+ if (rule_old->flags & (IPRULE_IN | IPRULE_OUT))
+ deregister_interfaces(rule_old);
+
+ if (rule_old->in_iface)
+ free(rule_old->in_iface);
+
+ if (rule_old->out_iface)
+ free(rule_old->out_iface);
+
free(rule_old);
}
- if (node_new)
- system_add_iprule(rule_new);
+ if (node_new) {
+ /* interface based rules calls system_add_iprule over the event cb */
+ if (rule_new->flags & (IPRULE_IN | IPRULE_OUT)) {
+ register_interfaces(rule_new);
+ } else {
+ system_add_iprule(rule_new);
+ }
+ }
}
static void __init
iprule_init_list(void)
{
vlist_init(&iprules, rule_cmp, iprule_update_rule);
+ interface_add_user(&generic_listener, NULL);
}