diff --git a/include/rule.h b/include/rule.h
index 2708cbebc9f8..ba40db8806fc 100644
--- a/include/rule.h
+++ b/include/rule.h
@@ -208,7 +208,9 @@ struct chain {
 	struct prio_spec	priority;
 	struct expr		*policy;
 	const char		*type;
-	const char		*dev;
+	const char		**dev_array;
+	struct expr		*dev_expr;
+	int			dev_array_len;
 	struct scope		scope;
 	struct list_head	rules;
 };
diff --git a/src/json.c b/src/json.c
index 13a064249d90..56b20549bd73 100644
--- a/src/json.c
+++ b/src/json.c
@@ -222,9 +222,9 @@ static json_t *rule_print_json(struct output_ctx *octx,
 
 static json_t *chain_print_json(const struct chain *chain)
 {
+	int priority, policy, n = 0;
+	struct expr *dev, *expr;
 	json_t *root, *tmp;
-	int priority;
-	int policy;
 
 	root = json_pack("{s:s, s:s, s:s, s:I}",
 			 "family", family2str(chain->handle.family),
@@ -243,8 +243,17 @@ static json_t *chain_print_json(const struct chain *chain)
 						    chain->hooknum),
 				"prio", priority,
 				"policy", chain_policy2str(policy));
-		if (chain->dev)
-			json_object_set_new(tmp, "dev", json_string(chain->dev));
+		if (chain->dev_expr) {
+			list_for_each_entry(expr, &chain->dev_expr->expressions, list) {
+				dev = expr;
+				n++;
+			}
+		}
+
+		if (n == 1) {
+			json_object_set_new(tmp, "dev",
+					    json_string(dev->identifier));
+		}
 		json_object_update(root, tmp);
 		json_decref(tmp);
 	}
diff --git a/src/mnl.c b/src/mnl.c
index 75ab07b045aa..ee5d0a1a2a15 100644
--- a/src/mnl.c
+++ b/src/mnl.c
@@ -526,10 +526,12 @@ err:
 int mnl_nft_chain_add(struct netlink_ctx *ctx, const struct cmd *cmd,
 		      unsigned int flags)
 {
+	int priority, policy, i = 0;
 	struct nftnl_chain *nlc;
+	const char **dev_array;
 	struct nlmsghdr *nlh;
-	int priority;
-	int policy;
+	struct expr *expr;
+	int dev_array_len;
 
 	nlc = nftnl_chain_alloc();
 	if (nlc == NULL)
@@ -555,9 +557,26 @@ int mnl_nft_chain_add(struct netlink_ctx *ctx, const struct cmd *cmd,
 					BYTEORDER_HOST_ENDIAN, sizeof(int));
 			nftnl_chain_set_u32(nlc, NFTNL_CHAIN_POLICY, policy);
 		}
-		if (cmd->chain->dev != NULL)
-			nftnl_chain_set_str(nlc, NFTNL_CHAIN_DEV,
-					    cmd->chain->dev);
+		if (cmd->chain->dev_expr) {
+			dev_array = xmalloc(sizeof(char *) * 8);
+			dev_array_len = 8;
+			list_for_each_entry(expr, &cmd->chain->dev_expr->expressions, list) {
+				dev_array[i++] = expr->identifier;
+				if (i == dev_array_len) {
+					dev_array_len *= 2;
+					dev_array = xrealloc(dev_array,
+							     dev_array_len);
+				}
+			}
+
+			dev_array[i] = NULL;
+			if (i == 1)
+				nftnl_chain_set_str(nlc, NFTNL_CHAIN_DEV, dev_array[0]);
+			else if (i > 1)
+				nftnl_chain_set(nlc, NFTNL_CHAIN_DEVICES, dev_array);
+
+			xfree(dev_array);
+		}
 	}
 	netlink_dump_chain(nlc, ctx);
 
diff --git a/src/netlink.c b/src/netlink.c
index 1e669e5dcaa1..c47771d3c801 100644
--- a/src/netlink.c
+++ b/src/netlink.c
@@ -378,9 +378,9 @@ void netlink_dump_chain(const struct nftnl_chain *nlc, struct netlink_ctx *ctx)
 struct chain *netlink_delinearize_chain(struct netlink_ctx *ctx,
 					const struct nftnl_chain *nlc)
 {
+	int priority, policy, len = 0, i;
+	const char * const *dev_array;
 	struct chain *chain;
-	int priority;
-	int policy;
 
 	chain = chain_alloc(nftnl_chain_get_str(nlc, NFTNL_CHAIN_NAME));
 	chain->handle.family =
@@ -415,8 +415,22 @@ struct chain *netlink_delinearize_chain(struct netlink_ctx *ctx,
 						    &policy);
 			nftnl_chain_get_u32(nlc, NFTNL_CHAIN_POLICY);
 		if (nftnl_chain_is_set(nlc, NFTNL_CHAIN_DEV)) {
-			chain->dev	=
+			chain->dev_array = xmalloc(sizeof(char *));
+			chain->dev_array_len = 1;
+			chain->dev_array[0] =
 				xstrdup(nftnl_chain_get_str(nlc, NFTNL_CHAIN_DEV));
+			chain->dev_array[1] = NULL;
+		} else if (nftnl_chain_is_set(nlc, NFTNL_CHAIN_DEVICES)) {
+			dev_array = nftnl_chain_get(nlc, NFTNL_CHAIN_DEVICES);
+			while (dev_array[len])
+				len++;
+
+			chain->dev_array = xmalloc(len * sizeof(char *));
+			for (i = 0; i < len; i++)
+				chain->dev_array[i] = xstrdup(dev_array[i]);
+
+			chain->dev_array[i] = NULL;
+			chain->dev_array_len = len;
 		}
 		chain->flags        |= CHAIN_F_BASECHAIN;
 	}
diff --git a/src/parser_bison.y b/src/parser_bison.y
index 11f0dc8b2153..7f9b1752f41d 100644
--- a/src/parser_bison.y
+++ b/src/parser_bison.y
@@ -564,11 +564,11 @@ int nft_lex(void *, void *, void *);
 %type <val>			family_spec family_spec_explicit
 %type <val32>			int_num	chain_policy
 %type <prio_spec>		extended_prio_spec prio_spec
-%type <string>			extended_prio_name
-%destructor { xfree($$); }	extended_prio_name
+%type <string>			extended_prio_name quota_unit
+%destructor { xfree($$); }	extended_prio_name quota_unit
 
-%type <string>			dev_spec quota_unit
-%destructor { xfree($$); }	dev_spec quota_unit
+%type <expr>			dev_spec
+%destructor { xfree($$); }	dev_spec
 
 %type <table>			table_block_alloc table_block
 %destructor { close_scope(state); table_free($$); }	table_block_alloc
@@ -1992,7 +1992,7 @@ hook_spec		:	TYPE		STRING		HOOK		STRING		dev_spec	prio_spec
 				}
 				xfree($4);
 
-				$<chain>0->dev		= $5;
+				$<chain>0->dev_expr	= $5;
 				$<chain>0->priority	= $6;
 				$<chain>0->flags	|= CHAIN_F_BASECHAIN;
 			}
@@ -2072,7 +2072,21 @@ int_num			:	NUM			{ $$ = $1; }
 			|	DASH	NUM		{ $$ = -$2; }
 			;
 
-dev_spec		:	DEVICE	string		{ $$ = $2; }
+dev_spec		:	DEVICE	string
+			{
+				struct expr *expr;
+
+				expr = constant_expr_alloc(&@$, &string_type,
+							   BYTEORDER_HOST_ENDIAN,
+							   strlen($2) * BITS_PER_BYTE, $2);
+				$$ = compound_expr_alloc(&@$, EXPR_LIST);
+				compound_expr_add($$, expr);
+
+			}
+			|	DEVICES		'='	flowtable_expr
+			{
+				$$ = $3;
+			}
 			|	/* empty */		{ $$ = NULL; }
 			;
 
diff --git a/src/parser_json.c b/src/parser_json.c
index 55dbc177cc98..0c79189ec526 100644
--- a/src/parser_json.c
+++ b/src/parser_json.c
@@ -17,6 +17,7 @@
 #include <netinet/icmp6.h>
 #include <netinet/ip.h>
 #include <netinet/ip_icmp.h>
+#include <net/if.h>
 #include <linux/xfrm.h>
 
 #include <linux/netfilter.h>
@@ -2581,8 +2582,9 @@ static struct cmd *json_parse_cmd_add_chain(struct json_ctx *ctx, json_t *root,
 		.table.location = *int_loc,
 	};
 	const char *family = "", *policy = "", *type, *hookstr;
-	int prio;
+	const char name[IFNAMSIZ];
 	struct chain *chain;
+	int prio;
 
 	if (json_unpack_err(ctx, root, "{s:s, s:s}",
 			    "family", &family,
@@ -2626,8 +2628,18 @@ static struct cmd *json_parse_cmd_add_chain(struct json_ctx *ctx, json_t *root,
 		return NULL;
 	}
 
-	if (!json_unpack(root, "{s:s}", "dev", &chain->dev))
-		chain->dev = xstrdup(chain->dev);
+	if (!json_unpack(root, "{s:s}", "dev", &name)) {
+		struct expr *dev_expr, *expr;
+
+		dev_expr = compound_expr_alloc(int_loc, EXPR_LIST);
+		expr = constant_expr_alloc(int_loc, &integer_type,
+					   BYTEORDER_HOST_ENDIAN,
+					   strlen(name) * BITS_PER_BYTE,
+					   name);
+		compound_expr_add(dev_expr, expr);
+		chain->dev_expr = dev_expr;
+	}
+
 	if (!json_unpack(root, "{s:s}", "policy", &policy)) {
 		chain->policy = parse_policy(policy);
 		if (chain->policy < 0) {
diff --git a/src/rule.c b/src/rule.c
index 55894cbdb766..ec7bcb97425e 100644
--- a/src/rule.c
+++ b/src/rule.c
@@ -813,6 +813,7 @@ struct chain *chain_get(struct chain *chain)
 void chain_free(struct chain *chain)
 {
 	struct rule *rule, *next;
+	int i;
 
 	if (--chain->refcnt > 0)
 		return;
@@ -821,8 +822,10 @@ void chain_free(struct chain *chain)
 	handle_free(&chain->handle);
 	scope_release(&chain->scope);
 	xfree(chain->type);
-	if (chain->dev != NULL)
-		xfree(chain->dev);
+	expr_free(chain->dev_expr);
+	for (i = 0; i < chain->dev_array_len; i++)
+		xfree(chain->dev_array[i]);
+	xfree(chain->dev_array);
 	expr_free(chain->priority.expr);
 	expr_free(chain->policy);
 	xfree(chain);
@@ -1101,7 +1104,7 @@ static void chain_print_declaration(const struct chain *chain,
 				    struct output_ctx *octx)
 {
 	char priobuf[STD_PRIO_BUFSIZE];
-	int policy;
+	int policy, i;
 
 	nft_print(octx, "\tchain %s {", chain->handle.chain.name);
 	if (nft_output_handle(octx))
@@ -1110,8 +1113,17 @@ static void chain_print_declaration(const struct chain *chain,
 	if (chain->flags & CHAIN_F_BASECHAIN) {
 		nft_print(octx, "\t\ttype %s hook %s", chain->type,
 			  hooknum2str(chain->handle.family, chain->hooknum));
-		if (chain->dev != NULL)
-			nft_print(octx, " device \"%s\"", chain->dev);
+		if (chain->dev_array_len == 1) {
+			nft_print(octx, " device \"%s\"", chain->dev_array[0]);
+		} else if (chain->dev_array_len > 1) {
+			nft_print(octx, " devices = { ");
+			for (i = 0; i < chain->dev_array_len; i++) {
+				nft_print(octx, "%s", chain->dev_array[i]);
+					if (i + 1 != chain->dev_array_len)
+						nft_print(octx, ", ");
+			}
+			nft_print(octx, " }");
+		}
 		nft_print(octx, " priority %s;",
 			  prio2str(octx, priobuf, sizeof(priobuf),
 				   chain->handle.family, chain->hooknum,
