[nf-next,V2] netfilter: ctnetlink: add kernel side filtering for dump
diff mbox series

Message ID 20200129094642.610-1-romain.bellan@wifirst.fr
State Changes Requested
Delegated to: Pablo Neira
Headers show
Series
  • [nf-next,V2] netfilter: ctnetlink: add kernel side filtering for dump
Related show

Commit Message

Romain Bellan Jan. 29, 2020, 9:46 a.m. UTC
Conntrack dump does not support kernel side filtering (only get exists,
but it returns only one entry. And user has to give a full valid tuple)

It means that userspace has to implement filtering after receiving many
irrelevant entries, consuming ressources (conntrack table is sometimes
very huge, much more than a routing table for example).

This patch adds filtering in kernel side. To achieve this goal, we:

 * Add a new CTA_FILTER netlink attributes, actually a flag list to
   parametize filtering
 * Convert some *nlattr_to_tuple() functions, to allow a partial parsing
   of CTA_TUPLE_ORIG and CTA_TUPLE_REPLY (so nf_conntrack_tuple it not
   fully set)

Filtering is now possible on:
 * IP SRC/DST values
 * Ports for TCP and UDP flows
 * IMCP(v6) codes types and IDs

Filtering is done has an "AND" operator. For example, when flags
PROTO_SRC_PORT, PROTO_NUM and IP_SRC are sets, only entries matching all
values are dumped.

Changes since v1:
  set NLM_F_DUMP_FILTERED in nlm flags if entries are filtered

Signed-off-by: Romain Bellan <romain.bellan@wifirst.fr>
Signed-off-by: Florent Fourcot <florent.fourcot@wifirst.fr>
---
 include/net/netfilter/nf_conntrack_l4proto.h  |   6 +-
 .../linux/netfilter/nfnetlink_conntrack.h     |  41 +++
 net/netfilter/nf_conntrack_core.c             |  19 +-
 net/netfilter/nf_conntrack_netlink.c          | 305 +++++++++++++++---
 net/netfilter/nf_conntrack_proto_icmp.c       |  41 ++-
 net/netfilter/nf_conntrack_proto_icmpv6.c     |  43 ++-
 6 files changed, 382 insertions(+), 73 deletions(-)

Comments

Florent Fourcot March 9, 2020, 10:26 a.m. UTC | #1
Hello Pablo,

What do you think of this version, with related libnftnl patches? Can we 
improve it?

Best regards,
Pablo Neira Ayuso March 9, 2020, 12:49 p.m. UTC | #2
Hi Romain, Florent,

Here comes my feedback. Thanks for your patience.

On Wed, Jan 29, 2020 at 10:46:42AM +0100, Romain Bellan wrote:
> Conntrack dump does not support kernel side filtering (only get exists,
> but it returns only one entry. And user has to give a full valid tuple)
> 
> It means that userspace has to implement filtering after receiving many
> irrelevant entries, consuming ressources (conntrack table is sometimes

typo: resources

> very huge, much more than a routing table for example).
> 
> This patch adds filtering in kernel side. To achieve this goal, we:
> 
>  * Add a new CTA_FILTER netlink attributes, actually a flag list to
>    parametize filtering
>  * Convert some *nlattr_to_tuple() functions, to allow a partial parsing
>    of CTA_TUPLE_ORIG and CTA_TUPLE_REPLY (so nf_conntrack_tuple it not
>    fully set)
> 
> Filtering is now possible on:
>  * IP SRC/DST values
>  * Ports for TCP and UDP flows
>  * IMCP(v6) codes types and IDs
> 
> Filtering is done has an "AND" operator. For example, when flags
> PROTO_SRC_PORT, PROTO_NUM and IP_SRC are sets, only entries matching all
> values are dumped.
> 
> Changes since v1:
>   set NLM_F_DUMP_FILTERED in nlm flags if entries are filtered
> 
> Signed-off-by: Romain Bellan <romain.bellan@wifirst.fr>
> Signed-off-by: Florent Fourcot <florent.fourcot@wifirst.fr>
> ---
>  include/net/netfilter/nf_conntrack_l4proto.h  |   6 +-
>  .../linux/netfilter/nfnetlink_conntrack.h     |  41 +++
>  net/netfilter/nf_conntrack_core.c             |  19 +-
>  net/netfilter/nf_conntrack_netlink.c          | 305 +++++++++++++++---
>  net/netfilter/nf_conntrack_proto_icmp.c       |  41 ++-
>  net/netfilter/nf_conntrack_proto_icmpv6.c     |  43 ++-
>  6 files changed, 382 insertions(+), 73 deletions(-)
> 
> diff --git a/include/net/netfilter/nf_conntrack_l4proto.h b/include/net/netfilter/nf_conntrack_l4proto.h
> index 4cad1f0a327a..88186b95b3c2 100644
> --- a/include/net/netfilter/nf_conntrack_l4proto.h
> +++ b/include/net/netfilter/nf_conntrack_l4proto.h
> @@ -42,7 +42,8 @@ struct nf_conntrack_l4proto {
>  	/* Calculate tuple nlattr size */
>  	unsigned int (*nlattr_tuple_size)(void);
>  	int (*nlattr_to_tuple)(struct nlattr *tb[],
> -			       struct nf_conntrack_tuple *t);
> +			       struct nf_conntrack_tuple *t,
> +			       u_int32_t flags);
>  	const struct nla_policy *nla_policy;
>  
>  	struct {
> @@ -152,7 +153,8 @@ const struct nf_conntrack_l4proto *nf_ct_l4proto_find(u8 l4proto);
>  int nf_ct_port_tuple_to_nlattr(struct sk_buff *skb,
>  			       const struct nf_conntrack_tuple *tuple);
>  int nf_ct_port_nlattr_to_tuple(struct nlattr *tb[],
> -			       struct nf_conntrack_tuple *t);
> +			       struct nf_conntrack_tuple *t,
> +			       u_int32_t flags);
>  unsigned int nf_ct_port_nlattr_tuple_size(void);
>  extern const struct nla_policy nf_ct_port_nla_policy[];
>  
> diff --git a/include/uapi/linux/netfilter/nfnetlink_conntrack.h b/include/uapi/linux/netfilter/nfnetlink_conntrack.h
> index 1d41810d17e2..8c67b9da2592 100644
> --- a/include/uapi/linux/netfilter/nfnetlink_conntrack.h
> +++ b/include/uapi/linux/netfilter/nfnetlink_conntrack.h
> @@ -55,6 +55,7 @@ enum ctattr_type {
>  	CTA_LABELS,
>  	CTA_LABELS_MASK,
>  	CTA_SYNPROXY,
> +	CTA_FILTER,
>  	__CTA_MAX
>  };
>  #define CTA_MAX (__CTA_MAX - 1)
> @@ -276,4 +277,44 @@ enum ctattr_expect_stats {
>  };
>  #define CTA_STATS_EXP_MAX (__CTA_STATS_EXP_MAX - 1)
>  
> +enum ctattr_filter {
> +	CTA_FILTER_UNSPEC,
> +	CTA_FILTER_ORIG_FLAGS,
> +	CTA_FILTER_REPLY_FLAGS,
> +	__CTA_FILTER_MAX
> +};
> +#define CTA_FILTER_MAX (__CTA_FILTER_MAX - 1)
> +
> +#define CTA_FILTER_FLAG(x) CTA_FILTER_FLAG_ ## x

Probably ctattr instead of x?

#define CTA_FILTER_FLAG(ctattr) CTA_FILTER_FLAG_##ctattr

that implicitly refers to the enum ctattr.

> +
> +/* applied on tuple filters */
> +#define CTA_FILTER_FLAG_CTA_IP_SRC		(1 << 0)
> +#define CTA_FILTER_FLAG_CTA_IP_DST		(1 << 1)
> +#define CTA_FILTER_FLAG_CTA_TUPLE_ZONE		(1 << 2)
> +#define CTA_FILTER_FLAG_CTA_PROTO_NUM		(1 << 3)
> +#define CTA_FILTER_FLAG_CTA_PROTO_SRC_PORT	(1 << 4)
> +#define CTA_FILTER_FLAG_CTA_PROTO_DST_PORT	(1 << 5)
> +#define CTA_FILTER_FLAG_CTA_PROTO_ICMP_TYPE	(1 << 6)
> +#define CTA_FILTER_FLAG_CTA_PROTO_ICMP_CODE	(1 << 7)
> +#define CTA_FILTER_FLAG_CTA_PROTO_ICMP_ID	(1 << 8)
> +#define CTA_FILTER_FLAG_CTA_PROTO_ICMPV6_TYPE	(1 << 9)
> +#define CTA_FILTER_FLAG_CTA_PROTO_ICMPV6_CODE	(1 << 10)
> +#define CTA_FILTER_FLAG_CTA_PROTO_ICMPV6_ID	(1 << 11)

Probably

        CTA_FILTER_F_CTA_IP_SRC

? Just use _F_ instead.

I like long names too, but probably this one can be shrinked a bit :-)

You might need this too

#define CTA_FILTER_F_MAX        (1 << 12)

> +#define CTA_FILTER_FLAG_ALL_CTA_PROTO \
> +  (CTA_FILTER_FLAG_CTA_PROTO_SRC_PORT | \
> +   CTA_FILTER_FLAG_CTA_PROTO_DST_PORT | \
> +   CTA_FILTER_FLAG_CTA_PROTO_ICMP_TYPE | \
> +   CTA_FILTER_FLAG_CTA_PROTO_ICMP_CODE | \
> +   CTA_FILTER_FLAG_CTA_PROTO_ICMP_ID | \
> +   CTA_FILTER_FLAG_CTA_PROTO_ICMPV6_TYPE | \
> +   CTA_FILTER_FLAG_CTA_PROTO_ICMPV6_CODE | \
> +   CTA_FILTER_FLAG_CTA_PROTO_ICMPV6_ID)

Please, define CTA_FILTER_FLAG_ALL_CTA_PROTO in
net/netfilter/nf_conntrack_netlink.c, no need to expose this through
UAPI. I can see you use this to check for missing protocol number in
the code.

Another suggestion: rename _ALL_CTA_PROTO to _CTA_PROTO.
Comestic update when defining this, I'd suggest:

#define CTA_FILTER_F_CTA_PROTO                  \
        (CTA_FILTER_F_CTA_PROTO_SRC_PORT |      \
         CTA_FILTER_F_CTA_PROTO_DST_PORT |      \
         CTA_FILTER_F_CTA_PROTO_ICMP_TYPE |     \
         CTA_FILTER_F_CTA_PROTO_ICMP_CODE |     \
         CTA_FILTER_F_CTA_PROTO_ICMP_ID |       \
         CTA_FILTER_F_CTA_PROTO_ICMPV6_TYPE |   \
         CTA_FILTER_F_CTA_PROTO_ICMPV6_CODE |   \
         CTA_FILTER_F_CTA_PROTO_ICMPV6_ID)

> +#define CTA_FILTER_FLAG_ALL			0xFFFFFFFF
> +
> +/* applied on filters */
> +#define CTA_FILTER_FLAG_CTA_MARK		(1 << 0)
> +#define CTA_FILTER_FLAG_CTA_MARK_MASK		(1 << 1)

This one is internal, correct? Better not to expose them through UAPI.
header file. User must use CTA_MARK for backward compatibility,
otherwise user might get confused when looking at this in the UAPI
file.

Better define these CTA_MARK* in net/netfilter/nf_conntrack_netlink.c
so it's clear that this is internal.

> +
> +
>  #endif /* _IPCONNTRACK_NETLINK_H */
> diff --git a/net/netfilter/nf_conntrack_core.c b/net/netfilter/nf_conntrack_core.c
> index 0af1898af2b8..790f0c1a46b8 100644
> --- a/net/netfilter/nf_conntrack_core.c
> +++ b/net/netfilter/nf_conntrack_core.c
> @@ -1835,13 +1835,22 @@ const struct nla_policy nf_ct_port_nla_policy[CTA_PROTO_MAX+1] = {
>  EXPORT_SYMBOL_GPL(nf_ct_port_nla_policy);
>  
>  int nf_ct_port_nlattr_to_tuple(struct nlattr *tb[],
> -			       struct nf_conntrack_tuple *t)
> +			       struct nf_conntrack_tuple *t,
> +			       u_int32_t flags)
>  {
> -	if (!tb[CTA_PROTO_SRC_PORT] || !tb[CTA_PROTO_DST_PORT])
> -		return -EINVAL;
> +	if (flags & CTA_FILTER_FLAG(CTA_PROTO_SRC_PORT)) {
> +		if (!tb[CTA_PROTO_SRC_PORT])
> +			return -EINVAL;
> +
> +		t->src.u.tcp.port = nla_get_be16(tb[CTA_PROTO_SRC_PORT]);
> +	}
>  
> -	t->src.u.tcp.port = nla_get_be16(tb[CTA_PROTO_SRC_PORT]);
> -	t->dst.u.tcp.port = nla_get_be16(tb[CTA_PROTO_DST_PORT]);
> +	if (flags & CTA_FILTER_FLAG(CTA_PROTO_DST_PORT)) {
> +		if (!tb[CTA_PROTO_DST_PORT])
> +			return -EINVAL;
> +
> +		t->dst.u.tcp.port = nla_get_be16(tb[CTA_PROTO_DST_PORT]);
> +	}
>  
>  	return 0;
>  }
> diff --git a/net/netfilter/nf_conntrack_netlink.c b/net/netfilter/nf_conntrack_netlink.c
> index d8d33ef52ce0..824f919e5ffe 100644
> --- a/net/netfilter/nf_conntrack_netlink.c
> +++ b/net/netfilter/nf_conntrack_netlink.c
> @@ -544,14 +544,16 @@ static int ctnetlink_dump_info(struct sk_buff *skb, struct nf_conn *ct)
>  
>  static int
>  ctnetlink_fill_info(struct sk_buff *skb, u32 portid, u32 seq, u32 type,
> -		    struct nf_conn *ct, bool extinfo)
> +		    struct nf_conn *ct, bool extinfo, unsigned int flags)
>  {
>  	const struct nf_conntrack_zone *zone;
>  	struct nlmsghdr *nlh;
>  	struct nfgenmsg *nfmsg;
>  	struct nlattr *nest_parms;
> -	unsigned int flags = portid ? NLM_F_MULTI : 0, event;
> +	unsigned int event;
>  
> +	if (portid)
> +		flags |= NLM_F_MULTI;
>  	event = nfnl_msg_type(NFNL_SUBSYS_CTNETLINK, IPCTNL_MSG_CT_NEW);
>  	nlh = nlmsg_put(skb, portid, seq, event, sizeof(*nfmsg), flags);
>  	if (nlh == NULL)
> @@ -847,20 +849,62 @@ static int ctnetlink_done(struct netlink_callback *cb)
>  }
>  
>  struct ctnetlink_filter {
> +	u_int32_t cta_flags;
>  	u8 family;
> +
> +	u_int32_t orig_flags;
> +	struct nf_conntrack_tuple orig;
> +
> +	u_int32_t reply_flags;
> +	struct nf_conntrack_tuple reply;
> +
> +	struct nf_conntrack_zone zone;
> +
>  	struct {
>  		u_int32_t val;
>  		u_int32_t mask;
>  	} mark;
>  };

Probably reorder fields in this structure?

struct ctnetlink_filter {
        u_int32_t                       orig_flags;
        u_int32_t                       reply_flags;

        struct nf_conntrack_tuple       orig;
        struct nf_conntrack_tuple       reply;
        struct nf_conntrack_zone        zone;

        struct {
                u_int32_t               val;
                u_int32_t               mask;
        } mark;
};

Maybe you squash the internal mark and mark_mask into orig_flags in
your internal definition. If there is a CTA_FILTER_F_MAX in place,
then you can define the internal mark/mark_mask flags from there.

> +static const struct nla_policy cta_filter_nla_policy[CTA_FILTER_MAX + 1] = {
> +	[CTA_FILTER_ORIG_FLAGS]		= { .type = NLA_U32 },
> +	[CTA_FILTER_REPLY_FLAGS]	= { .type = NLA_U32 },
> +};
> +
> +static int ctnetlink_parse_filter(const struct nlattr *attr,
> +				  struct ctnetlink_filter *filter)
> +{
> +	struct nlattr *tb[CTA_FILTER_MAX+1];
> +	int ret = 0;
> +
> +	ret = nla_parse_nested(tb, CTA_FILTER_MAX, attr, cta_filter_nla_policy, NULL);
> +	if (ret)
> +		return ret;
> +
> +	if (tb[CTA_FILTER_ORIG_FLAGS])
> +		filter->orig_flags = nla_get_u32(tb[CTA_FILTER_ORIG_FLAGS]);
> +
> +	if (tb[CTA_FILTER_REPLY_FLAGS])
> +		filter->reply_flags = nla_get_u32(tb[CTA_FILTER_REPLY_FLAGS]);

Please, check for unsupported flag and bail out:

        if (filter->orig_flags & ~(CTA_FILTER_F_MAX - 1))
                return -EOPNOTSUPP;

In case this needs to be extended later, new filtering attributes will
not be ignored by userspace.

> +
> +	return 0;
> +}
> +
> +static int ctnetlink_parse_zone(const struct nlattr *attr, struct nf_conntrack_zone *zone);
> +static int ctnetlink_parse_partial_tuple(const struct nlattr * const cda[],

Probably rename this to:

        ctnetlink_parse_tuple_filter()

?

> +					 struct nf_conntrack_tuple *tuple,
> +					 u32 type, u_int8_t l3num, struct nf_conntrack_zone *zone,

Break lines at 80 char columns, ie.

					 u32 type, u_int8_t l3num,
                                         struct nf_conntrack_zone *zone,

> +					 u_int32_t flags);
> +
>  static struct ctnetlink_filter *
>  ctnetlink_alloc_filter(const struct nlattr * const cda[], u8 family)
>  {
>  	struct ctnetlink_filter *filter;
> +	int err;
>  
>  #ifndef CONFIG_NF_CONNTRACK_MARK
> -	if (cda[CTA_MARK] && cda[CTA_MARK_MASK])
> +	if (cda[CTA_MARK] || cda[CTA_MARK_MASK])

Good catch. Probably you can add this in an initial patch explaining
that this was wrong?

>  		return ERR_PTR(-EOPNOTSUPP);
>  #endif
>  
> @@ -871,11 +915,53 @@ ctnetlink_alloc_filter(const struct nlattr * const cda[], u8 family)
>  	filter->family = family;
>  
>  #ifdef CONFIG_NF_CONNTRACK_MARK
> -	if (cda[CTA_MARK] && cda[CTA_MARK_MASK]) {
> +	if (cda[CTA_MARK]) {

I'm OK if you want to relax this, however.

>  		filter->mark.val = ntohl(nla_get_be32(cda[CTA_MARK]));
> -		filter->mark.mask = ntohl(nla_get_be32(cda[CTA_MARK_MASK]));
> +		filter->cta_flags |= CTA_FILTER_FLAG(CTA_MARK);
> +
> +		if (cda[CTA_MARK_MASK]) {
> +			filter->mark.mask = ntohl(nla_get_be32(cda[CTA_MARK_MASK]));
> +			filter->cta_flags |= CTA_FILTER_FLAG(CTA_MARK_MASK);
> +		}

If cda[CTA_MARK_MARK] is not set:

                } else {
                        filter->mark.mark = 0xffffffff;
                }

is required here.

Or you can just leave this as it was, ie.

	if (cda[CTA_MARK] && cda[CTA_MARK_MASK]) {
  		filter->mark.val = ntohl(nla_get_be32(cda[CTA_MARK]));
		filter->cta_flags |= CTA_FILTER_FLAG(CTA_MARK)
;
		filter->mark.mask = ntohl(nla_get_be32(cda[CTA_MARK_MASK]));
		filter->cta_flags |= CTA_FILTER_FLAG(CTA_MARK_MASK);
        }

>  	}
>  #endif
> +	if (!cda[CTA_FILTER])
> +		return filter;
> +
> +	err = ctnetlink_parse_zone(cda[CTA_ZONE], &(filter->zone));
> +	if (err < 0)
> +		return ERR_PTR(err);
> +
> +	err = ctnetlink_parse_filter(cda[CTA_FILTER], filter);
> +	if (err < 0)
> +		return ERR_PTR(err);
> +
> +	if (filter->orig_flags) {
> +		if (!cda[CTA_TUPLE_ORIG])
> +			return ERR_PTR(-EINVAL);
> +
> +		err = ctnetlink_parse_partial_tuple(cda, &(filter->orig),
> +						    CTA_TUPLE_ORIG,
> +						    filter->family,
> +						    &(filter->zone),
> +						    filter->orig_flags);
> +		if (err < 0)
> +			return ERR_PTR(err);
> +	}
> +
> +	if (filter->reply_flags) {
> +		if (!cda[CTA_TUPLE_REPLY])
> +			return ERR_PTR(-EINVAL);
> +
> +		err = ctnetlink_parse_partial_tuple(cda, &(filter->reply),
> +						    CTA_TUPLE_REPLY,
> +						    filter->family,
> +						    &(filter->zone),
> +						    filter->orig_flags);
> +		if (err < 0)
> +			return ERR_PTR(err);
> +	}
> +
>  	return filter;
>  }
>  
> @@ -886,7 +972,8 @@ static int ctnetlink_start(struct netlink_callback *cb)
>  	struct nfgenmsg *nfmsg = nlmsg_data(cb->nlh);
>  	u8 family = nfmsg->nfgen_family;
>  
> -	if (family || (cda[CTA_MARK] && cda[CTA_MARK_MASK])) {
> +	if (family || (cda[CTA_MARK] && cda[CTA_MARK_MASK]) ||
> +	    cda[CTA_FILTER]) {

Probably it is a good time to add a new helper function here:

        if (ctnetlink_needs_filter(family, cda)) {
                ...

where:

static bool ctnetlink_needs_filter(int family, ... cda)
{
        return family || cda[CTA_FILTER] ||
               (cda[CTA_MARK] && cda[CTA_MARK_MASK]);
}

>  		filter = ctnetlink_alloc_filter(cda, family);
>  		if (IS_ERR(filter))
>  			return PTR_ERR(filter);
> @@ -896,9 +983,82 @@ static int ctnetlink_start(struct netlink_callback *cb)
>  	return 0;
>  }
>  
> +static int ctnetlink_filter_match_tuple(struct nf_conntrack_tuple *filter_tuple,
> +					struct nf_conntrack_tuple *ct_tuple,
> +					u_int32_t flags, int family)
> +{
> +	switch (family) {
> +	case NFPROTO_IPV4:
> +		if (flags & CTA_FILTER_FLAG(CTA_IP_SRC) &&
> +		    filter_tuple->src.u3.ip != ct_tuple->src.u3.ip)
> +			return  0;
> +
> +		if (flags & CTA_FILTER_FLAG(CTA_IP_DST) &&
> +		    filter_tuple->dst.u3.ip != ct_tuple->dst.u3.ip)
> +			return  0;
> +		break;
> +
> +	case NFPROTO_IPV6:
> +		if (flags & CTA_FILTER_FLAG(CTA_IP_SRC) &&
> +		    !ipv6_addr_cmp(&(filter_tuple->src.u3.in6),
                                    ^
remove parens

> +				   &(ct_tuple->src.u3.in6)))
                                    ^
remove parens

> +			return 0;
> +
> +		if (flags & CTA_FILTER_FLAG(CTA_IP_DST) &&
> +		    !ipv6_addr_cmp(&(filter_tuple->dst.u3.in6),

same here.

> +				   &(ct_tuple->dst.u3.in6)))

and here.

> +			return 0;
> +		break;
> +	}
> +
> +	if (flags & CTA_FILTER_FLAG(CTA_PROTO_NUM) &&
> +	    filter_tuple->dst.protonum != ct_tuple->dst.protonum)
> +		return 0;
> +
> +	switch (ct_tuple->dst.protonum) {
> +	case IPPROTO_TCP:
> +	case IPPROTO_UDP:
> +		if (flags & CTA_FILTER_FLAG(CTA_PROTO_SRC_PORT) &&
> +		    filter_tuple->src.u.tcp.port != ct_tuple->src.u.tcp.port)
> +			return 0;
> +
> +		if (flags & CTA_FILTER_FLAG(CTA_PROTO_DST_PORT) &&
> +		    filter_tuple->dst.u.tcp.port != ct_tuple->dst.u.tcp.port)
> +			return 0;
> +		break;
> +
> +	case IPPROTO_ICMP:
> +		if (flags & CTA_FILTER_FLAG(CTA_PROTO_ICMP_TYPE) &&
> +		    filter_tuple->dst.u.icmp.type != ct_tuple->dst.u.icmp.type)
> +			return 0;
> +		if (flags & CTA_FILTER_FLAG(CTA_PROTO_ICMP_CODE) &&
> +		    filter_tuple->dst.u.icmp.code != ct_tuple->dst.u.icmp.code)
> +			return 0;
> +		if (flags & CTA_FILTER_FLAG(CTA_PROTO_ICMP_ID) &&
> +		    filter_tuple->src.u.icmp.id != ct_tuple->src.u.icmp.id)
> +			return 0;
> +		break;
> +
> +	case IPPROTO_ICMPV6:
> +		if (flags & CTA_FILTER_FLAG(CTA_PROTO_ICMPV6_TYPE) &&
> +		    filter_tuple->dst.u.icmp.type != ct_tuple->dst.u.icmp.type)
> +			return 0;
> +		if (flags & CTA_FILTER_FLAG(CTA_PROTO_ICMPV6_CODE) &&
> +		    filter_tuple->dst.u.icmp.code != ct_tuple->dst.u.icmp.code)
> +			return 0;
> +		if (flags & CTA_FILTER_FLAG(CTA_PROTO_ICMPV6_ID) &&
> +		    filter_tuple->src.u.icmp.id != ct_tuple->src.u.icmp.id)
> +			return 0;
> +		break;
> +	}
> +
> +	return 1;
> +}
> +
>  static int ctnetlink_filter_match(struct nf_conn *ct, void *data)
>  {
>  	struct ctnetlink_filter *filter = data;
> +	struct nf_conntrack_tuple *ct_tuple;

just call this variable tuple?

>  
>  	if (filter == NULL)
>  		goto out;
> @@ -910,8 +1070,29 @@ static int ctnetlink_filter_match(struct nf_conn *ct, void *data)
>  	if (filter->family && nf_ct_l3num(ct) != filter->family)
>  		goto ignore_entry;
>  
> +	if (filter->orig_flags) {
> +		ct_tuple = nf_ct_tuple(ct, IP_CT_DIR_ORIGINAL);
> +		if (!ctnetlink_filter_match_tuple(&(filter->orig), ct_tuple,
                                                   ^
remove parens

> +						  filter->orig_flags,
> +						  filter->family))
> +			goto ignore_entry;
> +	}
> +
> +	if (filter->reply_flags) {
> +		ct_tuple = nf_ct_tuple(ct, IP_CT_DIR_REPLY);
> +		if (!ctnetlink_filter_match_tuple(&(filter->reply), ct_tuple,
                                                   ^

remove parens, here and elsewhere.

> +						  filter->reply_flags,
> +						  filter->family))
> +			goto ignore_entry;
> +	}
> +
> +
>  #ifdef CONFIG_NF_CONNTRACK_MARK
> -	if ((ct->mark & filter->mark.mask) != filter->mark.val)
> +	if ((filter->cta_flags & CTA_FILTER_FLAG(CTA_MARK_MASK)) &&
> +	    (ct->mark & filter->mark.mask) != filter->mark.val)
> +		goto ignore_entry;
> +	else if ((filter->cta_flags & CTA_FILTER_FLAG(CTA_MARK)) &&
> +		 ct->mark != filter->mark.val)
>  		goto ignore_entry;
>  #endif
>  
> @@ -930,6 +1111,7 @@ ctnetlink_dump_table(struct sk_buff *skb, struct netlink_callback *cb)
>  	struct nf_conntrack_tuple_hash *h;
>  	struct hlist_nulls_node *n;
>  	struct nf_conn *nf_ct_evict[8];
> +	unsigned int flags = cb->data ? NLM_F_DUMP_FILTERED : 0;
>  	int res, i;
>  	spinlock_t *lockp;
>  
> @@ -979,7 +1161,7 @@ ctnetlink_dump_table(struct sk_buff *skb, struct netlink_callback *cb)
>  			ctnetlink_fill_info(skb, NETLINK_CB(cb->skb).portid,
>  					    cb->nlh->nlmsg_seq,
>  					    NFNL_MSG_TYPE(cb->nlh->nlmsg_type),
> -					    ct, true);
> +					    ct, true, flags);
>  			if (res < 0) {
>  				nf_conntrack_get(&ct->ct_general);
>  				cb->args[1] = (unsigned long)ct;
> @@ -1014,31 +1196,50 @@ ctnetlink_dump_table(struct sk_buff *skb, struct netlink_callback *cb)
>  }
>  
>  static int ipv4_nlattr_to_tuple(struct nlattr *tb[],
> -				struct nf_conntrack_tuple *t)
> +				struct nf_conntrack_tuple *t,
> +				u_int32_t flags)
>  {
> -	if (!tb[CTA_IP_V4_SRC] || !tb[CTA_IP_V4_DST])
> -		return -EINVAL;
> +	if (flags & CTA_FILTER_FLAG(CTA_IP_SRC)) {
> +		if (!tb[CTA_IP_V4_SRC])
> +			return -EINVAL;
>  
> -	t->src.u3.ip = nla_get_in_addr(tb[CTA_IP_V4_SRC]);
> -	t->dst.u3.ip = nla_get_in_addr(tb[CTA_IP_V4_DST]);
> +		t->src.u3.ip = nla_get_in_addr(tb[CTA_IP_V4_SRC]);
> +	}
> +
> +	if (flags & CTA_FILTER_FLAG(CTA_IP_DST)) {
> +		if (!tb[CTA_IP_V4_DST])
> +			return -EINVAL;
> +
> +		t->dst.u3.ip = nla_get_in_addr(tb[CTA_IP_V4_DST]);
> +	}
>  
>  	return 0;
>  }
>  
>  static int ipv6_nlattr_to_tuple(struct nlattr *tb[],
> -				struct nf_conntrack_tuple *t)
> +				struct nf_conntrack_tuple *t,
> +				u_int32_t flags)
>  {
> -	if (!tb[CTA_IP_V6_SRC] || !tb[CTA_IP_V6_DST])
> -		return -EINVAL;
> +	if (flags & CTA_FILTER_FLAG(CTA_IP_SRC)) {
> +		if (!tb[CTA_IP_V6_SRC])
> +			return -EINVAL;
>  
> -	t->src.u3.in6 = nla_get_in6_addr(tb[CTA_IP_V6_SRC]);
> -	t->dst.u3.in6 = nla_get_in6_addr(tb[CTA_IP_V6_DST]);
> +		t->src.u3.in6 = nla_get_in6_addr(tb[CTA_IP_V6_SRC]);
> +	}
> +
> +	if (flags & CTA_FILTER_FLAG(CTA_IP_DST)) {
> +		if (!tb[CTA_IP_V6_DST])
> +			return -EINVAL;
> +
> +		t->dst.u3.in6 = nla_get_in6_addr(tb[CTA_IP_V6_DST]);
> +	}
>  
>  	return 0;
>  }
>  
>  static int ctnetlink_parse_tuple_ip(struct nlattr *attr,
> -				    struct nf_conntrack_tuple *tuple)
> +				    struct nf_conntrack_tuple *tuple,
> +				    u_int32_t flags)
>  {
>  	struct nlattr *tb[CTA_IP_MAX+1];
>  	int ret = 0;
> @@ -1054,10 +1255,10 @@ static int ctnetlink_parse_tuple_ip(struct nlattr *attr,
>  
>  	switch (tuple->src.l3num) {
>  	case NFPROTO_IPV4:
> -		ret = ipv4_nlattr_to_tuple(tb, tuple);
> +		ret = ipv4_nlattr_to_tuple(tb, tuple, flags);
>  		break;
>  	case NFPROTO_IPV6:
> -		ret = ipv6_nlattr_to_tuple(tb, tuple);
> +		ret = ipv6_nlattr_to_tuple(tb, tuple, flags);
>  		break;
>  	}
>  
> @@ -1069,7 +1270,8 @@ static const struct nla_policy proto_nla_policy[CTA_PROTO_MAX+1] = {
>  };
>  
>  static int ctnetlink_parse_tuple_proto(struct nlattr *attr,
> -				       struct nf_conntrack_tuple *tuple)
> +				       struct nf_conntrack_tuple *tuple,
> +				       u_int32_t flags)
>  {
>  	const struct nf_conntrack_l4proto *l4proto;
>  	struct nlattr *tb[CTA_PROTO_MAX+1];
> @@ -1080,8 +1282,12 @@ static int ctnetlink_parse_tuple_proto(struct nlattr *attr,
>  	if (ret < 0)
>  		return ret;
>  
> +	if (!(flags & CTA_FILTER_FLAG(CTA_PROTO_NUM)))
> +		return 0;
> +
>  	if (!tb[CTA_PROTO_NUM])
>  		return -EINVAL;
> +
>  	tuple->dst.protonum = nla_get_u8(tb[CTA_PROTO_NUM]);
>  
>  	rcu_read_lock();
> @@ -1092,7 +1298,7 @@ static int ctnetlink_parse_tuple_proto(struct nlattr *attr,
>  						     l4proto->nla_policy,
>  						     NULL);
>  		if (ret == 0)
> -			ret = l4proto->nlattr_to_tuple(tb, tuple);
> +			ret = l4proto->nlattr_to_tuple(tb, tuple, flags);
>  	}
>  
>  	rcu_read_unlock();
> @@ -1144,9 +1350,10 @@ static const struct nla_policy tuple_nla_policy[CTA_TUPLE_MAX+1] = {
>  };
>  
>  static int
> -ctnetlink_parse_tuple(const struct nlattr * const cda[],
> -		      struct nf_conntrack_tuple *tuple, u32 type,
> -		      u_int8_t l3num, struct nf_conntrack_zone *zone)
> +ctnetlink_parse_partial_tuple(const struct nlattr * const cda[],
> +			      struct nf_conntrack_tuple *tuple, u32 type,
> +			      u_int8_t l3num, struct nf_conntrack_zone *zone,
> +			      u_int32_t flags)
>  {
>  	struct nlattr *tb[CTA_TUPLE_MAX+1];
>  	int err;
> @@ -1158,23 +1365,33 @@ ctnetlink_parse_tuple(const struct nlattr * const cda[],
>  	if (err < 0)
>  		return err;
>  
> -	if (!tb[CTA_TUPLE_IP])
> -		return -EINVAL;
>  
>  	tuple->src.l3num = l3num;
>  
> -	err = ctnetlink_parse_tuple_ip(tb[CTA_TUPLE_IP], tuple);
> -	if (err < 0)
> -		return err;
> +	if (flags & CTA_FILTER_FLAG(CTA_IP_DST) ||
> +	    flags & CTA_FILTER_FLAG(CTA_IP_SRC))
> +	{

        if (flags & CTA_FILTER_FLAG(CTA_IP_DST)) ||
            flags & CTA_FILTER_FLAG(CTA_IP_SRC)) {
                                                 ^
coding style nitpick.

> +		if (!tb[CTA_TUPLE_IP])
> +			return -EINVAL;
>  
> -	if (!tb[CTA_TUPLE_PROTO])
> -		return -EINVAL;
> +		err = ctnetlink_parse_tuple_ip(tb[CTA_TUPLE_IP], tuple, flags);
> +		if (err < 0)
> +			return err;
> +	}
>  
> -	err = ctnetlink_parse_tuple_proto(tb[CTA_TUPLE_PROTO], tuple);
> -	if (err < 0)
> -		return err;
> +	if (flags & CTA_FILTER_FLAG(CTA_PROTO_NUM)) {
> +		if (!tb[CTA_TUPLE_PROTO])
> +			return -EINVAL;
> +		err = ctnetlink_parse_tuple_proto(tb[CTA_TUPLE_PROTO], tuple, flags);
> +		if (err < 0)
> +			return err;
> +	}
> +	else if (flags & CTA_FILTER_FLAG(ALL_CTA_PROTO)) {

This should be instead:

	} else if (flags & CTA_FILTER_FLAG(ALL_CTA_PROTO)) {
        ^

on the same line (coding style nitpick).

> +		/* Can't manage proto flags without a protonum  */
> +		return -EINVAL;
> +	}
>  
> -	if (tb[CTA_TUPLE_ZONE]) {
> +	if (flags & CTA_FILTER_FLAG(CTA_TUPLE_ZONE)) {
>  		if (!zone)
>  			return -EINVAL;
>  
> @@ -1193,6 +1410,15 @@ ctnetlink_parse_tuple(const struct nlattr * const cda[],
>  	return 0;
>  }
>  
> +static int
> +ctnetlink_parse_tuple(const struct nlattr * const cda[],
> +		      struct nf_conntrack_tuple *tuple, u32 type,
> +		      u_int8_t l3num, struct nf_conntrack_zone *zone)
> +{
> +	return ctnetlink_parse_partial_tuple(cda, tuple, type, l3num, zone,
> +					     CTA_FILTER_FLAG(ALL));
> +}
> +
>  static const struct nla_policy help_nla_policy[CTA_HELP_MAX+1] = {
>  	[CTA_HELP_NAME]		= { .type = NLA_NUL_STRING,
>  				    .len = NF_CT_HELPER_NAME_LEN - 1 },
> @@ -1240,6 +1466,7 @@ static const struct nla_policy ct_nla_policy[CTA_MAX+1] = {
>  				    .len = NF_CT_LABELS_MAX_SIZE },
>  	[CTA_LABELS_MASK]	= { .type = NLA_BINARY,
>  				    .len = NF_CT_LABELS_MAX_SIZE },
> +	[CTA_FILTER]		= { .type = NLA_NESTED },
>  };
>  
>  static int ctnetlink_flush_iterate(struct nf_conn *ct, void *data)
> @@ -1385,7 +1612,7 @@ static int ctnetlink_get_conntrack(struct net *net, struct sock *ctnl,
>  	}
>  
>  	err = ctnetlink_fill_info(skb2, NETLINK_CB(skb).portid, nlh->nlmsg_seq,
> -				  NFNL_MSG_TYPE(nlh->nlmsg_type), ct, true);
> +				  NFNL_MSG_TYPE(nlh->nlmsg_type), ct, true, 0);
>  	nf_ct_put(ct);
>  	if (err <= 0)
>  		goto free;
> @@ -1458,7 +1685,7 @@ ctnetlink_dump_list(struct sk_buff *skb, struct netlink_callback *cb, bool dying
>  			res = ctnetlink_fill_info(skb, NETLINK_CB(cb->skb).portid,
>  						  cb->nlh->nlmsg_seq,
>  						  NFNL_MSG_TYPE(cb->nlh->nlmsg_type),
> -						  ct, dying ? true : false);
> +						  ct, dying ? true : false, 0);
>  			if (res < 0) {
>  				if (!atomic_inc_not_zero(&ct->ct_general.use))
>  					continue;
> diff --git a/net/netfilter/nf_conntrack_proto_icmp.c b/net/netfilter/nf_conntrack_proto_icmp.c
> index c2e3dff773bc..78092466192c 100644
> --- a/net/netfilter/nf_conntrack_proto_icmp.c
> +++ b/net/netfilter/nf_conntrack_proto_icmp.c
> @@ -271,20 +271,35 @@ static const struct nla_policy icmp_nla_policy[CTA_PROTO_MAX+1] = {
>  };
>  
>  static int icmp_nlattr_to_tuple(struct nlattr *tb[],
> -				struct nf_conntrack_tuple *tuple)
> +				struct nf_conntrack_tuple *tuple,
> +				u_int32_t flags)
>  {
> -	if (!tb[CTA_PROTO_ICMP_TYPE] ||
> -	    !tb[CTA_PROTO_ICMP_CODE] ||
> -	    !tb[CTA_PROTO_ICMP_ID])
> -		return -EINVAL;
> -
> -	tuple->dst.u.icmp.type = nla_get_u8(tb[CTA_PROTO_ICMP_TYPE]);
> -	tuple->dst.u.icmp.code = nla_get_u8(tb[CTA_PROTO_ICMP_CODE]);
> -	tuple->src.u.icmp.id = nla_get_be16(tb[CTA_PROTO_ICMP_ID]);
> -
> -	if (tuple->dst.u.icmp.type >= sizeof(invmap) ||
> -	    !invmap[tuple->dst.u.icmp.type])
> -		return -EINVAL;
> +	if (flags & CTA_FILTER_FLAG(CTA_PROTO_ICMP_TYPE)) {
> +
> +		if (!tb[CTA_PROTO_ICMP_TYPE])
> +			return -EINVAL;
> +
> +		tuple->dst.u.icmp.type = nla_get_u8(tb[CTA_PROTO_ICMP_TYPE]);
> +		if (tuple->dst.u.icmp.type >= sizeof(invmap) ||
> +		    !invmap[tuple->dst.u.icmp.type])
> +			return -EINVAL;
> +	}
> +
> +	if (flags & CTA_FILTER_FLAG(CTA_PROTO_ICMP_CODE)) {
> +
> +		if (!tb[CTA_PROTO_ICMP_CODE])
> +			return -EINVAL;
> +
> +		tuple->dst.u.icmp.code = nla_get_u8(tb[CTA_PROTO_ICMP_CODE]);
> +	}
> +
> +	if (flags & CTA_FILTER_FLAG(CTA_PROTO_ICMP_ID)) {
> +
> +		if (!tb[CTA_PROTO_ICMP_ID])
> +			return -EINVAL;
> +
> +		tuple->src.u.icmp.id = nla_get_be16(tb[CTA_PROTO_ICMP_ID]);
> +	}
>  
>  	return 0;
>  }
> diff --git a/net/netfilter/nf_conntrack_proto_icmpv6.c b/net/netfilter/nf_conntrack_proto_icmpv6.c
> index 6f9144e1f1c1..3bdccff83ec3 100644
> --- a/net/netfilter/nf_conntrack_proto_icmpv6.c
> +++ b/net/netfilter/nf_conntrack_proto_icmpv6.c
> @@ -193,21 +193,36 @@ static const struct nla_policy icmpv6_nla_policy[CTA_PROTO_MAX+1] = {
>  };
>  
>  static int icmpv6_nlattr_to_tuple(struct nlattr *tb[],
> -				struct nf_conntrack_tuple *tuple)
> +				struct nf_conntrack_tuple *tuple,
> +				u_int32_t flags)
>  {
> -	if (!tb[CTA_PROTO_ICMPV6_TYPE] ||
> -	    !tb[CTA_PROTO_ICMPV6_CODE] ||
> -	    !tb[CTA_PROTO_ICMPV6_ID])
> -		return -EINVAL;
> -
> -	tuple->dst.u.icmp.type = nla_get_u8(tb[CTA_PROTO_ICMPV6_TYPE]);
> -	tuple->dst.u.icmp.code = nla_get_u8(tb[CTA_PROTO_ICMPV6_CODE]);
> -	tuple->src.u.icmp.id = nla_get_be16(tb[CTA_PROTO_ICMPV6_ID]);
> -
> -	if (tuple->dst.u.icmp.type < 128 ||
> -	    tuple->dst.u.icmp.type - 128 >= sizeof(invmap) ||
> -	    !invmap[tuple->dst.u.icmp.type - 128])
> -		return -EINVAL;
> +	if (flags & CTA_FILTER_FLAG(CTA_PROTO_ICMPV6_TYPE)) {
> +
> +		if (!tb[CTA_PROTO_ICMPV6_TYPE])
> +			return -EINVAL;
> +
> +		tuple->dst.u.icmp.type = nla_get_u8(tb[CTA_PROTO_ICMPV6_TYPE]);
> +		if (tuple->dst.u.icmp.type < 128 ||
> +		    tuple->dst.u.icmp.type - 128 >= sizeof(invmap) ||
> +		    !invmap[tuple->dst.u.icmp.type - 128])
> +			return -EINVAL;
> +	}
> +
> +	if (flags & CTA_FILTER_FLAG(CTA_PROTO_ICMPV6_CODE)) {
> +
> +		if (!tb[CTA_PROTO_ICMPV6_CODE])
> +			return -EINVAL;
> +
> +		tuple->dst.u.icmp.code = nla_get_u8(tb[CTA_PROTO_ICMPV6_CODE]);
> +	}
> +
> +	if (flags & CTA_FILTER_FLAG(CTA_PROTO_ICMPV6_ID)) {
> +
> +		if (!tb[CTA_PROTO_ICMPV6_ID])
> +			return -EINVAL;
> +
> +		tuple->src.u.icmp.id = nla_get_be16(tb[CTA_PROTO_ICMPV6_ID]);
> +	}
>  
>  	return 0;
>  }
> -- 
> 2.20.1
>

Patch
diff mbox series

diff --git a/include/net/netfilter/nf_conntrack_l4proto.h b/include/net/netfilter/nf_conntrack_l4proto.h
index 4cad1f0a327a..88186b95b3c2 100644
--- a/include/net/netfilter/nf_conntrack_l4proto.h
+++ b/include/net/netfilter/nf_conntrack_l4proto.h
@@ -42,7 +42,8 @@  struct nf_conntrack_l4proto {
 	/* Calculate tuple nlattr size */
 	unsigned int (*nlattr_tuple_size)(void);
 	int (*nlattr_to_tuple)(struct nlattr *tb[],
-			       struct nf_conntrack_tuple *t);
+			       struct nf_conntrack_tuple *t,
+			       u_int32_t flags);
 	const struct nla_policy *nla_policy;
 
 	struct {
@@ -152,7 +153,8 @@  const struct nf_conntrack_l4proto *nf_ct_l4proto_find(u8 l4proto);
 int nf_ct_port_tuple_to_nlattr(struct sk_buff *skb,
 			       const struct nf_conntrack_tuple *tuple);
 int nf_ct_port_nlattr_to_tuple(struct nlattr *tb[],
-			       struct nf_conntrack_tuple *t);
+			       struct nf_conntrack_tuple *t,
+			       u_int32_t flags);
 unsigned int nf_ct_port_nlattr_tuple_size(void);
 extern const struct nla_policy nf_ct_port_nla_policy[];
 
diff --git a/include/uapi/linux/netfilter/nfnetlink_conntrack.h b/include/uapi/linux/netfilter/nfnetlink_conntrack.h
index 1d41810d17e2..8c67b9da2592 100644
--- a/include/uapi/linux/netfilter/nfnetlink_conntrack.h
+++ b/include/uapi/linux/netfilter/nfnetlink_conntrack.h
@@ -55,6 +55,7 @@  enum ctattr_type {
 	CTA_LABELS,
 	CTA_LABELS_MASK,
 	CTA_SYNPROXY,
+	CTA_FILTER,
 	__CTA_MAX
 };
 #define CTA_MAX (__CTA_MAX - 1)
@@ -276,4 +277,44 @@  enum ctattr_expect_stats {
 };
 #define CTA_STATS_EXP_MAX (__CTA_STATS_EXP_MAX - 1)
 
+enum ctattr_filter {
+	CTA_FILTER_UNSPEC,
+	CTA_FILTER_ORIG_FLAGS,
+	CTA_FILTER_REPLY_FLAGS,
+	__CTA_FILTER_MAX
+};
+#define CTA_FILTER_MAX (__CTA_FILTER_MAX - 1)
+
+#define CTA_FILTER_FLAG(x) CTA_FILTER_FLAG_ ## x
+
+/* applied on tuple filters */
+#define CTA_FILTER_FLAG_CTA_IP_SRC		(1 << 0)
+#define CTA_FILTER_FLAG_CTA_IP_DST		(1 << 1)
+#define CTA_FILTER_FLAG_CTA_TUPLE_ZONE		(1 << 2)
+#define CTA_FILTER_FLAG_CTA_PROTO_NUM		(1 << 3)
+#define CTA_FILTER_FLAG_CTA_PROTO_SRC_PORT	(1 << 4)
+#define CTA_FILTER_FLAG_CTA_PROTO_DST_PORT	(1 << 5)
+#define CTA_FILTER_FLAG_CTA_PROTO_ICMP_TYPE	(1 << 6)
+#define CTA_FILTER_FLAG_CTA_PROTO_ICMP_CODE	(1 << 7)
+#define CTA_FILTER_FLAG_CTA_PROTO_ICMP_ID	(1 << 8)
+#define CTA_FILTER_FLAG_CTA_PROTO_ICMPV6_TYPE	(1 << 9)
+#define CTA_FILTER_FLAG_CTA_PROTO_ICMPV6_CODE	(1 << 10)
+#define CTA_FILTER_FLAG_CTA_PROTO_ICMPV6_ID	(1 << 11)
+
+#define CTA_FILTER_FLAG_ALL_CTA_PROTO \
+  (CTA_FILTER_FLAG_CTA_PROTO_SRC_PORT | \
+   CTA_FILTER_FLAG_CTA_PROTO_DST_PORT | \
+   CTA_FILTER_FLAG_CTA_PROTO_ICMP_TYPE | \
+   CTA_FILTER_FLAG_CTA_PROTO_ICMP_CODE | \
+   CTA_FILTER_FLAG_CTA_PROTO_ICMP_ID | \
+   CTA_FILTER_FLAG_CTA_PROTO_ICMPV6_TYPE | \
+   CTA_FILTER_FLAG_CTA_PROTO_ICMPV6_CODE | \
+   CTA_FILTER_FLAG_CTA_PROTO_ICMPV6_ID)
+#define CTA_FILTER_FLAG_ALL			0xFFFFFFFF
+
+/* applied on filters */
+#define CTA_FILTER_FLAG_CTA_MARK		(1 << 0)
+#define CTA_FILTER_FLAG_CTA_MARK_MASK		(1 << 1)
+
+
 #endif /* _IPCONNTRACK_NETLINK_H */
diff --git a/net/netfilter/nf_conntrack_core.c b/net/netfilter/nf_conntrack_core.c
index 0af1898af2b8..790f0c1a46b8 100644
--- a/net/netfilter/nf_conntrack_core.c
+++ b/net/netfilter/nf_conntrack_core.c
@@ -1835,13 +1835,22 @@  const struct nla_policy nf_ct_port_nla_policy[CTA_PROTO_MAX+1] = {
 EXPORT_SYMBOL_GPL(nf_ct_port_nla_policy);
 
 int nf_ct_port_nlattr_to_tuple(struct nlattr *tb[],
-			       struct nf_conntrack_tuple *t)
+			       struct nf_conntrack_tuple *t,
+			       u_int32_t flags)
 {
-	if (!tb[CTA_PROTO_SRC_PORT] || !tb[CTA_PROTO_DST_PORT])
-		return -EINVAL;
+	if (flags & CTA_FILTER_FLAG(CTA_PROTO_SRC_PORT)) {
+		if (!tb[CTA_PROTO_SRC_PORT])
+			return -EINVAL;
+
+		t->src.u.tcp.port = nla_get_be16(tb[CTA_PROTO_SRC_PORT]);
+	}
 
-	t->src.u.tcp.port = nla_get_be16(tb[CTA_PROTO_SRC_PORT]);
-	t->dst.u.tcp.port = nla_get_be16(tb[CTA_PROTO_DST_PORT]);
+	if (flags & CTA_FILTER_FLAG(CTA_PROTO_DST_PORT)) {
+		if (!tb[CTA_PROTO_DST_PORT])
+			return -EINVAL;
+
+		t->dst.u.tcp.port = nla_get_be16(tb[CTA_PROTO_DST_PORT]);
+	}
 
 	return 0;
 }
diff --git a/net/netfilter/nf_conntrack_netlink.c b/net/netfilter/nf_conntrack_netlink.c
index d8d33ef52ce0..824f919e5ffe 100644
--- a/net/netfilter/nf_conntrack_netlink.c
+++ b/net/netfilter/nf_conntrack_netlink.c
@@ -544,14 +544,16 @@  static int ctnetlink_dump_info(struct sk_buff *skb, struct nf_conn *ct)
 
 static int
 ctnetlink_fill_info(struct sk_buff *skb, u32 portid, u32 seq, u32 type,
-		    struct nf_conn *ct, bool extinfo)
+		    struct nf_conn *ct, bool extinfo, unsigned int flags)
 {
 	const struct nf_conntrack_zone *zone;
 	struct nlmsghdr *nlh;
 	struct nfgenmsg *nfmsg;
 	struct nlattr *nest_parms;
-	unsigned int flags = portid ? NLM_F_MULTI : 0, event;
+	unsigned int event;
 
+	if (portid)
+		flags |= NLM_F_MULTI;
 	event = nfnl_msg_type(NFNL_SUBSYS_CTNETLINK, IPCTNL_MSG_CT_NEW);
 	nlh = nlmsg_put(skb, portid, seq, event, sizeof(*nfmsg), flags);
 	if (nlh == NULL)
@@ -847,20 +849,62 @@  static int ctnetlink_done(struct netlink_callback *cb)
 }
 
 struct ctnetlink_filter {
+	u_int32_t cta_flags;
 	u8 family;
+
+	u_int32_t orig_flags;
+	struct nf_conntrack_tuple orig;
+
+	u_int32_t reply_flags;
+	struct nf_conntrack_tuple reply;
+
+	struct nf_conntrack_zone zone;
+
 	struct {
 		u_int32_t val;
 		u_int32_t mask;
 	} mark;
 };
 
+
+static const struct nla_policy cta_filter_nla_policy[CTA_FILTER_MAX + 1] = {
+	[CTA_FILTER_ORIG_FLAGS]		= { .type = NLA_U32 },
+	[CTA_FILTER_REPLY_FLAGS]	= { .type = NLA_U32 },
+};
+
+static int ctnetlink_parse_filter(const struct nlattr *attr,
+				  struct ctnetlink_filter *filter)
+{
+	struct nlattr *tb[CTA_FILTER_MAX+1];
+	int ret = 0;
+
+	ret = nla_parse_nested(tb, CTA_FILTER_MAX, attr, cta_filter_nla_policy, NULL);
+	if (ret)
+		return ret;
+
+	if (tb[CTA_FILTER_ORIG_FLAGS])
+		filter->orig_flags = nla_get_u32(tb[CTA_FILTER_ORIG_FLAGS]);
+
+	if (tb[CTA_FILTER_REPLY_FLAGS])
+		filter->reply_flags = nla_get_u32(tb[CTA_FILTER_REPLY_FLAGS]);
+
+	return 0;
+}
+
+static int ctnetlink_parse_zone(const struct nlattr *attr, struct nf_conntrack_zone *zone);
+static int ctnetlink_parse_partial_tuple(const struct nlattr * const cda[],
+					 struct nf_conntrack_tuple *tuple,
+					 u32 type, u_int8_t l3num, struct nf_conntrack_zone *zone,
+					 u_int32_t flags);
+
 static struct ctnetlink_filter *
 ctnetlink_alloc_filter(const struct nlattr * const cda[], u8 family)
 {
 	struct ctnetlink_filter *filter;
+	int err;
 
 #ifndef CONFIG_NF_CONNTRACK_MARK
-	if (cda[CTA_MARK] && cda[CTA_MARK_MASK])
+	if (cda[CTA_MARK] || cda[CTA_MARK_MASK])
 		return ERR_PTR(-EOPNOTSUPP);
 #endif
 
@@ -871,11 +915,53 @@  ctnetlink_alloc_filter(const struct nlattr * const cda[], u8 family)
 	filter->family = family;
 
 #ifdef CONFIG_NF_CONNTRACK_MARK
-	if (cda[CTA_MARK] && cda[CTA_MARK_MASK]) {
+	if (cda[CTA_MARK]) {
 		filter->mark.val = ntohl(nla_get_be32(cda[CTA_MARK]));
-		filter->mark.mask = ntohl(nla_get_be32(cda[CTA_MARK_MASK]));
+		filter->cta_flags |= CTA_FILTER_FLAG(CTA_MARK);
+
+		if (cda[CTA_MARK_MASK]) {
+			filter->mark.mask = ntohl(nla_get_be32(cda[CTA_MARK_MASK]));
+			filter->cta_flags |= CTA_FILTER_FLAG(CTA_MARK_MASK);
+		}
 	}
 #endif
+	if (!cda[CTA_FILTER])
+		return filter;
+
+	err = ctnetlink_parse_zone(cda[CTA_ZONE], &(filter->zone));
+	if (err < 0)
+		return ERR_PTR(err);
+
+	err = ctnetlink_parse_filter(cda[CTA_FILTER], filter);
+	if (err < 0)
+		return ERR_PTR(err);
+
+	if (filter->orig_flags) {
+		if (!cda[CTA_TUPLE_ORIG])
+			return ERR_PTR(-EINVAL);
+
+		err = ctnetlink_parse_partial_tuple(cda, &(filter->orig),
+						    CTA_TUPLE_ORIG,
+						    filter->family,
+						    &(filter->zone),
+						    filter->orig_flags);
+		if (err < 0)
+			return ERR_PTR(err);
+	}
+
+	if (filter->reply_flags) {
+		if (!cda[CTA_TUPLE_REPLY])
+			return ERR_PTR(-EINVAL);
+
+		err = ctnetlink_parse_partial_tuple(cda, &(filter->reply),
+						    CTA_TUPLE_REPLY,
+						    filter->family,
+						    &(filter->zone),
+						    filter->orig_flags);
+		if (err < 0)
+			return ERR_PTR(err);
+	}
+
 	return filter;
 }
 
@@ -886,7 +972,8 @@  static int ctnetlink_start(struct netlink_callback *cb)
 	struct nfgenmsg *nfmsg = nlmsg_data(cb->nlh);
 	u8 family = nfmsg->nfgen_family;
 
-	if (family || (cda[CTA_MARK] && cda[CTA_MARK_MASK])) {
+	if (family || (cda[CTA_MARK] && cda[CTA_MARK_MASK]) ||
+	    cda[CTA_FILTER]) {
 		filter = ctnetlink_alloc_filter(cda, family);
 		if (IS_ERR(filter))
 			return PTR_ERR(filter);
@@ -896,9 +983,82 @@  static int ctnetlink_start(struct netlink_callback *cb)
 	return 0;
 }
 
+static int ctnetlink_filter_match_tuple(struct nf_conntrack_tuple *filter_tuple,
+					struct nf_conntrack_tuple *ct_tuple,
+					u_int32_t flags, int family)
+{
+	switch (family) {
+	case NFPROTO_IPV4:
+		if (flags & CTA_FILTER_FLAG(CTA_IP_SRC) &&
+		    filter_tuple->src.u3.ip != ct_tuple->src.u3.ip)
+			return  0;
+
+		if (flags & CTA_FILTER_FLAG(CTA_IP_DST) &&
+		    filter_tuple->dst.u3.ip != ct_tuple->dst.u3.ip)
+			return  0;
+		break;
+
+	case NFPROTO_IPV6:
+		if (flags & CTA_FILTER_FLAG(CTA_IP_SRC) &&
+		    !ipv6_addr_cmp(&(filter_tuple->src.u3.in6),
+				   &(ct_tuple->src.u3.in6)))
+			return 0;
+
+		if (flags & CTA_FILTER_FLAG(CTA_IP_DST) &&
+		    !ipv6_addr_cmp(&(filter_tuple->dst.u3.in6),
+				   &(ct_tuple->dst.u3.in6)))
+			return 0;
+		break;
+	}
+
+	if (flags & CTA_FILTER_FLAG(CTA_PROTO_NUM) &&
+	    filter_tuple->dst.protonum != ct_tuple->dst.protonum)
+		return 0;
+
+	switch (ct_tuple->dst.protonum) {
+	case IPPROTO_TCP:
+	case IPPROTO_UDP:
+		if (flags & CTA_FILTER_FLAG(CTA_PROTO_SRC_PORT) &&
+		    filter_tuple->src.u.tcp.port != ct_tuple->src.u.tcp.port)
+			return 0;
+
+		if (flags & CTA_FILTER_FLAG(CTA_PROTO_DST_PORT) &&
+		    filter_tuple->dst.u.tcp.port != ct_tuple->dst.u.tcp.port)
+			return 0;
+		break;
+
+	case IPPROTO_ICMP:
+		if (flags & CTA_FILTER_FLAG(CTA_PROTO_ICMP_TYPE) &&
+		    filter_tuple->dst.u.icmp.type != ct_tuple->dst.u.icmp.type)
+			return 0;
+		if (flags & CTA_FILTER_FLAG(CTA_PROTO_ICMP_CODE) &&
+		    filter_tuple->dst.u.icmp.code != ct_tuple->dst.u.icmp.code)
+			return 0;
+		if (flags & CTA_FILTER_FLAG(CTA_PROTO_ICMP_ID) &&
+		    filter_tuple->src.u.icmp.id != ct_tuple->src.u.icmp.id)
+			return 0;
+		break;
+
+	case IPPROTO_ICMPV6:
+		if (flags & CTA_FILTER_FLAG(CTA_PROTO_ICMPV6_TYPE) &&
+		    filter_tuple->dst.u.icmp.type != ct_tuple->dst.u.icmp.type)
+			return 0;
+		if (flags & CTA_FILTER_FLAG(CTA_PROTO_ICMPV6_CODE) &&
+		    filter_tuple->dst.u.icmp.code != ct_tuple->dst.u.icmp.code)
+			return 0;
+		if (flags & CTA_FILTER_FLAG(CTA_PROTO_ICMPV6_ID) &&
+		    filter_tuple->src.u.icmp.id != ct_tuple->src.u.icmp.id)
+			return 0;
+		break;
+	}
+
+	return 1;
+}
+
 static int ctnetlink_filter_match(struct nf_conn *ct, void *data)
 {
 	struct ctnetlink_filter *filter = data;
+	struct nf_conntrack_tuple *ct_tuple;
 
 	if (filter == NULL)
 		goto out;
@@ -910,8 +1070,29 @@  static int ctnetlink_filter_match(struct nf_conn *ct, void *data)
 	if (filter->family && nf_ct_l3num(ct) != filter->family)
 		goto ignore_entry;
 
+	if (filter->orig_flags) {
+		ct_tuple = nf_ct_tuple(ct, IP_CT_DIR_ORIGINAL);
+		if (!ctnetlink_filter_match_tuple(&(filter->orig), ct_tuple,
+						  filter->orig_flags,
+						  filter->family))
+			goto ignore_entry;
+	}
+
+	if (filter->reply_flags) {
+		ct_tuple = nf_ct_tuple(ct, IP_CT_DIR_REPLY);
+		if (!ctnetlink_filter_match_tuple(&(filter->reply), ct_tuple,
+						  filter->reply_flags,
+						  filter->family))
+			goto ignore_entry;
+	}
+
+
 #ifdef CONFIG_NF_CONNTRACK_MARK
-	if ((ct->mark & filter->mark.mask) != filter->mark.val)
+	if ((filter->cta_flags & CTA_FILTER_FLAG(CTA_MARK_MASK)) &&
+	    (ct->mark & filter->mark.mask) != filter->mark.val)
+		goto ignore_entry;
+	else if ((filter->cta_flags & CTA_FILTER_FLAG(CTA_MARK)) &&
+		 ct->mark != filter->mark.val)
 		goto ignore_entry;
 #endif
 
@@ -930,6 +1111,7 @@  ctnetlink_dump_table(struct sk_buff *skb, struct netlink_callback *cb)
 	struct nf_conntrack_tuple_hash *h;
 	struct hlist_nulls_node *n;
 	struct nf_conn *nf_ct_evict[8];
+	unsigned int flags = cb->data ? NLM_F_DUMP_FILTERED : 0;
 	int res, i;
 	spinlock_t *lockp;
 
@@ -979,7 +1161,7 @@  ctnetlink_dump_table(struct sk_buff *skb, struct netlink_callback *cb)
 			ctnetlink_fill_info(skb, NETLINK_CB(cb->skb).portid,
 					    cb->nlh->nlmsg_seq,
 					    NFNL_MSG_TYPE(cb->nlh->nlmsg_type),
-					    ct, true);
+					    ct, true, flags);
 			if (res < 0) {
 				nf_conntrack_get(&ct->ct_general);
 				cb->args[1] = (unsigned long)ct;
@@ -1014,31 +1196,50 @@  ctnetlink_dump_table(struct sk_buff *skb, struct netlink_callback *cb)
 }
 
 static int ipv4_nlattr_to_tuple(struct nlattr *tb[],
-				struct nf_conntrack_tuple *t)
+				struct nf_conntrack_tuple *t,
+				u_int32_t flags)
 {
-	if (!tb[CTA_IP_V4_SRC] || !tb[CTA_IP_V4_DST])
-		return -EINVAL;
+	if (flags & CTA_FILTER_FLAG(CTA_IP_SRC)) {
+		if (!tb[CTA_IP_V4_SRC])
+			return -EINVAL;
 
-	t->src.u3.ip = nla_get_in_addr(tb[CTA_IP_V4_SRC]);
-	t->dst.u3.ip = nla_get_in_addr(tb[CTA_IP_V4_DST]);
+		t->src.u3.ip = nla_get_in_addr(tb[CTA_IP_V4_SRC]);
+	}
+
+	if (flags & CTA_FILTER_FLAG(CTA_IP_DST)) {
+		if (!tb[CTA_IP_V4_DST])
+			return -EINVAL;
+
+		t->dst.u3.ip = nla_get_in_addr(tb[CTA_IP_V4_DST]);
+	}
 
 	return 0;
 }
 
 static int ipv6_nlattr_to_tuple(struct nlattr *tb[],
-				struct nf_conntrack_tuple *t)
+				struct nf_conntrack_tuple *t,
+				u_int32_t flags)
 {
-	if (!tb[CTA_IP_V6_SRC] || !tb[CTA_IP_V6_DST])
-		return -EINVAL;
+	if (flags & CTA_FILTER_FLAG(CTA_IP_SRC)) {
+		if (!tb[CTA_IP_V6_SRC])
+			return -EINVAL;
 
-	t->src.u3.in6 = nla_get_in6_addr(tb[CTA_IP_V6_SRC]);
-	t->dst.u3.in6 = nla_get_in6_addr(tb[CTA_IP_V6_DST]);
+		t->src.u3.in6 = nla_get_in6_addr(tb[CTA_IP_V6_SRC]);
+	}
+
+	if (flags & CTA_FILTER_FLAG(CTA_IP_DST)) {
+		if (!tb[CTA_IP_V6_DST])
+			return -EINVAL;
+
+		t->dst.u3.in6 = nla_get_in6_addr(tb[CTA_IP_V6_DST]);
+	}
 
 	return 0;
 }
 
 static int ctnetlink_parse_tuple_ip(struct nlattr *attr,
-				    struct nf_conntrack_tuple *tuple)
+				    struct nf_conntrack_tuple *tuple,
+				    u_int32_t flags)
 {
 	struct nlattr *tb[CTA_IP_MAX+1];
 	int ret = 0;
@@ -1054,10 +1255,10 @@  static int ctnetlink_parse_tuple_ip(struct nlattr *attr,
 
 	switch (tuple->src.l3num) {
 	case NFPROTO_IPV4:
-		ret = ipv4_nlattr_to_tuple(tb, tuple);
+		ret = ipv4_nlattr_to_tuple(tb, tuple, flags);
 		break;
 	case NFPROTO_IPV6:
-		ret = ipv6_nlattr_to_tuple(tb, tuple);
+		ret = ipv6_nlattr_to_tuple(tb, tuple, flags);
 		break;
 	}
 
@@ -1069,7 +1270,8 @@  static const struct nla_policy proto_nla_policy[CTA_PROTO_MAX+1] = {
 };
 
 static int ctnetlink_parse_tuple_proto(struct nlattr *attr,
-				       struct nf_conntrack_tuple *tuple)
+				       struct nf_conntrack_tuple *tuple,
+				       u_int32_t flags)
 {
 	const struct nf_conntrack_l4proto *l4proto;
 	struct nlattr *tb[CTA_PROTO_MAX+1];
@@ -1080,8 +1282,12 @@  static int ctnetlink_parse_tuple_proto(struct nlattr *attr,
 	if (ret < 0)
 		return ret;
 
+	if (!(flags & CTA_FILTER_FLAG(CTA_PROTO_NUM)))
+		return 0;
+
 	if (!tb[CTA_PROTO_NUM])
 		return -EINVAL;
+
 	tuple->dst.protonum = nla_get_u8(tb[CTA_PROTO_NUM]);
 
 	rcu_read_lock();
@@ -1092,7 +1298,7 @@  static int ctnetlink_parse_tuple_proto(struct nlattr *attr,
 						     l4proto->nla_policy,
 						     NULL);
 		if (ret == 0)
-			ret = l4proto->nlattr_to_tuple(tb, tuple);
+			ret = l4proto->nlattr_to_tuple(tb, tuple, flags);
 	}
 
 	rcu_read_unlock();
@@ -1144,9 +1350,10 @@  static const struct nla_policy tuple_nla_policy[CTA_TUPLE_MAX+1] = {
 };
 
 static int
-ctnetlink_parse_tuple(const struct nlattr * const cda[],
-		      struct nf_conntrack_tuple *tuple, u32 type,
-		      u_int8_t l3num, struct nf_conntrack_zone *zone)
+ctnetlink_parse_partial_tuple(const struct nlattr * const cda[],
+			      struct nf_conntrack_tuple *tuple, u32 type,
+			      u_int8_t l3num, struct nf_conntrack_zone *zone,
+			      u_int32_t flags)
 {
 	struct nlattr *tb[CTA_TUPLE_MAX+1];
 	int err;
@@ -1158,23 +1365,33 @@  ctnetlink_parse_tuple(const struct nlattr * const cda[],
 	if (err < 0)
 		return err;
 
-	if (!tb[CTA_TUPLE_IP])
-		return -EINVAL;
 
 	tuple->src.l3num = l3num;
 
-	err = ctnetlink_parse_tuple_ip(tb[CTA_TUPLE_IP], tuple);
-	if (err < 0)
-		return err;
+	if (flags & CTA_FILTER_FLAG(CTA_IP_DST) ||
+	    flags & CTA_FILTER_FLAG(CTA_IP_SRC))
+	{
+		if (!tb[CTA_TUPLE_IP])
+			return -EINVAL;
 
-	if (!tb[CTA_TUPLE_PROTO])
-		return -EINVAL;
+		err = ctnetlink_parse_tuple_ip(tb[CTA_TUPLE_IP], tuple, flags);
+		if (err < 0)
+			return err;
+	}
 
-	err = ctnetlink_parse_tuple_proto(tb[CTA_TUPLE_PROTO], tuple);
-	if (err < 0)
-		return err;
+	if (flags & CTA_FILTER_FLAG(CTA_PROTO_NUM)) {
+		if (!tb[CTA_TUPLE_PROTO])
+			return -EINVAL;
+		err = ctnetlink_parse_tuple_proto(tb[CTA_TUPLE_PROTO], tuple, flags);
+		if (err < 0)
+			return err;
+	}
+	else if (flags & CTA_FILTER_FLAG(ALL_CTA_PROTO)) {
+		/* Can't manage proto flags without a protonum  */
+		return -EINVAL;
+	}
 
-	if (tb[CTA_TUPLE_ZONE]) {
+	if (flags & CTA_FILTER_FLAG(CTA_TUPLE_ZONE)) {
 		if (!zone)
 			return -EINVAL;
 
@@ -1193,6 +1410,15 @@  ctnetlink_parse_tuple(const struct nlattr * const cda[],
 	return 0;
 }
 
+static int
+ctnetlink_parse_tuple(const struct nlattr * const cda[],
+		      struct nf_conntrack_tuple *tuple, u32 type,
+		      u_int8_t l3num, struct nf_conntrack_zone *zone)
+{
+	return ctnetlink_parse_partial_tuple(cda, tuple, type, l3num, zone,
+					     CTA_FILTER_FLAG(ALL));
+}
+
 static const struct nla_policy help_nla_policy[CTA_HELP_MAX+1] = {
 	[CTA_HELP_NAME]		= { .type = NLA_NUL_STRING,
 				    .len = NF_CT_HELPER_NAME_LEN - 1 },
@@ -1240,6 +1466,7 @@  static const struct nla_policy ct_nla_policy[CTA_MAX+1] = {
 				    .len = NF_CT_LABELS_MAX_SIZE },
 	[CTA_LABELS_MASK]	= { .type = NLA_BINARY,
 				    .len = NF_CT_LABELS_MAX_SIZE },
+	[CTA_FILTER]		= { .type = NLA_NESTED },
 };
 
 static int ctnetlink_flush_iterate(struct nf_conn *ct, void *data)
@@ -1385,7 +1612,7 @@  static int ctnetlink_get_conntrack(struct net *net, struct sock *ctnl,
 	}
 
 	err = ctnetlink_fill_info(skb2, NETLINK_CB(skb).portid, nlh->nlmsg_seq,
-				  NFNL_MSG_TYPE(nlh->nlmsg_type), ct, true);
+				  NFNL_MSG_TYPE(nlh->nlmsg_type), ct, true, 0);
 	nf_ct_put(ct);
 	if (err <= 0)
 		goto free;
@@ -1458,7 +1685,7 @@  ctnetlink_dump_list(struct sk_buff *skb, struct netlink_callback *cb, bool dying
 			res = ctnetlink_fill_info(skb, NETLINK_CB(cb->skb).portid,
 						  cb->nlh->nlmsg_seq,
 						  NFNL_MSG_TYPE(cb->nlh->nlmsg_type),
-						  ct, dying ? true : false);
+						  ct, dying ? true : false, 0);
 			if (res < 0) {
 				if (!atomic_inc_not_zero(&ct->ct_general.use))
 					continue;
diff --git a/net/netfilter/nf_conntrack_proto_icmp.c b/net/netfilter/nf_conntrack_proto_icmp.c
index c2e3dff773bc..78092466192c 100644
--- a/net/netfilter/nf_conntrack_proto_icmp.c
+++ b/net/netfilter/nf_conntrack_proto_icmp.c
@@ -271,20 +271,35 @@  static const struct nla_policy icmp_nla_policy[CTA_PROTO_MAX+1] = {
 };
 
 static int icmp_nlattr_to_tuple(struct nlattr *tb[],
-				struct nf_conntrack_tuple *tuple)
+				struct nf_conntrack_tuple *tuple,
+				u_int32_t flags)
 {
-	if (!tb[CTA_PROTO_ICMP_TYPE] ||
-	    !tb[CTA_PROTO_ICMP_CODE] ||
-	    !tb[CTA_PROTO_ICMP_ID])
-		return -EINVAL;
-
-	tuple->dst.u.icmp.type = nla_get_u8(tb[CTA_PROTO_ICMP_TYPE]);
-	tuple->dst.u.icmp.code = nla_get_u8(tb[CTA_PROTO_ICMP_CODE]);
-	tuple->src.u.icmp.id = nla_get_be16(tb[CTA_PROTO_ICMP_ID]);
-
-	if (tuple->dst.u.icmp.type >= sizeof(invmap) ||
-	    !invmap[tuple->dst.u.icmp.type])
-		return -EINVAL;
+	if (flags & CTA_FILTER_FLAG(CTA_PROTO_ICMP_TYPE)) {
+
+		if (!tb[CTA_PROTO_ICMP_TYPE])
+			return -EINVAL;
+
+		tuple->dst.u.icmp.type = nla_get_u8(tb[CTA_PROTO_ICMP_TYPE]);
+		if (tuple->dst.u.icmp.type >= sizeof(invmap) ||
+		    !invmap[tuple->dst.u.icmp.type])
+			return -EINVAL;
+	}
+
+	if (flags & CTA_FILTER_FLAG(CTA_PROTO_ICMP_CODE)) {
+
+		if (!tb[CTA_PROTO_ICMP_CODE])
+			return -EINVAL;
+
+		tuple->dst.u.icmp.code = nla_get_u8(tb[CTA_PROTO_ICMP_CODE]);
+	}
+
+	if (flags & CTA_FILTER_FLAG(CTA_PROTO_ICMP_ID)) {
+
+		if (!tb[CTA_PROTO_ICMP_ID])
+			return -EINVAL;
+
+		tuple->src.u.icmp.id = nla_get_be16(tb[CTA_PROTO_ICMP_ID]);
+	}
 
 	return 0;
 }
diff --git a/net/netfilter/nf_conntrack_proto_icmpv6.c b/net/netfilter/nf_conntrack_proto_icmpv6.c
index 6f9144e1f1c1..3bdccff83ec3 100644
--- a/net/netfilter/nf_conntrack_proto_icmpv6.c
+++ b/net/netfilter/nf_conntrack_proto_icmpv6.c
@@ -193,21 +193,36 @@  static const struct nla_policy icmpv6_nla_policy[CTA_PROTO_MAX+1] = {
 };
 
 static int icmpv6_nlattr_to_tuple(struct nlattr *tb[],
-				struct nf_conntrack_tuple *tuple)
+				struct nf_conntrack_tuple *tuple,
+				u_int32_t flags)
 {
-	if (!tb[CTA_PROTO_ICMPV6_TYPE] ||
-	    !tb[CTA_PROTO_ICMPV6_CODE] ||
-	    !tb[CTA_PROTO_ICMPV6_ID])
-		return -EINVAL;
-
-	tuple->dst.u.icmp.type = nla_get_u8(tb[CTA_PROTO_ICMPV6_TYPE]);
-	tuple->dst.u.icmp.code = nla_get_u8(tb[CTA_PROTO_ICMPV6_CODE]);
-	tuple->src.u.icmp.id = nla_get_be16(tb[CTA_PROTO_ICMPV6_ID]);
-
-	if (tuple->dst.u.icmp.type < 128 ||
-	    tuple->dst.u.icmp.type - 128 >= sizeof(invmap) ||
-	    !invmap[tuple->dst.u.icmp.type - 128])
-		return -EINVAL;
+	if (flags & CTA_FILTER_FLAG(CTA_PROTO_ICMPV6_TYPE)) {
+
+		if (!tb[CTA_PROTO_ICMPV6_TYPE])
+			return -EINVAL;
+
+		tuple->dst.u.icmp.type = nla_get_u8(tb[CTA_PROTO_ICMPV6_TYPE]);
+		if (tuple->dst.u.icmp.type < 128 ||
+		    tuple->dst.u.icmp.type - 128 >= sizeof(invmap) ||
+		    !invmap[tuple->dst.u.icmp.type - 128])
+			return -EINVAL;
+	}
+
+	if (flags & CTA_FILTER_FLAG(CTA_PROTO_ICMPV6_CODE)) {
+
+		if (!tb[CTA_PROTO_ICMPV6_CODE])
+			return -EINVAL;
+
+		tuple->dst.u.icmp.code = nla_get_u8(tb[CTA_PROTO_ICMPV6_CODE]);
+	}
+
+	if (flags & CTA_FILTER_FLAG(CTA_PROTO_ICMPV6_ID)) {
+
+		if (!tb[CTA_PROTO_ICMPV6_ID])
+			return -EINVAL;
+
+		tuple->src.u.icmp.id = nla_get_be16(tb[CTA_PROTO_ICMPV6_ID]);
+	}
 
 	return 0;
 }