diff --git a/doc/nft.xml b/doc/nft.xml
index 8d79016..d51876c 100644
--- a/doc/nft.xml
+++ b/doc/nft.xml
@@ -267,6 +267,14 @@ filter input iif $int_ifs accept
 						</para>
 					</listitem>
 				</varlistentry>
+				<varlistentry>
+					<term><option>netdev</option></term>
+					<listitem>
+						<para>
+							Netdev address family, handling packets from ingress.
+						</para>
+					</listitem>
+				</varlistentry>
 			</variablelist>
 		</para>
 		<para>
@@ -373,6 +381,38 @@ filter input iif $int_ifs accept
 				The bridge address family handles ethernet packets traversing bridge devices.
 			</para>
 		</refsect2>
+		<refsect2>
+			<title>Netdev address family</title>
+			<para>
+				The Netdev address family handles packets from ingress.
+			</para>
+			<para>
+				<table frame="all">
+					<title>Netdev address family hooks</title>
+					<tgroup cols='2' align='left' colsep='1' rowsep='1' pgwide="1">
+						<colspec colname='c1' colwidth="1*"/>
+						<colspec colname='c2' colwidth="5*"/>
+						<thead>
+							<row>
+								<entry>Hook</entry>
+								<entry>Description</entry>
+							</row>
+						</thead>
+						<tbody>
+							<row>
+								<entry>ingress</entry>
+								<entry>
+									All packets entering the system are processed by this hook. It is invoked
+									before layer 3 protocol handlers and it can be used for early filtering and
+									policing.
+								</entry>
+							</row>
+						</tbody>
+					</tgroup>
+				</table>
+			</para>
+		</refsect2>
+
 	</refsect1>
 
 	<refsect1>
@@ -401,6 +441,7 @@ filter input iif $int_ifs accept
 				<member><literal>inet</literal></member>
 				<member><literal>arp</literal></member>
 				<member><literal>bridge</literal></member>
+				<member><literal>netdev</literal></member>
 			</simplelist>.
 
 			The <literal>inet</literal> address family is a dummy family which is used to create
@@ -457,6 +498,7 @@ filter input iif $int_ifs accept
 				<arg choice="req"><replaceable>hook</replaceable></arg>
 				<arg choice="req"><replaceable>priority</replaceable></arg>
 				<arg choice="req"><replaceable>policy</replaceable></arg>
+				<arg choice="req"><replaceable>device</replaceable></arg>
 			</cmdsynopsis>
 			<cmdsynopsis>
 				<group choice="req">
diff --git a/include/linux/netfilter.h b/include/linux/netfilter.h
index be0bc18..18075f9 100644
--- a/include/linux/netfilter.h
+++ b/include/linux/netfilter.h
@@ -32,6 +32,7 @@
 #define NF_DROP_ERR(x) (((-x) << 16) | NF_DROP)
 
 /* only for userspace compatibility */
+#ifndef __KERNEL__
 /* Generic cache responses from hook functions.
    <= 0x2000 is used for protocol-flags. */
 #define NFC_UNKNOWN 0x4000
@@ -39,6 +40,7 @@
 
 /* NF_VERDICT_BITS should be 8 now, but userspace might break if this changes */
 #define NF_VERDICT_BITS 16
+#endif
 
 enum nf_inet_hooks {
 	NF_INET_PRE_ROUTING,
@@ -49,11 +51,17 @@ enum nf_inet_hooks {
 	NF_INET_NUMHOOKS
 };
 
+enum nf_dev_hooks {
+	NF_NETDEV_INGRESS,
+	NF_NETDEV_NUMHOOKS
+};
+
 enum {
 	NFPROTO_UNSPEC =  0,
 	NFPROTO_INET   =  1,
 	NFPROTO_IPV4   =  2,
 	NFPROTO_ARP    =  3,
+	NFPROTO_NETDEV =  5,
 	NFPROTO_BRIDGE =  7,
 	NFPROTO_IPV6   = 10,
 	NFPROTO_DECNET = 12,
diff --git a/include/rule.h b/include/rule.h
index 5d44599..604de14 100644
--- a/include/rule.h
+++ b/include/rule.h
@@ -113,6 +113,7 @@ enum chain_flags {
  * @priority:	hook priority (base chains)
  * @policy:	default chain policy (base chains)
  * @type:	chain type
+ * @dev:	device (if any)
  * @rules:	rules contained in the chain
  */
 struct chain {
@@ -125,6 +126,7 @@ struct chain {
 	int			priority;
 	int			policy;
 	const char		*type;
+	const char		*dev;
 	struct scope		scope;
 	struct list_head	rules;
 };
diff --git a/src/evaluate.c b/src/evaluate.c
index e260a80..dbecfc1 100644
--- a/src/evaluate.c
+++ b/src/evaluate.c
@@ -1840,6 +1840,10 @@ static uint32_t str2hooknum(uint32_t family, const char *hook)
 		else if (!strcmp(hook, "output"))
 			return NF_ARP_OUT;
 		break;
+	case NFPROTO_NETDEV:
+		if (!strcmp(hook, "ingress"))
+			return NF_NETDEV_INGRESS;
+		break;
 	default:
 		break;
 	}
diff --git a/src/netlink.c b/src/netlink.c
index d31387f..0375a68 100644
--- a/src/netlink.c
+++ b/src/netlink.c
@@ -557,6 +557,9 @@ static int netlink_add_chain_batch(struct netlink_ctx *ctx,
 		if (chain->policy != -1)
 			nft_chain_attr_set_u32(nlc, NFT_CHAIN_ATTR_POLICY,
 					       chain->policy);
+		if (chain->dev != NULL)
+			nft_chain_attr_set_str(nlc, NFT_CHAIN_ATTR_DEV,
+					       chain->dev);
 	}
 
 	netlink_dump_chain(nlc);
@@ -699,6 +702,10 @@ static struct chain *netlink_delinearize_chain(struct netlink_ctx *ctx,
 			xstrdup(nft_chain_attr_get_str(nlc, NFT_CHAIN_ATTR_TYPE));
 		chain->policy          =
 			nft_chain_attr_get_u32(nlc, NFT_CHAIN_ATTR_POLICY);
+		if (nft_chain_attr_is_set(nlc, NFT_CHAIN_ATTR_DEV)) {
+			chain->dev	=
+				xstrdup(nft_chain_attr_get_str(nlc, NFT_CHAIN_ATTR_DEV));
+		}
 		chain->flags        |= CHAIN_F_BASECHAIN;
 	}
 
diff --git a/src/parser_bison.y b/src/parser_bison.y
index eac3fcb..89874e9 100644
--- a/src/parser_bison.y
+++ b/src/parser_bison.y
@@ -165,6 +165,7 @@ static void location_update(struct location *loc, struct location *rhs, int n)
 %token DEFINE			"define"
 
 %token HOOK			"hook"
+%token DEVICE			"device"
 %token TABLE			"table"
 %token TABLES			"tables"
 %token CHAIN			"chain"
@@ -179,6 +180,7 @@ static void location_update(struct location *loc, struct location *rhs, int n)
 %token RULESET			"ruleset"
 
 %token INET			"inet"
+%token NETDEV			"netdev"
 
 %token ADD			"add"
 %token UPDATE			"update"
@@ -924,6 +926,7 @@ chain_block		:	/* empty */	{ $$ = $<chain>-1; }
 	     		|	chain_block	stmt_seperator
 			|	chain_block	hook_spec	stmt_seperator
 			|	chain_block	policy_spec	stmt_seperator
+			|	chain_block	device_spec	stmt_seperator
 			|	chain_block	rule		stmt_seperator
 			{
 				list_add_tail(&$2->list, &$1->rules);
@@ -1103,6 +1106,17 @@ policy_spec		:	POLICY		chain_policy
 			}
 			;
 
+device_spec		:	DEVICE		string
+			{
+				if ($<chain>0->dev != NULL) {
+					erec_queue(error(&@$, "you cannot set device twice"),
+						   state->msgs);
+					YYERROR;
+				}
+				$<chain>0->dev		= $2;
+			}
+			;
+
 chain_policy		:	ACCEPT		{ $$ = NF_ACCEPT; }
 			|	DROP		{ $$ = NF_DROP;   }
 			;
@@ -1137,6 +1151,7 @@ family_spec_explicit	:	IP		{ $$ = NFPROTO_IPV4; }
 			|	INET		{ $$ = NFPROTO_INET; }
 			|	ARP		{ $$ = NFPROTO_ARP; }
 			|	BRIDGE		{ $$ = NFPROTO_BRIDGE; }
+			|	NETDEV		{ $$ = NFPROTO_NETDEV; }
 			;
 
 table_spec		:	family_spec	identifier
diff --git a/src/payload.c b/src/payload.c
index 08578fd..1a9d491 100644
--- a/src/payload.c
+++ b/src/payload.c
@@ -215,6 +215,7 @@ int payload_gen_dependency(struct eval_ctx *ctx, const struct expr *expr,
 			}
 			break;
 		case NFPROTO_BRIDGE:
+		case NFPROTO_NETDEV:
 			switch (expr->payload.base) {
 			case PROTO_BASE_LL_HDR:
 				desc = &proto_eth;
diff --git a/src/proto.c b/src/proto.c
index 7dc7b3e..dc671bd 100644
--- a/src/proto.c
+++ b/src/proto.c
@@ -123,6 +123,7 @@ const struct proto_desc *proto_dev_desc(uint16_t type)
 
 const struct hook_proto_desc hook_proto_desc[] = {
 	[NFPROTO_BRIDGE]	= HOOK_PROTO_DESC(PROTO_BASE_LL_HDR,	  &proto_eth),
+	[NFPROTO_NETDEV]	= HOOK_PROTO_DESC(PROTO_BASE_LL_HDR,	  &proto_eth),
 	[NFPROTO_INET]		= HOOK_PROTO_DESC(PROTO_BASE_LL_HDR,	  &proto_inet),
 	[NFPROTO_IPV4]		= HOOK_PROTO_DESC(PROTO_BASE_NETWORK_HDR, &proto_ip),
 	[NFPROTO_IPV6]		= HOOK_PROTO_DESC(PROTO_BASE_NETWORK_HDR, &proto_ip6),
diff --git a/src/rule.c b/src/rule.c
index b2090dd..c670fde 100644
--- a/src/rule.c
+++ b/src/rule.c
@@ -333,6 +333,7 @@ static const char *chain_hookname_str_array[] = {
 	"forward",
 	"postrouting",
 	"output",
+	"ingress",
 	NULL,
 };
 
@@ -398,6 +399,8 @@ const char *family2str(unsigned int family)
 			return "ip6";
 		case NFPROTO_INET:
 			return "inet";
+		case NFPROTO_NETDEV:
+			return "netdev";
 		case NFPROTO_ARP:
 			return "arp";
 		case NFPROTO_BRIDGE:
@@ -441,6 +444,13 @@ static const char *hooknum2str(unsigned int family, unsigned int hooknum)
 		default:
 			break;
 		}
+		break;
+	case NFPROTO_NETDEV:
+		switch (hooknum) {
+		case NF_NETDEV_INGRESS:
+			return "ingress";
+		}
+		break;
 	default:
 		break;
 	};
@@ -465,10 +475,14 @@ static void chain_print(const struct chain *chain)
 
 	printf("\tchain %s {\n", chain->handle.chain);
 	if (chain->flags & CHAIN_F_BASECHAIN) {
-		printf("\t\ttype %s hook %s priority %d; policy %s;\n",
+		printf("\t\ttype %s hook %s priority %d;",
 		       chain->type,
 		       hooknum2str(chain->handle.family, chain->hooknum),
-		       chain->priority, chain_policy2str(chain->policy));
+		       chain->priority);
+		if (chain->dev != NULL)
+			printf(" device %s;", chain->dev);
+
+		printf(" policy %s;\n", chain_policy2str(chain->policy));
 	}
 	list_for_each_entry(rule, &chain->rules, list) {
 		printf("\t\t");
diff --git a/src/scanner.l b/src/scanner.l
index 985ea2a..2d9871d 100644
--- a/src/scanner.l
+++ b/src/scanner.l
@@ -233,6 +233,7 @@ addrstring	({macaddr}|{ip4addr}|{ip6addr})
 "describe"		{ return DESCRIBE; }
 
 "hook"			{ return HOOK; }
+"device"		{ return DEVICE; }
 "table"			{ return TABLE; }
 "tables"		{ return TABLES; }
 "chain"			{ return CHAIN; }
@@ -255,6 +256,7 @@ addrstring	({macaddr}|{ip4addr}|{ip6addr})
 "to"			{ return TO; }
 
 "inet"			{ return INET; }
+"netdev"		{ return NETDEV; }
 
 "add"			{ return ADD; }
 "update"		{ return UPDATE; }
