diff mbox series

[net-next,2/8] net: sched: cls_api: handle generic cls errors

Message ID 20180116172027.22128-3-aring@mojatatu.com
State Changes Requested, archived
Delegated to: David Miller
Headers show
Series net: sched: cls: add extack support | expand

Commit Message

Alexander Aring Jan. 16, 2018, 5:20 p.m. UTC
This patch adds extack support for generic cls handling. The extack
will be set deeper to each called function which is not part of netdev
core api.

Cc: David Ahern <dsahern@gmail.com>
Signed-off-by: Alexander Aring <aring@mojatatu.com>
---
 net/sched/cls_api.c | 55 +++++++++++++++++++++++++++++++++++++++++------------
 1 file changed, 43 insertions(+), 12 deletions(-)

Comments

Cong Wang Jan. 16, 2018, 11:12 p.m. UTC | #1
On Tue, Jan 16, 2018 at 9:20 AM, Alexander Aring <aring@mojatatu.com> wrote:
> @@ -1117,8 +1146,10 @@ int tcf_exts_validate(struct net *net, struct tcf_proto *tp, struct nlattr **tb,
>         }
>  #else
>         if ((exts->action && tb[exts->action]) ||
> -           (exts->police && tb[exts->police]))
> +           (exts->police && tb[exts->police])) {
> +               NL_SET_ERR_MSG(extack, "Actions are not supported. Check compile options");
>                 return -EOPNOTSUPP;
> +       }
>  #endif

"Check compile options" is confusing, it is clearer if we can just
say we need to enable CONFIG_NET_CLS_ACT here.
David Ahern Jan. 16, 2018, 11:58 p.m. UTC | #2
On 1/16/18 9:20 AM, Alexander Aring wrote:
> This patch adds extack support for generic cls handling. The extack
> will be set deeper to each called function which is not part of netdev
> core api.
> 
> Cc: David Ahern <dsahern@gmail.com>
> Signed-off-by: Alexander Aring <aring@mojatatu.com>
> ---
>  net/sched/cls_api.c | 55 +++++++++++++++++++++++++++++++++++++++++------------
>  1 file changed, 43 insertions(+), 12 deletions(-)
> 
> diff --git a/net/sched/cls_api.c b/net/sched/cls_api.c
> index 01d09055707d..c25a9b4bcb4b 100644
> --- a/net/sched/cls_api.c
> +++ b/net/sched/cls_api.c
> @@ -122,7 +122,8 @@ static inline u32 tcf_auto_prio(struct tcf_proto *tp)
>  
>  static struct tcf_proto *tcf_proto_create(const char *kind, u32 protocol,
>  					  u32 prio, u32 parent, struct Qdisc *q,
> -					  struct tcf_chain *chain)
> +					  struct tcf_chain *chain,
> +					  struct netlink_ext_ack *extack)
>  {
>  	struct tcf_proto *tp;
>  	int err;
> @@ -148,6 +149,7 @@ static struct tcf_proto *tcf_proto_create(const char *kind, u32 protocol,
>  			module_put(tp->ops->owner);
>  			err = -EAGAIN;
>  		} else {
> +			NL_SET_ERR_MSG(extack, "TC classifier not found");
>  			err = -ENOENT;
>  		}
>  		goto errout;
> @@ -662,7 +664,8 @@ static int tfilter_notify(struct net *net, struct sk_buff *oskb,
>  static int tfilter_del_notify(struct net *net, struct sk_buff *oskb,
>  			      struct nlmsghdr *n, struct tcf_proto *tp,
>  			      struct Qdisc *q, u32 parent,
> -			      void *fh, bool unicast, bool *last)
> +			      void *fh, bool unicast, bool *last,
> +			      struct netlink_ext_ack *extack)
>  {
>  	struct sk_buff *skb;
>  	u32 portid = oskb ? NETLINK_CB(oskb).portid : 0;
> @@ -674,6 +677,7 @@ static int tfilter_del_notify(struct net *net, struct sk_buff *oskb,
>  
>  	if (tcf_fill_node(net, skb, tp, q, parent, fh, portid, n->nlmsg_seq,
>  			  n->nlmsg_flags, RTM_DELTFILTER) <= 0) {
> +		NL_SET_ERR_MSG(extack, "Failed to build del event notification");
>  		kfree_skb(skb);
>  		return -EINVAL;
>  	}
> @@ -687,8 +691,11 @@ static int tfilter_del_notify(struct net *net, struct sk_buff *oskb,
>  	if (unicast)
>  		return netlink_unicast(net->rtnl, skb, portid, MSG_DONTWAIT);
>  
> -	return rtnetlink_send(skb, net, portid, RTNLGRP_TC,
> -			      n->nlmsg_flags & NLM_F_ECHO);
> +	err = rtnetlink_send(skb, net, portid, RTNLGRP_TC,
> +			     n->nlmsg_flags & NLM_F_ECHO);
> +	if (err < 0)
> +		NL_SET_ERR_MSG(extack, "Failed to send filter delete notification");

not sure we want to do this -- extack for internal failures like this
one or below in tc_ctl_tfilter.


> +	return err;
>  }
>  
>  static void tfilter_notify_chain(struct net *net, struct sk_buff *oskb,
> @@ -749,8 +756,10 @@ static int tc_ctl_tfilter(struct sk_buff *skb, struct nlmsghdr *n,
>  	if (prio == 0) {
>  		switch (n->nlmsg_type) {
>  		case RTM_DELTFILTER:
> -			if (protocol || t->tcm_handle || tca[TCA_KIND])
> +			if (protocol || t->tcm_handle || tca[TCA_KIND]) {
> +				NL_SET_ERR_MSG(extack, "Cannot flush filters with protocol, handle or kind set");
>  				return -ENOENT;
> +			}
>  			break;
>  		case RTM_NEWTFILTER:
>  			/* If no priority is provided by the user,
> @@ -763,6 +772,7 @@ static int tc_ctl_tfilter(struct sk_buff *skb, struct nlmsghdr *n,
>  			}
>  			/* fall-through */
>  		default:
> +			NL_SET_ERR_MSG(extack, "Invalid filter command with priority of zero");
>  			return -ENOENT;
>  		}
>  	}
> @@ -780,23 +790,31 @@ static int tc_ctl_tfilter(struct sk_buff *skb, struct nlmsghdr *n,
>  		parent = q->handle;
>  	} else {
>  		q = qdisc_lookup(dev, TC_H_MAJ(t->tcm_parent));
> -		if (!q)
> +		if (!q) {
> +			NL_SET_ERR_MSG(extack, "Parent Qdisc doesn't exists");

Messages should avoid contractions; spell out 'does not'. Please check
all of the patches.

Also, it should be 'exist' (no 's' on the end).


>  			return -EINVAL;
> +		}
>  	}
>  
>  	/* Is it classful? */
>  	cops = q->ops->cl_ops;
> -	if (!cops)
> +	if (!cops) {
> +		NL_SET_ERR_MSG(extack, "Qdisc not classful");
>  		return -EINVAL;
> +	}
>  
> -	if (!cops->tcf_block)
> +	if (!cops->tcf_block) {
> +		NL_SET_ERR_MSG(extack, "Class doesn't support blocks");
>  		return -EOPNOTSUPP;
> +	}
>  
>  	/* Do we search for filter, attached to class? */
>  	if (TC_H_MIN(parent)) {
>  		cl = cops->find(q, parent);
> -		if (cl == 0)
> +		if (cl == 0) {
> +			NL_SET_ERR_MSG(extack, "Specified Class doesn't exist");

s/Class/class/

>  			return -ENOENT;
> +		}
>  	}
>  
>  	/* And the last stroke */
> @@ -808,12 +826,14 @@ static int tc_ctl_tfilter(struct sk_buff *skb, struct nlmsghdr *n,
>  
>  	chain_index = tca[TCA_CHAIN] ? nla_get_u32(tca[TCA_CHAIN]) : 0;
>  	if (chain_index > TC_ACT_EXT_VAL_MASK) {
> +		NL_SET_ERR_MSG(extack, "Specified chain index exceeds upper limit");
>  		err = -EINVAL;
>  		goto errout;
>  	}
>  	chain = tcf_chain_get(block, chain_index,
>  			      n->nlmsg_type == RTM_NEWTFILTER);
>  	if (!chain) {
> +		NL_SET_ERR_MSG(extack, "Cannot find specified filter chain");
>  		err = n->nlmsg_type == RTM_NEWTFILTER ? -ENOMEM : -EINVAL;
>  		goto errout;
>  	}
> @@ -829,6 +849,7 @@ static int tc_ctl_tfilter(struct sk_buff *skb, struct nlmsghdr *n,
>  	tp = tcf_chain_tp_find(chain, &chain_info, protocol,
>  			       prio, prio_allocate);
>  	if (IS_ERR(tp)) {
> +		NL_SET_ERR_MSG(extack, "Filter with specified priority/protocol not found");
>  		err = PTR_ERR(tp);
>  		goto errout;
>  	}
> @@ -837,12 +858,14 @@ static int tc_ctl_tfilter(struct sk_buff *skb, struct nlmsghdr *n,
>  		/* Proto-tcf does not exist, create new one */
>  
>  		if (tca[TCA_KIND] == NULL || !protocol) {
> +			NL_SET_ERR_MSG(extack, "Filter kind and protocol must be specified");
>  			err = -EINVAL;
>  			goto errout;
>  		}
>  
>  		if (n->nlmsg_type != RTM_NEWTFILTER ||
>  		    !(n->nlmsg_flags & NLM_F_CREATE)) {
> +			NL_SET_ERR_MSG(extack, "Need both RTM_NEWTFILTER and NLM_F_CREATE to create a new filter");

that does not seem the right message. tc_ctl_tfilter is overloaded for
new, delete and get so the response here needs to reflect that. I
believe in this case the user did not specify a valid chain.

Also, the messages are targeted at users not developers, so no code
jargon / API references.


>  			err = -ENOENT;
>  			goto errout;
>  		}
> @@ -851,13 +874,14 @@ static int tc_ctl_tfilter(struct sk_buff *skb, struct nlmsghdr *n,
>  			prio = tcf_auto_prio(tcf_chain_tp_prev(&chain_info));
>  
>  		tp = tcf_proto_create(nla_data(tca[TCA_KIND]),
> -				      protocol, prio, parent, q, chain);
> +				      protocol, prio, parent, q, chain, extack);
>  		if (IS_ERR(tp)) {
>  			err = PTR_ERR(tp);
>  			goto errout;
>  		}
>  		tp_created = 1;
>  	} else if (tca[TCA_KIND] && nla_strcmp(tca[TCA_KIND], tp->ops->kind)) {
> +		NL_SET_ERR_MSG(extack, "Specified filter kind does not match existing one");
>  		err = -EINVAL;
>  		goto errout;
>  	}
> @@ -876,6 +900,7 @@ static int tc_ctl_tfilter(struct sk_buff *skb, struct nlmsghdr *n,
>  
>  		if (n->nlmsg_type != RTM_NEWTFILTER ||
>  		    !(n->nlmsg_flags & NLM_F_CREATE)) {
> +			NL_SET_ERR_MSG(extack, "Need both RTM_NEWTFILTER and NLM_F_CREATE to create a new filter");

same here.


>  			err = -ENOENT;
>  			goto errout;
>  		}
> @@ -887,13 +912,14 @@ static int tc_ctl_tfilter(struct sk_buff *skb, struct nlmsghdr *n,
>  			if (n->nlmsg_flags & NLM_F_EXCL) {
>  				if (tp_created)
>  					tcf_proto_destroy(tp);
> +				NL_SET_ERR_MSG(extack, "Exclusivity check success. Filter already exists");

Just "Filter already exists"


>  				err = -EEXIST;
>  				goto errout;
>  			}
>  			break;
>  		case RTM_DELTFILTER:
>  			err = tfilter_del_notify(net, skb, n, tp, q, parent,
> -						 fh, false, &last);
> +						 fh, false, &last, extack);
>  			if (err)
>  				goto errout;
>  			if (last) {
> @@ -904,8 +930,11 @@ static int tc_ctl_tfilter(struct sk_buff *skb, struct nlmsghdr *n,
>  		case RTM_GETTFILTER:
>  			err = tfilter_notify(net, skb, n, tp, q, parent, fh,
>  					     RTM_NEWTFILTER, true);
> +			if (err < 0)
> +				NL_SET_ERR_MSG(extack, "Failed to send filter notify message");


>  			goto errout;
>  		default:
> +			NL_SET_ERR_MSG(extack, "Invalid netlink message type");
>  			err = -EINVAL;
>  			goto errout;
>  		}
> @@ -1117,8 +1146,10 @@ int tcf_exts_validate(struct net *net, struct tcf_proto *tp, struct nlattr **tb,
>  	}
>  #else
>  	if ((exts->action && tb[exts->action]) ||
> -	    (exts->police && tb[exts->police]))
> +	    (exts->police && tb[exts->police])) {
> +		NL_SET_ERR_MSG(extack, "Actions are not supported. Check compile options");

To Cong's comment, perhaps "Classifier actions are not supported per
compile options"


>  		return -EOPNOTSUPP;
> +	}
>  #endif
>  
>  	return 0;
>
Jamal Hadi Salim Jan. 17, 2018, 12:19 a.m. UTC | #3
On 18-01-16 06:58 PM, David Ahern wrote:
> On 1/16/18 9:20 AM, Alexander Aring wrote:


>>   		}
>>   
>>   		if (n->nlmsg_type != RTM_NEWTFILTER ||
>>   		    !(n->nlmsg_flags & NLM_F_CREATE)) {
>> +			NL_SET_ERR_MSG(extack, "Need both RTM_NEWTFILTER and NLM_F_CREATE to create a new filter");
> 
> that does not seem the right message. tc_ctl_tfilter is overloaded for
> new, delete and get so the response here needs to reflect that. I
> believe in this case the user did not specify a valid chain.
> 

Are you sure you are looking at the correct code?
It is a create message that is at stake here.
A create has to have RTM_NEWTFILTER and NLM_F_CREATE

> Also, the messages are targeted at users not developers, so no code
> jargon / API references.

Generally true, but should this rule really be scripture?
The main user here is tc in  user space and it doesnt make mistakes
in this case i.e we will  never see this error with tc because a
create will always have those two set correctly; OTOH, a developer
writing some new app is more likely to make this mistake (in which
case this message is very helpful).

cheers,
jamal
David Ahern Jan. 17, 2018, 3:22 a.m. UTC | #4
On 1/16/18 4:19 PM, Jamal Hadi Salim wrote:
> On 18-01-16 06:58 PM, David Ahern wrote:
>> On 1/16/18 9:20 AM, Alexander Aring wrote:
> 
> 
>>>           }
>>>             if (n->nlmsg_type != RTM_NEWTFILTER ||
>>>               !(n->nlmsg_flags & NLM_F_CREATE)) {
>>> +            NL_SET_ERR_MSG(extack, "Need both RTM_NEWTFILTER and
>>> NLM_F_CREATE to create a new filter");
>>
>> that does not seem the right message. tc_ctl_tfilter is overloaded for
>> new, delete and get so the response here needs to reflect that. I
>> believe in this case the user did not specify a valid chain.
>>
> 
> Are you sure you are looking at the correct code?

        tp = tcf_chain_tp_find(chain, &chain_info, protocol,
                               prio, prio_allocate);
        if (IS_ERR(tp)) {
                err = PTR_ERR(tp);
                goto errout;
        }

        if (tp == NULL) {
                /* Proto-tcf does not exist, create new one */

                if (tca[TCA_KIND] == NULL || !protocol) {
                        err = -EINVAL;
                        goto errout;
                }

                if (n->nlmsg_type != RTM_NEWTFILTER ||
                    !(n->nlmsg_flags & NLM_F_CREATE)) {
                        err = -ENOENT;
                        goto errout;
                }

Seems like that code path is run for other than RTM_NEWTFILTER. Even the
check there says != is ok -- just error out with an ENOENT.


> It is a create message that is at stake here.
> A create has to have RTM_NEWTFILTER and NLM_F_CREATE
> 
>> Also, the messages are targeted at users not developers, so no code
>> jargon / API references.
> 
> Generally true, but should this rule really be scripture?
> The main user here is tc in  user space and it doesnt make mistakes
> in this case i.e we will  never see this error with tc because a
> create will always have those two set correctly; OTOH, a developer
> writing some new app is more likely to make this mistake (in which
> case this message is very helpful).

argumentative. I have focused on adding specific error messages that
help a user understand why a command failed. It can be done with
referencing API names.
Jamal Hadi Salim Jan. 17, 2018, 2:32 p.m. UTC | #5
On 18-01-16 10:22 PM, David Ahern wrote:

>          tp = tcf_chain_tp_find(chain, &chain_info, protocol,
>                                 prio, prio_allocate);
>          if (IS_ERR(tp)) {
>                  err = PTR_ERR(tp);
>                  goto errout;
>          }
> 
 >
>          if (tp == NULL) {
>                  /* Proto-tcf does not exist, create new one */
>  >
>                  if (tca[TCA_KIND] == NULL || !protocol) {
>                          err = -EINVAL;
>                          goto errout;
>                  }
> 
>                  if (n->nlmsg_type != RTM_NEWTFILTER ||
>                      !(n->nlmsg_flags & NLM_F_CREATE)) {
>                          err = -ENOENT;
>                          goto errout;
>                  }
> 


The assumption is if we got that far in the code a create
path(per the comment) is the sane thing to do. A create
requires tca[TCA_KIND], protocol, RTM_NEWTFILTER and
NLM_F_CREATE

> Seems like that code path is run for other than RTM_NEWTFILTER. Even the
> check there says != is ok -- just error out with an ENOENT.
> 

To be honest, I am not fond of those checks either, I find myself
thinking sometimes about what would happen given a specific message.
A lot of these control paths in tc kernel parts sometimes are too
clever. That code needs refactoring to separate all 3 operations
for readability/maintainability. I would say the same for sch_api
We could fix it up (but those are separate patches); then we can
have much better error messages as well.

>> Generally true, but should this rule really be scripture?
>> The main user here is tc in  user space and it doesnt make mistakes
>> in this case i.e we will  never see this error with tc because a
>> create will always have those two set correctly; OTOH, a developer
>> writing some new app is more likely to make this mistake (in which
>> case this message is very helpful).
> 
> argumentative.
We are having a discussion.

>I have focused on adding specific error messages that
> help a user understand why a command failed. It can be done with
> referencing API names.


That is one use case which is sensible. It is not like the
message is saying "consult IBM manual X for error code 1234"
IMO:
The kernel should be helpful to the user - but that user is not just
the user of an app within iproute2. It could be a robot, a human
using another CLI app, a developer etc. I think we need to look
at specific cases and make those calls. My view is that we should
never fail from tc for this specific case unless someone is fixing
up tc.

If you want to say it is always a human that needs to interpret
and react - then we need rules well stated somewhere
(eg you stated ENOMEM should not have extack, it was sufficiently
expressive etc). This will help reduce the time spent reviewing
patches or for people to respin.

The kernel should - when it makes sense - even return an extack for
a _successful message_ (I can give you several use cases in tc today)
or binary data via the cookie; and hopefully we can avoid collission
when we start sending such patches by having the discussion now.

cheers,
jamal
diff mbox series

Patch

diff --git a/net/sched/cls_api.c b/net/sched/cls_api.c
index 01d09055707d..c25a9b4bcb4b 100644
--- a/net/sched/cls_api.c
+++ b/net/sched/cls_api.c
@@ -122,7 +122,8 @@  static inline u32 tcf_auto_prio(struct tcf_proto *tp)
 
 static struct tcf_proto *tcf_proto_create(const char *kind, u32 protocol,
 					  u32 prio, u32 parent, struct Qdisc *q,
-					  struct tcf_chain *chain)
+					  struct tcf_chain *chain,
+					  struct netlink_ext_ack *extack)
 {
 	struct tcf_proto *tp;
 	int err;
@@ -148,6 +149,7 @@  static struct tcf_proto *tcf_proto_create(const char *kind, u32 protocol,
 			module_put(tp->ops->owner);
 			err = -EAGAIN;
 		} else {
+			NL_SET_ERR_MSG(extack, "TC classifier not found");
 			err = -ENOENT;
 		}
 		goto errout;
@@ -662,7 +664,8 @@  static int tfilter_notify(struct net *net, struct sk_buff *oskb,
 static int tfilter_del_notify(struct net *net, struct sk_buff *oskb,
 			      struct nlmsghdr *n, struct tcf_proto *tp,
 			      struct Qdisc *q, u32 parent,
-			      void *fh, bool unicast, bool *last)
+			      void *fh, bool unicast, bool *last,
+			      struct netlink_ext_ack *extack)
 {
 	struct sk_buff *skb;
 	u32 portid = oskb ? NETLINK_CB(oskb).portid : 0;
@@ -674,6 +677,7 @@  static int tfilter_del_notify(struct net *net, struct sk_buff *oskb,
 
 	if (tcf_fill_node(net, skb, tp, q, parent, fh, portid, n->nlmsg_seq,
 			  n->nlmsg_flags, RTM_DELTFILTER) <= 0) {
+		NL_SET_ERR_MSG(extack, "Failed to build del event notification");
 		kfree_skb(skb);
 		return -EINVAL;
 	}
@@ -687,8 +691,11 @@  static int tfilter_del_notify(struct net *net, struct sk_buff *oskb,
 	if (unicast)
 		return netlink_unicast(net->rtnl, skb, portid, MSG_DONTWAIT);
 
-	return rtnetlink_send(skb, net, portid, RTNLGRP_TC,
-			      n->nlmsg_flags & NLM_F_ECHO);
+	err = rtnetlink_send(skb, net, portid, RTNLGRP_TC,
+			     n->nlmsg_flags & NLM_F_ECHO);
+	if (err < 0)
+		NL_SET_ERR_MSG(extack, "Failed to send filter delete notification");
+	return err;
 }
 
 static void tfilter_notify_chain(struct net *net, struct sk_buff *oskb,
@@ -749,8 +756,10 @@  static int tc_ctl_tfilter(struct sk_buff *skb, struct nlmsghdr *n,
 	if (prio == 0) {
 		switch (n->nlmsg_type) {
 		case RTM_DELTFILTER:
-			if (protocol || t->tcm_handle || tca[TCA_KIND])
+			if (protocol || t->tcm_handle || tca[TCA_KIND]) {
+				NL_SET_ERR_MSG(extack, "Cannot flush filters with protocol, handle or kind set");
 				return -ENOENT;
+			}
 			break;
 		case RTM_NEWTFILTER:
 			/* If no priority is provided by the user,
@@ -763,6 +772,7 @@  static int tc_ctl_tfilter(struct sk_buff *skb, struct nlmsghdr *n,
 			}
 			/* fall-through */
 		default:
+			NL_SET_ERR_MSG(extack, "Invalid filter command with priority of zero");
 			return -ENOENT;
 		}
 	}
@@ -780,23 +790,31 @@  static int tc_ctl_tfilter(struct sk_buff *skb, struct nlmsghdr *n,
 		parent = q->handle;
 	} else {
 		q = qdisc_lookup(dev, TC_H_MAJ(t->tcm_parent));
-		if (!q)
+		if (!q) {
+			NL_SET_ERR_MSG(extack, "Parent Qdisc doesn't exists");
 			return -EINVAL;
+		}
 	}
 
 	/* Is it classful? */
 	cops = q->ops->cl_ops;
-	if (!cops)
+	if (!cops) {
+		NL_SET_ERR_MSG(extack, "Qdisc not classful");
 		return -EINVAL;
+	}
 
-	if (!cops->tcf_block)
+	if (!cops->tcf_block) {
+		NL_SET_ERR_MSG(extack, "Class doesn't support blocks");
 		return -EOPNOTSUPP;
+	}
 
 	/* Do we search for filter, attached to class? */
 	if (TC_H_MIN(parent)) {
 		cl = cops->find(q, parent);
-		if (cl == 0)
+		if (cl == 0) {
+			NL_SET_ERR_MSG(extack, "Specified Class doesn't exist");
 			return -ENOENT;
+		}
 	}
 
 	/* And the last stroke */
@@ -808,12 +826,14 @@  static int tc_ctl_tfilter(struct sk_buff *skb, struct nlmsghdr *n,
 
 	chain_index = tca[TCA_CHAIN] ? nla_get_u32(tca[TCA_CHAIN]) : 0;
 	if (chain_index > TC_ACT_EXT_VAL_MASK) {
+		NL_SET_ERR_MSG(extack, "Specified chain index exceeds upper limit");
 		err = -EINVAL;
 		goto errout;
 	}
 	chain = tcf_chain_get(block, chain_index,
 			      n->nlmsg_type == RTM_NEWTFILTER);
 	if (!chain) {
+		NL_SET_ERR_MSG(extack, "Cannot find specified filter chain");
 		err = n->nlmsg_type == RTM_NEWTFILTER ? -ENOMEM : -EINVAL;
 		goto errout;
 	}
@@ -829,6 +849,7 @@  static int tc_ctl_tfilter(struct sk_buff *skb, struct nlmsghdr *n,
 	tp = tcf_chain_tp_find(chain, &chain_info, protocol,
 			       prio, prio_allocate);
 	if (IS_ERR(tp)) {
+		NL_SET_ERR_MSG(extack, "Filter with specified priority/protocol not found");
 		err = PTR_ERR(tp);
 		goto errout;
 	}
@@ -837,12 +858,14 @@  static int tc_ctl_tfilter(struct sk_buff *skb, struct nlmsghdr *n,
 		/* Proto-tcf does not exist, create new one */
 
 		if (tca[TCA_KIND] == NULL || !protocol) {
+			NL_SET_ERR_MSG(extack, "Filter kind and protocol must be specified");
 			err = -EINVAL;
 			goto errout;
 		}
 
 		if (n->nlmsg_type != RTM_NEWTFILTER ||
 		    !(n->nlmsg_flags & NLM_F_CREATE)) {
+			NL_SET_ERR_MSG(extack, "Need both RTM_NEWTFILTER and NLM_F_CREATE to create a new filter");
 			err = -ENOENT;
 			goto errout;
 		}
@@ -851,13 +874,14 @@  static int tc_ctl_tfilter(struct sk_buff *skb, struct nlmsghdr *n,
 			prio = tcf_auto_prio(tcf_chain_tp_prev(&chain_info));
 
 		tp = tcf_proto_create(nla_data(tca[TCA_KIND]),
-				      protocol, prio, parent, q, chain);
+				      protocol, prio, parent, q, chain, extack);
 		if (IS_ERR(tp)) {
 			err = PTR_ERR(tp);
 			goto errout;
 		}
 		tp_created = 1;
 	} else if (tca[TCA_KIND] && nla_strcmp(tca[TCA_KIND], tp->ops->kind)) {
+		NL_SET_ERR_MSG(extack, "Specified filter kind does not match existing one");
 		err = -EINVAL;
 		goto errout;
 	}
@@ -876,6 +900,7 @@  static int tc_ctl_tfilter(struct sk_buff *skb, struct nlmsghdr *n,
 
 		if (n->nlmsg_type != RTM_NEWTFILTER ||
 		    !(n->nlmsg_flags & NLM_F_CREATE)) {
+			NL_SET_ERR_MSG(extack, "Need both RTM_NEWTFILTER and NLM_F_CREATE to create a new filter");
 			err = -ENOENT;
 			goto errout;
 		}
@@ -887,13 +912,14 @@  static int tc_ctl_tfilter(struct sk_buff *skb, struct nlmsghdr *n,
 			if (n->nlmsg_flags & NLM_F_EXCL) {
 				if (tp_created)
 					tcf_proto_destroy(tp);
+				NL_SET_ERR_MSG(extack, "Exclusivity check success. Filter already exists");
 				err = -EEXIST;
 				goto errout;
 			}
 			break;
 		case RTM_DELTFILTER:
 			err = tfilter_del_notify(net, skb, n, tp, q, parent,
-						 fh, false, &last);
+						 fh, false, &last, extack);
 			if (err)
 				goto errout;
 			if (last) {
@@ -904,8 +930,11 @@  static int tc_ctl_tfilter(struct sk_buff *skb, struct nlmsghdr *n,
 		case RTM_GETTFILTER:
 			err = tfilter_notify(net, skb, n, tp, q, parent, fh,
 					     RTM_NEWTFILTER, true);
+			if (err < 0)
+				NL_SET_ERR_MSG(extack, "Failed to send filter notify message");
 			goto errout;
 		default:
+			NL_SET_ERR_MSG(extack, "Invalid netlink message type");
 			err = -EINVAL;
 			goto errout;
 		}
@@ -1117,8 +1146,10 @@  int tcf_exts_validate(struct net *net, struct tcf_proto *tp, struct nlattr **tb,
 	}
 #else
 	if ((exts->action && tb[exts->action]) ||
-	    (exts->police && tb[exts->police]))
+	    (exts->police && tb[exts->police])) {
+		NL_SET_ERR_MSG(extack, "Actions are not supported. Check compile options");
 		return -EOPNOTSUPP;
+	}
 #endif
 
 	return 0;