// SPDX-License-Identifier: GPL-2.0-only
/*
* Monitoring code for network dropped packet alerts
*
* Copyright (C) 2009 Neil Horman <nhorman@tuxdriver.com>
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/netdevice.h>
#include <linux/etherdevice.h>
#include <linux/string.h>
#include <linux/if_arp.h>
#include <linux/inetdevice.h>
#include <linux/inet.h>
#include <linux/interrupt.h>
#include <linux/netpoll.h>
#include <linux/sched.h>
#include <linux/delay.h>
#include <linux/types.h>
#include <linux/workqueue.h>
#include <linux/netlink.h>
#include <linux/net_dropmon.h>
#include <linux/percpu.h>
#include <linux/timer.h>
#include <linux/bitops.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <net/drop_monitor.h>
#include <net/genetlink.h>
#include <net/netevent.h>
#include <trace/events/skb.h>
#include <trace/events/napi.h>
#include <asm/unaligned.h>
#define TRACE_ON 1
#define TRACE_OFF 0
/*
* Globals, our netlink socket pointer
* and the work handle that will send up
* netlink alerts
*/
static int trace_state = TRACE_OFF;
static bool monitor_hw;
/* net_dm_mutex
*
* An overall lock guarding every operation coming from userspace.
* It also guards the global 'hw_stats_list' list.
*/
static DEFINE_MUTEX(net_dm_mutex);
struct net_dm_stats {
u64 dropped;
struct u64_stats_sync syncp;
};
struct per_cpu_dm_data {
spinlock_t lock; /* Protects 'skb' and 'send_timer' */
struct sk_buff *skb;
struct sk_buff_head drop_queue;
struct work_struct dm_alert_work;
struct timer_list send_timer;
struct net_dm_stats stats;
};
struct dm_hw_stat_delta {
struct net_device *dev;
unsigned long last_rx;
struct list_head list;
struct rcu_head rcu;
unsigned long last_drop_val;
};
static struct genl_family net_drop_monitor_family;
static DEFINE_PER_CPU(struct per_cpu_dm_data, dm_cpu_data);
static DEFINE_PER_CPU(struct per_cpu_dm_data, dm_hw_cpu_data);
static int dm_hit_limit = 64;
static int dm_delay = 1;
static unsigned long dm_hw_check_delta = 2*HZ;
static LIST_HEAD(hw_stats_list);
static enum net_dm_alert_mode net_dm_alert_mode = NET_DM_ALERT_MODE_SUMMARY;
static u32 net_dm_trunc_len;
static u32 net_dm_queue_len = 1000;
struct net_dm_alert_ops {
void (*kfree_skb_probe)(void *ignore, struct sk_buff *skb,
void *location);
void (*napi_poll_probe)(void *ignore, struct napi_struct *napi,
int work, int budget);
void (*work_item_func)(struct work_struct *work);
void (*hw_probe)(struct sk_buff *skb,
const struct net_dm_hw_metadata *hw_metadata);
};
struct net_dm_skb_cb {
void *pc;
};
#define NET_DM_SKB_CB(__skb) ((struct net_dm_skb_cb *)&((__skb)->cb[0]))
static struct sk_buff *reset_per_cpu_data(struct per_cpu_dm_data *data)
{
size_t al;
struct net_dm_alert_msg *msg;
struct nlattr *nla;
struct sk_buff *skb;
unsigned long flags;
void *msg_header;
al = sizeof(struct net_dm_alert_msg);
al += dm_hit_limit * sizeof(struct net_dm_drop_point);
al += sizeof(struct nlattr);
skb = genlmsg_new(al, GFP_KERNEL);
if (!skb)
goto err;
msg_header = genlmsg_put(skb, 0, 0, &net_drop_monitor_family,
0, NET_DM_CMD_ALERT);
if (!msg_header) {
nlmsg_free(skb);
skb = NULL;
goto err;
}
nla = nla_reserve(skb, NLA_UNSPEC,
sizeof(struct net_dm_alert_msg));
if (!nla) {
nlmsg_free(skb);
skb = NULL;
goto err;
}
msg = nla_data(nla);
memset(msg, 0, al);
goto out;
err:
mod_timer(&data->send_timer, jiffies + HZ / 10);
out:
spin_lock_irqsave(&data->lock, flags);
swap(data->skb, skb);
spin_unlock_irqrestore(&data->lock, flags);
if (skb) {
struct nlmsghdr *nlh = (struct nlmsghdr *)skb->data;
struct genlmsghdr *gnlh = (struct genlmsghdr *)nlmsg_data(nlh);
genlmsg_end(skb, genlmsg_data(gnlh));
}
return skb;
}
static const struct genl_multicast_group dropmon_mcgrps[] = {
{ .name = "events", },
};
static void send_dm_alert(struct work_struct *work)
{
struct sk_buff *skb;
struct per_cpu_dm_data *data;
data = container_of(work, struct per_cpu_dm_data, dm_alert_work);
skb = reset_per_cpu_data(data);
if (skb)
genlmsg_multicast(&net_drop_monitor_family, skb, 0,
0, GFP_KERNEL);
}
/*
* This is the timer function to delay the sending of an alert
* in the event that more drops will arrive during the
* hysteresis period.
*/
static void sched_send_work(struct timer_list