xref: /openbmc/linux/net/psample/psample.c (revision f1fd20c3)
16ae0a628SYotam Gigi /*
26ae0a628SYotam Gigi  * net/psample/psample.c - Netlink channel for packet sampling
36ae0a628SYotam Gigi  * Copyright (c) 2017 Yotam Gigi <yotamg@mellanox.com>
46ae0a628SYotam Gigi  *
56ae0a628SYotam Gigi  * This program is free software; you can redistribute it and/or modify
66ae0a628SYotam Gigi  * it under the terms of the GNU General Public License version 2 as
76ae0a628SYotam Gigi  * published by the Free Software Foundation.
86ae0a628SYotam Gigi  */
96ae0a628SYotam Gigi 
106ae0a628SYotam Gigi #include <linux/types.h>
116ae0a628SYotam Gigi #include <linux/kernel.h>
126ae0a628SYotam Gigi #include <linux/skbuff.h>
136ae0a628SYotam Gigi #include <linux/module.h>
146ae0a628SYotam Gigi #include <net/net_namespace.h>
156ae0a628SYotam Gigi #include <net/sock.h>
166ae0a628SYotam Gigi #include <net/netlink.h>
176ae0a628SYotam Gigi #include <net/genetlink.h>
186ae0a628SYotam Gigi #include <net/psample.h>
196ae0a628SYotam Gigi #include <linux/spinlock.h>
206ae0a628SYotam Gigi 
216ae0a628SYotam Gigi #define PSAMPLE_MAX_PACKET_SIZE 0xffff
226ae0a628SYotam Gigi 
236ae0a628SYotam Gigi static LIST_HEAD(psample_groups_list);
246ae0a628SYotam Gigi static DEFINE_SPINLOCK(psample_groups_lock);
256ae0a628SYotam Gigi 
266ae0a628SYotam Gigi /* multicast groups */
276ae0a628SYotam Gigi enum psample_nl_multicast_groups {
286ae0a628SYotam Gigi 	PSAMPLE_NL_MCGRP_CONFIG,
296ae0a628SYotam Gigi 	PSAMPLE_NL_MCGRP_SAMPLE,
306ae0a628SYotam Gigi };
316ae0a628SYotam Gigi 
326ae0a628SYotam Gigi static const struct genl_multicast_group psample_nl_mcgrps[] = {
336ae0a628SYotam Gigi 	[PSAMPLE_NL_MCGRP_CONFIG] = { .name = PSAMPLE_NL_MCGRP_CONFIG_NAME },
346ae0a628SYotam Gigi 	[PSAMPLE_NL_MCGRP_SAMPLE] = { .name = PSAMPLE_NL_MCGRP_SAMPLE_NAME },
356ae0a628SYotam Gigi };
366ae0a628SYotam Gigi 
376ae0a628SYotam Gigi static struct genl_family psample_nl_family __ro_after_init;
386ae0a628SYotam Gigi 
396ae0a628SYotam Gigi static int psample_group_nl_fill(struct sk_buff *msg,
406ae0a628SYotam Gigi 				 struct psample_group *group,
416ae0a628SYotam Gigi 				 enum psample_command cmd, u32 portid, u32 seq,
426ae0a628SYotam Gigi 				 int flags)
436ae0a628SYotam Gigi {
446ae0a628SYotam Gigi 	void *hdr;
456ae0a628SYotam Gigi 	int ret;
466ae0a628SYotam Gigi 
476ae0a628SYotam Gigi 	hdr = genlmsg_put(msg, portid, seq, &psample_nl_family, flags, cmd);
486ae0a628SYotam Gigi 	if (!hdr)
496ae0a628SYotam Gigi 		return -EMSGSIZE;
506ae0a628SYotam Gigi 
516ae0a628SYotam Gigi 	ret = nla_put_u32(msg, PSAMPLE_ATTR_SAMPLE_GROUP, group->group_num);
526ae0a628SYotam Gigi 	if (ret < 0)
536ae0a628SYotam Gigi 		goto error;
546ae0a628SYotam Gigi 
556ae0a628SYotam Gigi 	ret = nla_put_u32(msg, PSAMPLE_ATTR_GROUP_REFCOUNT, group->refcount);
566ae0a628SYotam Gigi 	if (ret < 0)
576ae0a628SYotam Gigi 		goto error;
586ae0a628SYotam Gigi 
596ae0a628SYotam Gigi 	ret = nla_put_u32(msg, PSAMPLE_ATTR_GROUP_SEQ, group->seq);
606ae0a628SYotam Gigi 	if (ret < 0)
616ae0a628SYotam Gigi 		goto error;
626ae0a628SYotam Gigi 
636ae0a628SYotam Gigi 	genlmsg_end(msg, hdr);
646ae0a628SYotam Gigi 	return 0;
656ae0a628SYotam Gigi 
666ae0a628SYotam Gigi error:
676ae0a628SYotam Gigi 	genlmsg_cancel(msg, hdr);
686ae0a628SYotam Gigi 	return -EMSGSIZE;
696ae0a628SYotam Gigi }
706ae0a628SYotam Gigi 
716ae0a628SYotam Gigi static int psample_nl_cmd_get_group_dumpit(struct sk_buff *msg,
726ae0a628SYotam Gigi 					   struct netlink_callback *cb)
736ae0a628SYotam Gigi {
746ae0a628SYotam Gigi 	struct psample_group *group;
756ae0a628SYotam Gigi 	int start = cb->args[0];
766ae0a628SYotam Gigi 	int idx = 0;
776ae0a628SYotam Gigi 	int err;
786ae0a628SYotam Gigi 
796ae0a628SYotam Gigi 	spin_lock(&psample_groups_lock);
806ae0a628SYotam Gigi 	list_for_each_entry(group, &psample_groups_list, list) {
816ae0a628SYotam Gigi 		if (!net_eq(group->net, sock_net(msg->sk)))
826ae0a628SYotam Gigi 			continue;
836ae0a628SYotam Gigi 		if (idx < start) {
846ae0a628SYotam Gigi 			idx++;
856ae0a628SYotam Gigi 			continue;
866ae0a628SYotam Gigi 		}
876ae0a628SYotam Gigi 		err = psample_group_nl_fill(msg, group, PSAMPLE_CMD_NEW_GROUP,
886ae0a628SYotam Gigi 					    NETLINK_CB(cb->skb).portid,
896ae0a628SYotam Gigi 					    cb->nlh->nlmsg_seq, NLM_F_MULTI);
906ae0a628SYotam Gigi 		if (err)
916ae0a628SYotam Gigi 			break;
926ae0a628SYotam Gigi 		idx++;
936ae0a628SYotam Gigi 	}
946ae0a628SYotam Gigi 
956ae0a628SYotam Gigi 	spin_unlock(&psample_groups_lock);
966ae0a628SYotam Gigi 	cb->args[0] = idx;
976ae0a628SYotam Gigi 	return msg->len;
986ae0a628SYotam Gigi }
996ae0a628SYotam Gigi 
1006ae0a628SYotam Gigi static const struct genl_ops psample_nl_ops[] = {
1016ae0a628SYotam Gigi 	{
1026ae0a628SYotam Gigi 		.cmd = PSAMPLE_CMD_GET_GROUP,
1036ae0a628SYotam Gigi 		.dumpit = psample_nl_cmd_get_group_dumpit,
1046ae0a628SYotam Gigi 		/* can be retrieved by unprivileged users */
1056ae0a628SYotam Gigi 	}
1066ae0a628SYotam Gigi };
1076ae0a628SYotam Gigi 
1086ae0a628SYotam Gigi static struct genl_family psample_nl_family __ro_after_init = {
1096ae0a628SYotam Gigi 	.name		= PSAMPLE_GENL_NAME,
1106ae0a628SYotam Gigi 	.version	= PSAMPLE_GENL_VERSION,
1116ae0a628SYotam Gigi 	.maxattr	= PSAMPLE_ATTR_MAX,
1126ae0a628SYotam Gigi 	.netnsok	= true,
1136ae0a628SYotam Gigi 	.module		= THIS_MODULE,
1146ae0a628SYotam Gigi 	.mcgrps		= psample_nl_mcgrps,
1156ae0a628SYotam Gigi 	.ops		= psample_nl_ops,
1166ae0a628SYotam Gigi 	.n_ops		= ARRAY_SIZE(psample_nl_ops),
1176ae0a628SYotam Gigi 	.n_mcgrps	= ARRAY_SIZE(psample_nl_mcgrps),
1186ae0a628SYotam Gigi };
1196ae0a628SYotam Gigi 
1206ae0a628SYotam Gigi static void psample_group_notify(struct psample_group *group,
1216ae0a628SYotam Gigi 				 enum psample_command cmd)
1226ae0a628SYotam Gigi {
1236ae0a628SYotam Gigi 	struct sk_buff *msg;
1246ae0a628SYotam Gigi 	int err;
1256ae0a628SYotam Gigi 
1266ae0a628SYotam Gigi 	msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_ATOMIC);
1276ae0a628SYotam Gigi 	if (!msg)
1286ae0a628SYotam Gigi 		return;
1296ae0a628SYotam Gigi 
1306ae0a628SYotam Gigi 	err = psample_group_nl_fill(msg, group, cmd, 0, 0, NLM_F_MULTI);
1316ae0a628SYotam Gigi 	if (!err)
1326ae0a628SYotam Gigi 		genlmsg_multicast_netns(&psample_nl_family, group->net, msg, 0,
1336ae0a628SYotam Gigi 					PSAMPLE_NL_MCGRP_CONFIG, GFP_ATOMIC);
1346ae0a628SYotam Gigi 	else
1356ae0a628SYotam Gigi 		nlmsg_free(msg);
1366ae0a628SYotam Gigi }
1376ae0a628SYotam Gigi 
1386ae0a628SYotam Gigi static struct psample_group *psample_group_create(struct net *net,
1396ae0a628SYotam Gigi 						  u32 group_num)
1406ae0a628SYotam Gigi {
1416ae0a628SYotam Gigi 	struct psample_group *group;
1426ae0a628SYotam Gigi 
1436ae0a628SYotam Gigi 	group = kzalloc(sizeof(*group), GFP_ATOMIC);
1446ae0a628SYotam Gigi 	if (!group)
1456ae0a628SYotam Gigi 		return NULL;
1466ae0a628SYotam Gigi 
1476ae0a628SYotam Gigi 	group->net = net;
1486ae0a628SYotam Gigi 	group->group_num = group_num;
1496ae0a628SYotam Gigi 	list_add_tail(&group->list, &psample_groups_list);
1506ae0a628SYotam Gigi 
1516ae0a628SYotam Gigi 	psample_group_notify(group, PSAMPLE_CMD_NEW_GROUP);
1526ae0a628SYotam Gigi 	return group;
1536ae0a628SYotam Gigi }
1546ae0a628SYotam Gigi 
1556ae0a628SYotam Gigi static void psample_group_destroy(struct psample_group *group)
1566ae0a628SYotam Gigi {
1576ae0a628SYotam Gigi 	psample_group_notify(group, PSAMPLE_CMD_DEL_GROUP);
1586ae0a628SYotam Gigi 	list_del(&group->list);
1596ae0a628SYotam Gigi 	kfree(group);
1606ae0a628SYotam Gigi }
1616ae0a628SYotam Gigi 
1626ae0a628SYotam Gigi static struct psample_group *
1636ae0a628SYotam Gigi psample_group_lookup(struct net *net, u32 group_num)
1646ae0a628SYotam Gigi {
1656ae0a628SYotam Gigi 	struct psample_group *group;
1666ae0a628SYotam Gigi 
1676ae0a628SYotam Gigi 	list_for_each_entry(group, &psample_groups_list, list)
1686ae0a628SYotam Gigi 		if ((group->group_num == group_num) && (group->net == net))
1696ae0a628SYotam Gigi 			return group;
1706ae0a628SYotam Gigi 	return NULL;
1716ae0a628SYotam Gigi }
1726ae0a628SYotam Gigi 
1736ae0a628SYotam Gigi struct psample_group *psample_group_get(struct net *net, u32 group_num)
1746ae0a628SYotam Gigi {
1756ae0a628SYotam Gigi 	struct psample_group *group;
1766ae0a628SYotam Gigi 
1776ae0a628SYotam Gigi 	spin_lock(&psample_groups_lock);
1786ae0a628SYotam Gigi 
1796ae0a628SYotam Gigi 	group = psample_group_lookup(net, group_num);
1806ae0a628SYotam Gigi 	if (!group) {
1816ae0a628SYotam Gigi 		group = psample_group_create(net, group_num);
1826ae0a628SYotam Gigi 		if (!group)
1836ae0a628SYotam Gigi 			goto out;
1846ae0a628SYotam Gigi 	}
1856ae0a628SYotam Gigi 	group->refcount++;
1866ae0a628SYotam Gigi 
1876ae0a628SYotam Gigi out:
1886ae0a628SYotam Gigi 	spin_unlock(&psample_groups_lock);
1896ae0a628SYotam Gigi 	return group;
1906ae0a628SYotam Gigi }
1916ae0a628SYotam Gigi EXPORT_SYMBOL_GPL(psample_group_get);
1926ae0a628SYotam Gigi 
1936ae0a628SYotam Gigi void psample_group_put(struct psample_group *group)
1946ae0a628SYotam Gigi {
1956ae0a628SYotam Gigi 	spin_lock(&psample_groups_lock);
1966ae0a628SYotam Gigi 
1976ae0a628SYotam Gigi 	if (--group->refcount == 0)
1986ae0a628SYotam Gigi 		psample_group_destroy(group);
1996ae0a628SYotam Gigi 
2006ae0a628SYotam Gigi 	spin_unlock(&psample_groups_lock);
2016ae0a628SYotam Gigi }
2026ae0a628SYotam Gigi EXPORT_SYMBOL_GPL(psample_group_put);
2036ae0a628SYotam Gigi 
2046ae0a628SYotam Gigi void psample_sample_packet(struct psample_group *group, struct sk_buff *skb,
2056ae0a628SYotam Gigi 			   u32 trunc_size, int in_ifindex, int out_ifindex,
2066ae0a628SYotam Gigi 			   u32 sample_rate)
2076ae0a628SYotam Gigi {
2086ae0a628SYotam Gigi 	struct sk_buff *nl_skb;
2096ae0a628SYotam Gigi 	int data_len;
2106ae0a628SYotam Gigi 	int meta_len;
2116ae0a628SYotam Gigi 	void *data;
2126ae0a628SYotam Gigi 	int ret;
2136ae0a628SYotam Gigi 
2146ae0a628SYotam Gigi 	meta_len = (in_ifindex ? nla_total_size(sizeof(u16)) : 0) +
2156ae0a628SYotam Gigi 		   (out_ifindex ? nla_total_size(sizeof(u16)) : 0) +
2166ae0a628SYotam Gigi 		   nla_total_size(sizeof(u32)) +	/* sample_rate */
2176ae0a628SYotam Gigi 		   nla_total_size(sizeof(u32)) +	/* orig_size */
2186ae0a628SYotam Gigi 		   nla_total_size(sizeof(u32)) +	/* group_num */
2196ae0a628SYotam Gigi 		   nla_total_size(sizeof(u32));		/* seq */
2206ae0a628SYotam Gigi 
2216ae0a628SYotam Gigi 	data_len = min(skb->len, trunc_size);
2226ae0a628SYotam Gigi 	if (meta_len + nla_total_size(data_len) > PSAMPLE_MAX_PACKET_SIZE)
2236ae0a628SYotam Gigi 		data_len = PSAMPLE_MAX_PACKET_SIZE - meta_len - NLA_HDRLEN
2246ae0a628SYotam Gigi 			    - NLA_ALIGNTO;
2256ae0a628SYotam Gigi 
2266ae0a628SYotam Gigi 	nl_skb = genlmsg_new(meta_len + data_len, GFP_ATOMIC);
2276ae0a628SYotam Gigi 	if (unlikely(!nl_skb))
2286ae0a628SYotam Gigi 		return;
2296ae0a628SYotam Gigi 
2306ae0a628SYotam Gigi 	data = genlmsg_put(nl_skb, 0, 0, &psample_nl_family, 0,
2316ae0a628SYotam Gigi 			   PSAMPLE_CMD_SAMPLE);
2326ae0a628SYotam Gigi 	if (unlikely(!data))
2336ae0a628SYotam Gigi 		goto error;
2346ae0a628SYotam Gigi 
2356ae0a628SYotam Gigi 	if (in_ifindex) {
2366ae0a628SYotam Gigi 		ret = nla_put_u16(nl_skb, PSAMPLE_ATTR_IIFINDEX, in_ifindex);
2376ae0a628SYotam Gigi 		if (unlikely(ret < 0))
2386ae0a628SYotam Gigi 			goto error;
2396ae0a628SYotam Gigi 	}
2406ae0a628SYotam Gigi 
2416ae0a628SYotam Gigi 	if (out_ifindex) {
2426ae0a628SYotam Gigi 		ret = nla_put_u16(nl_skb, PSAMPLE_ATTR_OIFINDEX, out_ifindex);
2436ae0a628SYotam Gigi 		if (unlikely(ret < 0))
2446ae0a628SYotam Gigi 			goto error;
2456ae0a628SYotam Gigi 	}
2466ae0a628SYotam Gigi 
2476ae0a628SYotam Gigi 	ret = nla_put_u32(nl_skb, PSAMPLE_ATTR_SAMPLE_RATE, sample_rate);
2486ae0a628SYotam Gigi 	if (unlikely(ret < 0))
2496ae0a628SYotam Gigi 		goto error;
2506ae0a628SYotam Gigi 
2516ae0a628SYotam Gigi 	ret = nla_put_u32(nl_skb, PSAMPLE_ATTR_ORIGSIZE, skb->len);
2526ae0a628SYotam Gigi 	if (unlikely(ret < 0))
2536ae0a628SYotam Gigi 		goto error;
2546ae0a628SYotam Gigi 
2556ae0a628SYotam Gigi 	ret = nla_put_u32(nl_skb, PSAMPLE_ATTR_SAMPLE_GROUP, group->group_num);
2566ae0a628SYotam Gigi 	if (unlikely(ret < 0))
2576ae0a628SYotam Gigi 		goto error;
2586ae0a628SYotam Gigi 
2596ae0a628SYotam Gigi 	ret = nla_put_u32(nl_skb, PSAMPLE_ATTR_GROUP_SEQ, group->seq++);
2606ae0a628SYotam Gigi 	if (unlikely(ret < 0))
2616ae0a628SYotam Gigi 		goto error;
2626ae0a628SYotam Gigi 
2636ae0a628SYotam Gigi 	if (data_len) {
2646ae0a628SYotam Gigi 		int nla_len = nla_total_size(data_len);
2656ae0a628SYotam Gigi 		struct nlattr *nla;
2666ae0a628SYotam Gigi 
2674df864c1SJohannes Berg 		nla = skb_put(nl_skb, nla_len);
2686ae0a628SYotam Gigi 		nla->nla_type = PSAMPLE_ATTR_DATA;
2696ae0a628SYotam Gigi 		nla->nla_len = nla_attr_size(data_len);
2706ae0a628SYotam Gigi 
2716ae0a628SYotam Gigi 		if (skb_copy_bits(skb, 0, nla_data(nla), data_len))
2726ae0a628SYotam Gigi 			goto error;
2736ae0a628SYotam Gigi 	}
2746ae0a628SYotam Gigi 
2756ae0a628SYotam Gigi 	genlmsg_end(nl_skb, data);
2766ae0a628SYotam Gigi 	genlmsg_multicast_netns(&psample_nl_family, group->net, nl_skb, 0,
2776ae0a628SYotam Gigi 				PSAMPLE_NL_MCGRP_SAMPLE, GFP_ATOMIC);
2786ae0a628SYotam Gigi 
2796ae0a628SYotam Gigi 	return;
2806ae0a628SYotam Gigi error:
2816ae0a628SYotam Gigi 	pr_err_ratelimited("Could not create psample log message\n");
2826ae0a628SYotam Gigi 	nlmsg_free(nl_skb);
2836ae0a628SYotam Gigi }
2846ae0a628SYotam Gigi EXPORT_SYMBOL_GPL(psample_sample_packet);
2856ae0a628SYotam Gigi 
2866ae0a628SYotam Gigi static int __init psample_module_init(void)
2876ae0a628SYotam Gigi {
2886ae0a628SYotam Gigi 	return genl_register_family(&psample_nl_family);
2896ae0a628SYotam Gigi }
2906ae0a628SYotam Gigi 
2916ae0a628SYotam Gigi static void __exit psample_module_exit(void)
2926ae0a628SYotam Gigi {
2936ae0a628SYotam Gigi 	genl_unregister_family(&psample_nl_family);
2946ae0a628SYotam Gigi }
2956ae0a628SYotam Gigi 
2966ae0a628SYotam Gigi module_init(psample_module_init);
2976ae0a628SYotam Gigi module_exit(psample_module_exit);
2986ae0a628SYotam Gigi 
299f1fd20c3SYotam Gigi MODULE_AUTHOR("Yotam Gigi <yotam.gi@gmail.com>");
3006ae0a628SYotam Gigi MODULE_DESCRIPTION("netlink channel for packet sampling");
3016ae0a628SYotam Gigi MODULE_LICENSE("GPL v2");
302