diff --git a/iprule.c b/iprule.c
index 7cf7422f4168..105f469b2b35 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);
 }
diff --git a/iprule.h b/iprule.h
index b723bdb05d7d..f05c3c93b4a1 100644
--- a/iprule.h
+++ b/iprule.h
@@ -74,6 +74,15 @@ struct iprule {
 
 	bool invert;
 
+	/* uci interface name */
+	char *in_iface;
+	char *out_iface;
+
+	/* to receive interface events */
+	struct interface_user in_iface_user;
+	struct interface_user out_iface_user;
+
+	/* device name */
 	char in_dev[IFNAMSIZ + 1];
 	char out_dev[IFNAMSIZ + 1];
 
