net: add network device notifier trace points
Subsystems (3)
PATCH 1/1 net: add network device notifier trace points
There already are network trace points for transmit and receive but nothing for state changes. Add network tracepoints for before and after netlink callback is done. This is simple (without extack or other info) but that could be added if useful. Network namespace id would also be helpful but hard to get a string for it.
This is the result of a conversation about monitoring of link state changes with BPF. Parsing netlink is hard and unnecessary because the data exists (unserialized) already in the callbacks.
# cd /sys/kernel/debug/tracing # echo 1 > events/net/net_dev_notifier_entry/enable # echo 1 > events/net/net_dev_notifier/enable # ip li set dev eno1 down # ip li set dev eno1 up # cat trace # tracer: nop # # _-----=> irqs-off # / _----=> need-resched # | / _---=> hardirq/softirq # || / _--=> preempt-depth # ||| / delay # TASK-PID CPU# |||| TIMESTAMP FUNCTION # | | | |||| | | ip-3194 [011] .... 74.926831: net_dev_notifier_entry: dev=eno1 event=GOING_DOWN ip-3194 [011] .... 74.926838: net_dev_notifier: dev=eno1 event=GOING_DOWN ret=1 ip-3194 [011] .... 75.029827: net_dev_notifier_entry: dev=eno1 event=DOWN ip-3194 [011] .... 75.031808: net_dev_notifier: dev=eno1 event=DOWN ret=1 ip-3195 [011] .... 78.063845: net_dev_notifier_entry: dev=eno1 event=PRE_UP ip-3195 [011] .... 78.063854: net_dev_notifier: dev=eno1 event=PRE_UP ret=1 ip-3195 [011] .... 78.279038: net_dev_notifier_entry: dev=eno1 event=UP ip-3195 [011] .... 78.279065: net_dev_notifier: dev=eno1 event=UP ret=1
--- include/trace/events/net.h | 115 +++++++++++++++++++++++++++++++++++++ net/core/dev.c | 9 ++- 2 files changed, 123 insertions(+), 1 deletion(-)
include/trace/events/net.h +115 -0
@@ -11,6 +11,121 @@#include <linux/ip.h> #include <linux/tracepoint.h> +TRACE_DEFINE_ENUM (NETDEV_UP ); +TRACE_DEFINE_ENUM (NETDEV_DOWN ); +TRACE_DEFINE_ENUM (NETDEV_REBOOT ); +TRACE_DEFINE_ENUM (NETDEV_CHANGE ); +TRACE_DEFINE_ENUM (NETDEV_REGISTER ); +TRACE_DEFINE_ENUM (NETDEV_UNREGISTER ); +TRACE_DEFINE_ENUM (NETDEV_CHANGEMTU ); +TRACE_DEFINE_ENUM (NETDEV_CHANGEADDR ); +TRACE_DEFINE_ENUM (NETDEV_PRE_CHANGEADDR ); +TRACE_DEFINE_ENUM (NETDEV_GOING_DOWN ); +TRACE_DEFINE_ENUM (NETDEV_CHANGENAME ); +TRACE_DEFINE_ENUM (NETDEV_FEAT_CHANGE ); +TRACE_DEFINE_ENUM (NETDEV_BONDING_FAILOVER ); +TRACE_DEFINE_ENUM (NETDEV_PRE_UP ); +TRACE_DEFINE_ENUM (NETDEV_PRE_TYPE_CHANGE ); +TRACE_DEFINE_ENUM (NETDEV_POST_TYPE_CHANGE ); +TRACE_DEFINE_ENUM (NETDEV_POST_INIT ); +TRACE_DEFINE_ENUM (NETDEV_RELEASE ); +TRACE_DEFINE_ENUM (NETDEV_NOTIFY_PEERS ); +TRACE_DEFINE_ENUM (NETDEV_JOIN ); +TRACE_DEFINE_ENUM (NETDEV_CHANGEUPPER ); +TRACE_DEFINE_ENUM (NETDEV_RESEND_IGMP ); +TRACE_DEFINE_ENUM (NETDEV_PRECHANGEMTU ); +TRACE_DEFINE_ENUM (NETDEV_CHANGEINFODATA ); +TRACE_DEFINE_ENUM (NETDEV_BONDING_INFO ); +TRACE_DEFINE_ENUM (NETDEV_PRECHANGEUPPER ); +TRACE_DEFINE_ENUM (NETDEV_CHANGELOWERSTATE ); +TRACE_DEFINE_ENUM (NETDEV_UDP_TUNNEL_PUSH_INFO ); +TRACE_DEFINE_ENUM (NETDEV_UDP_TUNNEL_DROP_INFO ); +TRACE_DEFINE_ENUM (NETDEV_CHANGE_TX_QUEUE_LEN ); +TRACE_DEFINE_ENUM (NETDEV_CVLAN_FILTER_PUSH_INFO ); +TRACE_DEFINE_ENUM (NETDEV_CVLAN_FILTER_DROP_INFO ); +TRACE_DEFINE_ENUM (NETDEV_SVLAN_FILTER_PUSH_INFO ); +TRACE_DEFINE_ENUM (NETDEV_SVLAN_FILTER_DROP_INFO ); + +#define netdev_event_type (type) \ + __print_symbolic(type, \ + { NETDEV_UP, "UP" }, \ + { NETDEV_DOWN, "DOWN" }, \ + { NETDEV_REBOOT, "REBOOT" }, \ + { NETDEV_CHANGE, "CHANGE" }, \ + { NETDEV_REGISTER, "REGISTER" }, \ + { NETDEV_UNREGISTER, "UNREGISTER" }, \ + { NETDEV_CHANGEMTU, "CHANGEMTU" }, \ + { NETDEV_CHANGEADDR, "CHANGEADDR" }, \ + { NETDEV_PRE_CHANGEADDR, "PRE_CHANGEADDR" }, \ + { NETDEV_GOING_DOWN, "GOING_DOWN" }, \ + { NETDEV_CHANGENAME, "CHANGENAME" }, \ + { NETDEV_FEAT_CHANGE, "FEAT_CHANGE" }, \ + { NETDEV_BONDING_FAILOVER, "BONDING_FAILOVER" }, \ + { NETDEV_PRE_UP, "PRE_UP" }, \ + { NETDEV_PRE_TYPE_CHANGE, "PRE_TYPE_CHANGE" }, \ + { NETDEV_POST_TYPE_CHANGE, "POST_TYPE_CHANGE" }, \ + { NETDEV_POST_INIT, "POST_INIT" }, \ + { NETDEV_RELEASE, "RELEASE" }, \ + { NETDEV_NOTIFY_PEERS, "NOTIFY_PEERS" }, \ + { NETDEV_JOIN, "JOIN" }, \ + { NETDEV_CHANGEUPPER, "CHANGEUPPER" }, \ + { NETDEV_RESEND_IGMP, "RESEND_IGMP" }, \ + { NETDEV_PRECHANGEMTU, "PRECHANGEMTU" }, \ + { NETDEV_CHANGEINFODATA, "CHANGEINFODATA" }, \ + { NETDEV_BONDING_INFO, "BONDING_INFO" }, \ + { NETDEV_PRECHANGEUPPER, "PRECHANGEUPPER" }, \ + { NETDEV_CHANGELOWERSTATE, "CHANGELOWERSTATE" }, \ + { NETDEV_UDP_TUNNEL_PUSH_INFO, "UDP_TUNNEL_PUSH_INFO" }, \ + { NETDEV_UDP_TUNNEL_DROP_INFO, "UDP_TUNNEL_DROP_INFO" }, \ + { NETDEV_CHANGE_TX_QUEUE_LEN, "CHANGE_TX_QUEUE_LEN" }, \ + { NETDEV_CVLAN_FILTER_PUSH_INFO, "CVLAN_FILTER_PUSH_INFO" }, \ + { NETDEV_CVLAN_FILTER_DROP_INFO, "CVLAN_FILTER_DROP_INFO" }, \ + { NETDEV_SVLAN_FILTER_PUSH_INFO, "SVLAN_FILTER_PUSH_INFO" }, \ + { NETDEV_SVLAN_FILTER_DROP_INFO, "SVLAN_FILTER_DROP_INFO" } ) + +TRACE_EVENT (net_dev_notifier_entry, + + TP_PROTO(const struct netdev_notifier_info *info, unsigned long val), + +TP_ARGS (info, val), + +TP_STRUCT__entry ( + __string( name, info->dev->name ) + __field( enum netdev_cmd, event ) + ), + +TP_fast_assign ( + __assign_str(name, info->dev->name); + __entry->event = val; + ), + +TP_printk ("dev=%s event=%s" , +__get_str (name),netdev_event_type (__entry->event)) +); + +TRACE_EVENT (net_dev_notifier, + + TP_PROTO(const struct netdev_notifier_info *info, int rc, unsigned long val), + +TP_ARGS (info, rc, val), + +TP_STRUCT__entry ( + __string( name, info->dev->name ) + __field( enum netdev_cmd, event ) +__field ( int, rc ) + ), + +TP_fast_assign ( + __assign_str(name, info->dev->name); + __entry->event = val; + __entry->rc = rc; + ), + +TP_printk ("dev=%s event=%s ret=%d" , +__get_str (name),netdev_event_type (__entry->event), + __entry->rc) +); + TRACE_EVENT(net_dev_start_xmit, TP_PROTO(const struct sk_buff *skb, const struct net_device *dev),
net/core/dev.c +8 -1
@@ -1735,8 +1735,15 @@ EXPORT_SYMBOL(unregister_netdevice_notifier);static int call_netdevice_notifiers_info (unsigned long val,struct netdev_notifier_info *info) { +int rc; +ASSERT_RTNL (); -return raw_notifier_call_chain (&netdev_chain, val, info); + +trace_net_dev_notifier_entry (info, val); + rc =raw_notifier_call_chain (&netdev_chain, val, info); +trace_net_dev_notifier (info, rc, val); + +return rc; } static int call_netdevice_notifiers_extack(unsigned long val,
Signature · 1 line
2.19.2
Quoted 5 lines · Stephen Hemminger
There already are network trace points for transmit and receive but nothing for state changes. Add network tracepoints for before and after netlink callback is done. This is simple (without extack or other info) but that could be added if useful. Network namespace id would also be helpful but hard to get a string for it.
That has been a sore spot for a long time. One option is to allocate a namespace id relative to init_net as every namespace is created and then use the nsid in tracepoints.
Quoted 15 lines
This is the result of a conversation about monitoring of link state changes with BPF. Parsing netlink is hard and unnecessary because the data exists (unserialized) already in the callbacks.
# cd /sys/kernel/debug/tracing # echo 1 > events/net/net_dev_notifier_entry/enable # echo 1 > events/net/net_dev_notifier/enable # ip li set dev eno1 down # ip li set dev eno1 up # cat trace # tracer: nop # # _-----=> irqs-off
Quoted 8 lines
# / _----=> need-resched # | / _---=> hardirq/softirq # || / _--=> preempt-depth # ||| / delay # TASK-PID CPU# |||| TIMESTAMP FUNCTION # | | | |||| | | ip-3194 [011] .... 74.926831: net_dev_notifier_entry: dev=eno1 event=GOING_DOWN ip-3194 [011] .... 74.926838: net_dev_notifier: dev=eno1 event=GOING_DOWN ret=1
naming the second one 'net_dev_notifier__exit' would align the columns, make this a lot easier to read and better discriminate the entry/exit difference.
Quoted 2 lines · On Tue, 18 Dec 2018 18:27:06 -0800 Stephen Hemminger…
This is the result of a conversation about monitoring of link state changes with BPF.
If you want to use this from BPF then you are in for a surprise. As tracepoints BPF cannot read these "__string" constructs, here the netdev name. I tried a lot of different tricks that didn't work, see [1], until Alexei explained that it simply isn't supported.
I instead recommend adding the ifindex to the tracepoint. The__string and __assign_str is also a performance concern as it does strcpy behind your back.
I have an year old TODO list item about improving this:
** TODO Make perf-script plugin for ifindex to name translation
SCHEDULED: <2017-11-20 Mon>
Today, the existing network tracepoints using dev->name is not that usable by BPF, as BPF cannot identify the interface. Thus IMHO it would make sense to convert the existing network tracepoints dev->name into dev->ifindex, and then let perf-script convert this to the interface name. Either in userspace via if_indextoname(3), or (as ACME pointed out at the time) we might want to have a lookup table stored together with perf.data for later inspection (in-case ifindexes changed).
Quoted 10 lines
Signed-off-by Stephen Hemminger <[email protected]>--- include/trace/events/net.h | 115 +++++++++++++++++++++++++++++++++++++ net/core/dev.c | 9 ++- 2 files changed, 123 insertions(+), 1 deletion(-)include/trace/events/net.h +0
-0
[...]
Quoted 5 lines
include/trace/events/net.h +5
-0+TRACE_EVENT(net_dev_notifier_entry, + + TP_PROTO(const struct netdev_notifier_info *info, unsigned long val), + + TP_ARGS(info, val),
include/trace/events/net.h line 100
Quoted 10 lines
include/trace/events/net.h +10
-0+ +TP_STRUCT__entry ( + __string( name, info->dev->name ) + __field( enum netdev_cmd, event ) + ), + +TP_fast_assign ( + __assign_str(name, info->dev->name); + __entry->event = val; + ),
These __string and __assign_str are costly and behind the scenes does a strcpy.
Quoted 18 lines
include/trace/events/net.h +18
-0+ +TP_printk ("dev=%s event=%s" , +__get_str (name),netdev_event_type (__entry->event)) +); + +TRACE_EVENT(net_dev_notifier, + + TP_PROTO(const struct netdev_notifier_info *info, int rc, unsigned long val), + + TP_ARGS(info, rc, val), + + TP_STRUCT__entry( + __string( name, info->dev->name ) + __field( enum netdev_cmd, event ) + __field( int, rc ) + ), + + TP_fast_assign(
include/trace/events/net.h line 128
Quoted 10 lines
include/trace/events/net.h +10
-0+__assign_str (name, info->dev->name); + __entry->event = val; + __entry->rc = rc; + ), + +TP_printk ("dev=%s event=%s ret=%d" , +__get_str (name),netdev_event_type (__entry->event), + __entry->rc) +); +
The bare minimum change is to _also_ add (info->dev->)ifindex to the tracepoint, as this makes it usable from BPF.
Signature · 4 lines
Best regards, Jesper Dangaard Brouer MSc.CS, Principal Kernel Engineer at Red Hat LinkedIn: http://www.linkedin.com/in/brouer
Quoted 6 lines · On Wed, 19 Dec 2018 08:36:43 +0100 Jesper Dangaard Brouer <[email protected]>…
Quoted 2 lines · On Tue, 18 Dec 2018 18:27:06 -0800 Stephen Hemminger…
This is the result of a conversation about monitoring of link state changes with BPF.
Quoted 8 lines
If you want to use this from BPF then you are in for a surprise. As tracepoints BPF cannot read these "
__string" constructs, here the netdev name. I tried a lot of different tricks that didn't work, see [1], until Alexei explained that it simply isn't supported.I instead recommend adding the ifindex to the tracepoint.
The__stringand__assign_stris also a performance concern as it does strcpy behind your back.
Can we record the ifindex in the event record and do the decode in the printk?
This is not a critical path so don't really care that much.
Quoted 13 lines
I have an year old TODO list item about improving this:
** TODO Make perf-script plugin for ifindex to name translationSCHEDULED: <2017-11-20 Mon>Today, the existing network tracepoints using
dev->nameis not that usable by BPF, as BPF cannot identify the interface. Thus IMHO it would make sense to convert the existing network tracepointsdev->nameintodev->ifindex, and then let perf-script convert this to the interface name. Either in userspace viaif_indextoname(3), or (as ACME pointed out at the time) we might want to have a lookup table stored together with perf.data for later inspection (in-case ifindexes changed).
Quoted 1 line
What about the event enum, can BPF take that?
Also want to add net namespace create/destroy events.
Quoted 19 lines · Jesper Dangaard Brouer
Quoted 2 lines · On Tue, 18 Dec 2018 18:27:06 -0800 Stephen Hemminger…
This is the result of a conversation about monitoring of link state changes with BPF.
If you want to use this from BPF then you are in for a surprise. As tracepoints BPF cannot read these "
__string" constructs, here the netdev name. I tried a lot of different tricks that didn't work, see [1], until Alexei explained that it simply isn't supported.I instead recommend adding the ifindex to the tracepoint.
The__stringand__assign_stris also a performance concern as it does strcpy behind your back.I have an year old TODO list item about improving this:
** TODO Make perf-script plugin for ifindex to name translationSCHEDULED: <2017-11-20 Mon>
Quoted 7 lines
Today, the existing network tracepoints using
dev->nameis not that usable by BPF, as BPF cannot identify the interface. Thus IMHO it would make sense to convert the existing network tracepointsdev->nameintodev->ifindex, and then let perf-script convert this to the interface name. Either in userspace viaif_indextoname(3), or (as ACME pointed out at the time) we might want to have a lookup table stored together with perf.data for later inspection (in-case ifindexes changed).
Hmm, why not just doing something as in your example below with napi_poll() where you pass in the napi pointer, and then use bpf_probe_read_str() on ctx->dev for fetching the name? At least there this should work and should be okay given it's rather slow-path event.
Thanks,
Daniel
Quoted 64 lines
Quoted 10 lines
Signed-off-by Stephen Hemminger <[email protected]>--- include/trace/events/net.h | 115 +++++++++++++++++++++++++++++++++++++ net/core/dev.c | 9 ++- 2 files changed, 123 insertions(+), 1 deletion(-)include/trace/events/net.h +0
-0[...]
Quoted 5 lines
include/trace/events/net.h +5
-0+TRACE_EVENT(net_dev_notifier_entry, + + TP_PROTO(const struct netdev_notifier_info *info, unsigned long val), + + TP_ARGS(info, val),include/trace/events/net.h line 100
Quoted 10 lines
include/trace/events/net.h +10
-0+ +TP_STRUCT__entry ( + __string( name, info->dev->name ) + __field( enum netdev_cmd, event ) + ), + +TP_fast_assign ( + __assign_str(name, info->dev->name); + __entry->event = val; + ),These
__stringand__assign_strare costly and behind the scenes does a strcpy.Quoted 18 lines
include/trace/events/net.h +18
-0+ +TP_printk ("dev=%s event=%s" , +__get_str (name),netdev_event_type (__entry->event)) +); + +TRACE_EVENT(net_dev_notifier, + + TP_PROTO(const struct netdev_notifier_info *info, int rc, unsigned long val), + + TP_ARGS(info, rc, val), + + TP_STRUCT__entry( + __string( name, info->dev->name ) + __field( enum netdev_cmd, event ) + __field( int, rc ) + ), + + TP_fast_assign(include/trace/events/net.h line 128
Quoted 10 lines
include/trace/events/net.h +10
-0+__assign_str (name, info->dev->name); + __entry->event = val; + __entry->rc = rc; + ), + +TP_printk ("dev=%s event=%s ret=%d" , +__get_str (name),netdev_event_type (__entry->event), + __entry->rc) +); +The bare minimum change is to _also_ add (info->dev->)ifindex to the tracepoint, as this makes it usable from BPF.
Quoted 6 lines · On Wed, 19 Dec 2018 16:46:05 +0100 Daniel Borkmann <[email protected]>…
Hmm, why not just doing something as in your example below with
napi_poll()where you pass in the napi pointer, and then usebpf_probe_read_str()onctx->devfor fetching the name? At least there this should work and should be okay given it's rather slow-path event.
I didn't try to use bpf_probe_read_str() in [1], but that is also not what I want in my use-case. I don't want the name, but the ifindex to filter on, as it will be faster. My use-case is allowing my napi_monitor program to filter on a specific net_device, inside the kernel via BPF.
E.g. this didn't work:
bpf_probe_read(&ifindex, 4, &ctx->napi->dev->ifindex);
Perhaps you know how I can do this deref correctly?
My napi_monitor use-case is not a slow-path event, even-though in optimal cases we should handle 64 packets per tracepoint invocation, but I'm using this for 100G NICs with >20Mpps. And I mostly use the tool when something looks wrong and I don't see 64 packet bulks, which is also why I detect when this gets invoked from idle task or from ksoftirqd.
Signature · 4 lines
Best regards, Jesper Dangaard Brouer MSc.CS, Principal Kernel Engineer at Red Hat LinkedIn: http://www.linkedin.com/in/brouer
Steven Rostedt
maintainerQuoted 7 lines · On Wed, 19 Dec 2018 17:40:49 +0100 Jesper Dangaard Brouer <[email protected]>…
My
napi_monitoruse-case is not a slow-path event, even-though in optimal cases we should handle 64 packets per tracepoint invocation, but I'm using this for 100G NICs with >20Mpps. And I mostly use the tool when something looks wrong and I don't see 64 packet bulks, which is also why I detect when this gets invoked from idle task or from ksoftirqd.
Would it be possible to add an interface that just connects to the tracepoint without having to use the trace event itself (the trace event is what shows up in tracefs/events/*/*).
That would give you full control of what you want:
void my_probe(void *data, const struct netdev_notifier_info *info,
unsigned long val)
{
do_something_with(info->dev->ifindex);
}void setup(void) {
register_trace_netdev_notifier_entry(my_probe, data);
}
That allows you to attach a function to the tracepoint and not worry about the overhead of what the trace event gives you.
-- Steve
Quoted 10 lines · Jesper Dangaard Brouer
Quoted 6 lines · On Wed, 19 Dec 2018 16:46:05 +0100 Daniel Borkmann <[email protected]>…
Hmm, why not just doing something as in your example below with
napi_poll()where you pass in the napi pointer, and then usebpf_probe_read_str()onctx->devfor fetching the name? At least there this should work and should be okay given it's rather slow-path event.
Quoted 8 lines
I didn't try to use
bpf_probe_read_str()in [1], but that is also not what I want in my use-case. I don't want the name, but the ifindex to filter on, as it will be faster. My use-case is allowing mynapi_monitorprogram to filter on a specificnet_device, inside the kernel via BPF.E.g. this didn't work:
bpf_probe_read(&ifindex, 4, &ctx->napi->dev->ifindex);
Something along the lines of this you could try:
#define probe_fetch(X) ({typeof(X) val; bpf_probe_read(&val, sizeof(val), &X); val;})
SEC("tracepoint/napi/napi_poll")
int napi_poll(struct napi_poll_ctx *ctx)
{
struct napi_struct *napi = ctx->napi;
struct net_device *dev;
int ifindex;
[...]
dev = probe_fetch(napi->dev);
ifindex = probe_fetch(dev->ifindex);
[...]
}Quoted 9 lines
Perhaps you know how I can do this deref correctly?
My
napi_monitoruse-case is not a slow-path event, even-though in optimal cases we should handle 64 packets per tracepoint invocation, but I'm using this for 100G NICs with >20Mpps. And I mostly use the tool when something looks wrong and I don't see 64 packet bulks, which is also why I detect when this gets invoked from idle task or from ksoftirqd.
Steven Rostedt
maintainerQuoted 10 lines · On Wed, 19 Dec 2018 08:36:43 +0100 Jesper Dangaard Brouer <[email protected]>…
Quoted 10 lines
include/trace/events/net.h +10
-0+TRACE_EVENT(net_dev_notifier_entry, + + TP_PROTO(const struct netdev_notifier_info *info, unsigned long val), + + TP_ARGS(info, val), + + TP_STRUCT__entry( + __string( name, info->dev->name ) + __field( enum netdev_cmd, event ) + ),
Quoted 8 lines
include/trace/events/net.h line 100
Quoted 5 lines
include/trace/events/net.h +5
-0+ +TP_fast_assign ( + __assign_str(name, info->dev->name); + __entry->event = val; + ),These
__stringand__assign_strare costly and behind the scenes does a strcpy.
True. But you could also make this into a memcpy with:
__array( char, name, IFNAMSIZ)
And in TP_fast_assign:
memcpy(__entry->name, info->dev->name, IFNAMSIZ);
And for the TP_printk:
"dev=%s", __entry->name
Yes, ifindex is still faster, but this does give you the name in a way that I think even BPF can use it. I also believe that memcopy on a constant is faster than a strcpy.
-- Steve
Quoted 8 lines · Jesper Dangaard Brouer
Today, the existing network tracepoints using
dev->nameis not that usable by BPF, as BPF cannot identify the interface. Thus IMHO it would make sense to convert the existing network tracepointsdev->nameintodev->ifindex, and then let perf-script convert this to the interface name. Either in userspace viaif_indextoname(3), or (as ACME pointed out at the time) we might want to have a lookup table stored together with perf.data for later inspection (in-case ifindexes changed).
You need network namespace references as well and the ability to monitor device notifications.