xref: /openbmc/linux/net/ethtool/tunnels.c (revision c900529f3d9161bfde5cca0754f83b4d3c3e0220)
1c7d759ebSJakub Kicinski // SPDX-License-Identifier: GPL-2.0-only
2c7d759ebSJakub Kicinski 
3c7d759ebSJakub Kicinski #include <linux/ethtool_netlink.h>
4c7d759ebSJakub Kicinski #include <net/udp_tunnel.h>
5966e5059SJakub Kicinski #include <net/vxlan.h>
6c7d759ebSJakub Kicinski 
7c7d759ebSJakub Kicinski #include "bitset.h"
8c7d759ebSJakub Kicinski #include "common.h"
9c7d759ebSJakub Kicinski #include "netlink.h"
10c7d759ebSJakub Kicinski 
11ff419afaSJakub Kicinski const struct nla_policy ethnl_tunnel_info_get_policy[] = {
12329d9c33SJakub Kicinski 	[ETHTOOL_A_TUNNEL_INFO_HEADER]		=
13329d9c33SJakub Kicinski 		NLA_POLICY_NESTED(ethnl_header_policy),
14c7d759ebSJakub Kicinski };
15c7d759ebSJakub Kicinski 
16c7d759ebSJakub Kicinski static_assert(ETHTOOL_UDP_TUNNEL_TYPE_VXLAN == ilog2(UDP_TUNNEL_TYPE_VXLAN));
17c7d759ebSJakub Kicinski static_assert(ETHTOOL_UDP_TUNNEL_TYPE_GENEVE == ilog2(UDP_TUNNEL_TYPE_GENEVE));
18c7d759ebSJakub Kicinski static_assert(ETHTOOL_UDP_TUNNEL_TYPE_VXLAN_GPE ==
19c7d759ebSJakub Kicinski 	      ilog2(UDP_TUNNEL_TYPE_VXLAN_GPE));
20c7d759ebSJakub Kicinski 
ethnl_udp_table_reply_size(unsigned int types,bool compact)21966e5059SJakub Kicinski static ssize_t ethnl_udp_table_reply_size(unsigned int types, bool compact)
22966e5059SJakub Kicinski {
23966e5059SJakub Kicinski 	ssize_t size;
24966e5059SJakub Kicinski 
25966e5059SJakub Kicinski 	size = ethnl_bitset32_size(&types, NULL, __ETHTOOL_UDP_TUNNEL_TYPE_CNT,
26966e5059SJakub Kicinski 				   udp_tunnel_type_names, compact);
27966e5059SJakub Kicinski 	if (size < 0)
28966e5059SJakub Kicinski 		return size;
29966e5059SJakub Kicinski 
30966e5059SJakub Kicinski 	return size +
31966e5059SJakub Kicinski 		nla_total_size(0) + /* _UDP_TABLE */
32966e5059SJakub Kicinski 		nla_total_size(sizeof(u32)); /* _UDP_TABLE_SIZE */
33966e5059SJakub Kicinski }
34966e5059SJakub Kicinski 
35c7d759ebSJakub Kicinski static ssize_t
ethnl_tunnel_info_reply_size(const struct ethnl_req_info * req_base,struct netlink_ext_ack * extack)36c7d759ebSJakub Kicinski ethnl_tunnel_info_reply_size(const struct ethnl_req_info *req_base,
37c7d759ebSJakub Kicinski 			     struct netlink_ext_ack *extack)
38c7d759ebSJakub Kicinski {
39c7d759ebSJakub Kicinski 	bool compact = req_base->flags & ETHTOOL_FLAG_COMPACT_BITSETS;
40c7d759ebSJakub Kicinski 	const struct udp_tunnel_nic_info *info;
41c7d759ebSJakub Kicinski 	unsigned int i;
42966e5059SJakub Kicinski 	ssize_t ret;
43c7d759ebSJakub Kicinski 	size_t size;
44c7d759ebSJakub Kicinski 
45c7d759ebSJakub Kicinski 	info = req_base->dev->udp_tunnel_nic_info;
46c7d759ebSJakub Kicinski 	if (!info) {
47c7d759ebSJakub Kicinski 		NL_SET_ERR_MSG(extack,
48c7d759ebSJakub Kicinski 			       "device does not report tunnel offload info");
49c7d759ebSJakub Kicinski 		return -EOPNOTSUPP;
50c7d759ebSJakub Kicinski 	}
51c7d759ebSJakub Kicinski 
52c7d759ebSJakub Kicinski 	size =	nla_total_size(0); /* _INFO_UDP_PORTS */
53c7d759ebSJakub Kicinski 
54c7d759ebSJakub Kicinski 	for (i = 0; i < UDP_TUNNEL_NIC_MAX_TABLES; i++) {
55c7d759ebSJakub Kicinski 		if (!info->tables[i].n_entries)
56966e5059SJakub Kicinski 			break;
57c7d759ebSJakub Kicinski 
58966e5059SJakub Kicinski 		ret = ethnl_udp_table_reply_size(info->tables[i].tunnel_types,
59966e5059SJakub Kicinski 						 compact);
60c7d759ebSJakub Kicinski 		if (ret < 0)
61c7d759ebSJakub Kicinski 			return ret;
62c7d759ebSJakub Kicinski 		size += ret;
63c7d759ebSJakub Kicinski 
64c7d759ebSJakub Kicinski 		size += udp_tunnel_nic_dump_size(req_base->dev, i);
65c7d759ebSJakub Kicinski 	}
66c7d759ebSJakub Kicinski 
67966e5059SJakub Kicinski 	if (info->flags & UDP_TUNNEL_NIC_INFO_STATIC_IANA_VXLAN) {
68966e5059SJakub Kicinski 		ret = ethnl_udp_table_reply_size(0, compact);
69966e5059SJakub Kicinski 		if (ret < 0)
70966e5059SJakub Kicinski 			return ret;
71966e5059SJakub Kicinski 		size += ret;
72966e5059SJakub Kicinski 
73966e5059SJakub Kicinski 		size += nla_total_size(0) +		 /* _TABLE_ENTRY */
74966e5059SJakub Kicinski 			nla_total_size(sizeof(__be16)) + /* _ENTRY_PORT */
75966e5059SJakub Kicinski 			nla_total_size(sizeof(u32));	 /* _ENTRY_TYPE */
76966e5059SJakub Kicinski 	}
77966e5059SJakub Kicinski 
78c7d759ebSJakub Kicinski 	return size;
79c7d759ebSJakub Kicinski }
80c7d759ebSJakub Kicinski 
81c7d759ebSJakub Kicinski static int
ethnl_tunnel_info_fill_reply(const struct ethnl_req_info * req_base,struct sk_buff * skb)82c7d759ebSJakub Kicinski ethnl_tunnel_info_fill_reply(const struct ethnl_req_info *req_base,
83c7d759ebSJakub Kicinski 			     struct sk_buff *skb)
84c7d759ebSJakub Kicinski {
85c7d759ebSJakub Kicinski 	bool compact = req_base->flags & ETHTOOL_FLAG_COMPACT_BITSETS;
86c7d759ebSJakub Kicinski 	const struct udp_tunnel_nic_info *info;
87966e5059SJakub Kicinski 	struct nlattr *ports, *table, *entry;
88c7d759ebSJakub Kicinski 	unsigned int i;
89c7d759ebSJakub Kicinski 
90c7d759ebSJakub Kicinski 	info = req_base->dev->udp_tunnel_nic_info;
91c7d759ebSJakub Kicinski 	if (!info)
92c7d759ebSJakub Kicinski 		return -EOPNOTSUPP;
93c7d759ebSJakub Kicinski 
94c7d759ebSJakub Kicinski 	ports = nla_nest_start(skb, ETHTOOL_A_TUNNEL_INFO_UDP_PORTS);
95c7d759ebSJakub Kicinski 	if (!ports)
96c7d759ebSJakub Kicinski 		return -EMSGSIZE;
97c7d759ebSJakub Kicinski 
98c7d759ebSJakub Kicinski 	for (i = 0; i < UDP_TUNNEL_NIC_MAX_TABLES; i++) {
99c7d759ebSJakub Kicinski 		if (!info->tables[i].n_entries)
100c7d759ebSJakub Kicinski 			break;
101c7d759ebSJakub Kicinski 
102c7d759ebSJakub Kicinski 		table = nla_nest_start(skb, ETHTOOL_A_TUNNEL_UDP_TABLE);
103c7d759ebSJakub Kicinski 		if (!table)
104c7d759ebSJakub Kicinski 			goto err_cancel_ports;
105c7d759ebSJakub Kicinski 
106c7d759ebSJakub Kicinski 		if (nla_put_u32(skb, ETHTOOL_A_TUNNEL_UDP_TABLE_SIZE,
107c7d759ebSJakub Kicinski 				info->tables[i].n_entries))
108c7d759ebSJakub Kicinski 			goto err_cancel_table;
109c7d759ebSJakub Kicinski 
110c7d759ebSJakub Kicinski 		if (ethnl_put_bitset32(skb, ETHTOOL_A_TUNNEL_UDP_TABLE_TYPES,
111c7d759ebSJakub Kicinski 				       &info->tables[i].tunnel_types, NULL,
112c7d759ebSJakub Kicinski 				       __ETHTOOL_UDP_TUNNEL_TYPE_CNT,
113c7d759ebSJakub Kicinski 				       udp_tunnel_type_names, compact))
114c7d759ebSJakub Kicinski 			goto err_cancel_table;
115c7d759ebSJakub Kicinski 
116c7d759ebSJakub Kicinski 		if (udp_tunnel_nic_dump_write(req_base->dev, i, skb))
117c7d759ebSJakub Kicinski 			goto err_cancel_table;
118c7d759ebSJakub Kicinski 
119c7d759ebSJakub Kicinski 		nla_nest_end(skb, table);
120c7d759ebSJakub Kicinski 	}
121c7d759ebSJakub Kicinski 
122966e5059SJakub Kicinski 	if (info->flags & UDP_TUNNEL_NIC_INFO_STATIC_IANA_VXLAN) {
123966e5059SJakub Kicinski 		u32 zero = 0;
124966e5059SJakub Kicinski 
125966e5059SJakub Kicinski 		table = nla_nest_start(skb, ETHTOOL_A_TUNNEL_UDP_TABLE);
126966e5059SJakub Kicinski 		if (!table)
127966e5059SJakub Kicinski 			goto err_cancel_ports;
128966e5059SJakub Kicinski 
129966e5059SJakub Kicinski 		if (nla_put_u32(skb, ETHTOOL_A_TUNNEL_UDP_TABLE_SIZE, 1))
130966e5059SJakub Kicinski 			goto err_cancel_table;
131966e5059SJakub Kicinski 
132966e5059SJakub Kicinski 		if (ethnl_put_bitset32(skb, ETHTOOL_A_TUNNEL_UDP_TABLE_TYPES,
133966e5059SJakub Kicinski 				       &zero, NULL,
134966e5059SJakub Kicinski 				       __ETHTOOL_UDP_TUNNEL_TYPE_CNT,
135966e5059SJakub Kicinski 				       udp_tunnel_type_names, compact))
136966e5059SJakub Kicinski 			goto err_cancel_table;
137966e5059SJakub Kicinski 
138966e5059SJakub Kicinski 		entry = nla_nest_start(skb, ETHTOOL_A_TUNNEL_UDP_TABLE_ENTRY);
13905cd8238SLi Zhong 		if (!entry)
14005cd8238SLi Zhong 			goto err_cancel_entry;
141966e5059SJakub Kicinski 
142966e5059SJakub Kicinski 		if (nla_put_be16(skb, ETHTOOL_A_TUNNEL_UDP_ENTRY_PORT,
143966e5059SJakub Kicinski 				 htons(IANA_VXLAN_UDP_PORT)) ||
144966e5059SJakub Kicinski 		    nla_put_u32(skb, ETHTOOL_A_TUNNEL_UDP_ENTRY_TYPE,
145966e5059SJakub Kicinski 				ilog2(UDP_TUNNEL_TYPE_VXLAN)))
146966e5059SJakub Kicinski 			goto err_cancel_entry;
147966e5059SJakub Kicinski 
148966e5059SJakub Kicinski 		nla_nest_end(skb, entry);
149966e5059SJakub Kicinski 		nla_nest_end(skb, table);
150966e5059SJakub Kicinski 	}
151966e5059SJakub Kicinski 
152c7d759ebSJakub Kicinski 	nla_nest_end(skb, ports);
153c7d759ebSJakub Kicinski 
154c7d759ebSJakub Kicinski 	return 0;
155c7d759ebSJakub Kicinski 
156966e5059SJakub Kicinski err_cancel_entry:
157966e5059SJakub Kicinski 	nla_nest_cancel(skb, entry);
158c7d759ebSJakub Kicinski err_cancel_table:
159c7d759ebSJakub Kicinski 	nla_nest_cancel(skb, table);
160c7d759ebSJakub Kicinski err_cancel_ports:
161c7d759ebSJakub Kicinski 	nla_nest_cancel(skb, ports);
162c7d759ebSJakub Kicinski 	return -EMSGSIZE;
163c7d759ebSJakub Kicinski }
164c7d759ebSJakub Kicinski 
ethnl_tunnel_info_doit(struct sk_buff * skb,struct genl_info * info)165c7d759ebSJakub Kicinski int ethnl_tunnel_info_doit(struct sk_buff *skb, struct genl_info *info)
166c7d759ebSJakub Kicinski {
167c7d759ebSJakub Kicinski 	struct ethnl_req_info req_info = {};
1684f30974fSJakub Kicinski 	struct nlattr **tb = info->attrs;
169c7d759ebSJakub Kicinski 	struct sk_buff *rskb;
170c7d759ebSJakub Kicinski 	void *reply_payload;
171c7d759ebSJakub Kicinski 	int reply_len;
172c7d759ebSJakub Kicinski 	int ret;
173c7d759ebSJakub Kicinski 
1744f30974fSJakub Kicinski 	ret = ethnl_parse_header_dev_get(&req_info,
1754f30974fSJakub Kicinski 					 tb[ETHTOOL_A_TUNNEL_INFO_HEADER],
176c7d759ebSJakub Kicinski 					 genl_info_net(info), info->extack,
177c7d759ebSJakub Kicinski 					 true);
178c7d759ebSJakub Kicinski 	if (ret < 0)
179c7d759ebSJakub Kicinski 		return ret;
180c7d759ebSJakub Kicinski 
181c7d759ebSJakub Kicinski 	rtnl_lock();
182c7d759ebSJakub Kicinski 	ret = ethnl_tunnel_info_reply_size(&req_info, info->extack);
183c7d759ebSJakub Kicinski 	if (ret < 0)
184c7d759ebSJakub Kicinski 		goto err_unlock_rtnl;
185c7d759ebSJakub Kicinski 	reply_len = ret + ethnl_reply_header_size();
186c7d759ebSJakub Kicinski 
187c7d759ebSJakub Kicinski 	rskb = ethnl_reply_init(reply_len, req_info.dev,
18819a83d36SMichal Kubecek 				ETHTOOL_MSG_TUNNEL_INFO_GET_REPLY,
189c7d759ebSJakub Kicinski 				ETHTOOL_A_TUNNEL_INFO_HEADER,
190c7d759ebSJakub Kicinski 				info, &reply_payload);
191c7d759ebSJakub Kicinski 	if (!rskb) {
192c7d759ebSJakub Kicinski 		ret = -ENOMEM;
193c7d759ebSJakub Kicinski 		goto err_unlock_rtnl;
194c7d759ebSJakub Kicinski 	}
195c7d759ebSJakub Kicinski 
196c7d759ebSJakub Kicinski 	ret = ethnl_tunnel_info_fill_reply(&req_info, rskb);
197c7d759ebSJakub Kicinski 	if (ret)
198c7d759ebSJakub Kicinski 		goto err_free_msg;
199c7d759ebSJakub Kicinski 	rtnl_unlock();
20034ac17ecSEric Dumazet 	ethnl_parse_header_dev_put(&req_info);
201c7d759ebSJakub Kicinski 	genlmsg_end(rskb, reply_payload);
202c7d759ebSJakub Kicinski 
203c7d759ebSJakub Kicinski 	return genlmsg_reply(rskb, info);
204c7d759ebSJakub Kicinski 
205c7d759ebSJakub Kicinski err_free_msg:
206c7d759ebSJakub Kicinski 	nlmsg_free(rskb);
207c7d759ebSJakub Kicinski err_unlock_rtnl:
208c7d759ebSJakub Kicinski 	rtnl_unlock();
20934ac17ecSEric Dumazet 	ethnl_parse_header_dev_put(&req_info);
210c7d759ebSJakub Kicinski 	return ret;
211c7d759ebSJakub Kicinski }
212c7d759ebSJakub Kicinski 
213c7d759ebSJakub Kicinski struct ethnl_tunnel_info_dump_ctx {
214c7d759ebSJakub Kicinski 	struct ethnl_req_info	req_info;
21584e00d9bSJakub Kicinski 	unsigned long		ifindex;
216c7d759ebSJakub Kicinski };
217c7d759ebSJakub Kicinski 
ethnl_tunnel_info_start(struct netlink_callback * cb)218c7d759ebSJakub Kicinski int ethnl_tunnel_info_start(struct netlink_callback *cb)
219c7d759ebSJakub Kicinski {
2204f30974fSJakub Kicinski 	const struct genl_dumpit_info *info = genl_dumpit_info(cb);
221c7d759ebSJakub Kicinski 	struct ethnl_tunnel_info_dump_ctx *ctx = (void *)cb->ctx;
222*7288dd2fSJakub Kicinski 	struct nlattr **tb = info->info.attrs;
223c7d759ebSJakub Kicinski 	int ret;
224c7d759ebSJakub Kicinski 
225c7d759ebSJakub Kicinski 	BUILD_BUG_ON(sizeof(*ctx) > sizeof(cb->ctx));
226c7d759ebSJakub Kicinski 
227c7d759ebSJakub Kicinski 	memset(ctx, 0, sizeof(*ctx));
228c7d759ebSJakub Kicinski 
2294f30974fSJakub Kicinski 	ret = ethnl_parse_header_dev_get(&ctx->req_info,
2304f30974fSJakub Kicinski 					 tb[ETHTOOL_A_TUNNEL_INFO_HEADER],
231c7d759ebSJakub Kicinski 					 sock_net(cb->skb->sk), cb->extack,
232c7d759ebSJakub Kicinski 					 false);
233c7d759ebSJakub Kicinski 	if (ctx->req_info.dev) {
23434ac17ecSEric Dumazet 		ethnl_parse_header_dev_put(&ctx->req_info);
235c7d759ebSJakub Kicinski 		ctx->req_info.dev = NULL;
236c7d759ebSJakub Kicinski 	}
237c7d759ebSJakub Kicinski 
238c7d759ebSJakub Kicinski 	return ret;
239c7d759ebSJakub Kicinski }
240c7d759ebSJakub Kicinski 
ethnl_tunnel_info_dumpit(struct sk_buff * skb,struct netlink_callback * cb)241c7d759ebSJakub Kicinski int ethnl_tunnel_info_dumpit(struct sk_buff *skb, struct netlink_callback *cb)
242c7d759ebSJakub Kicinski {
243c7d759ebSJakub Kicinski 	struct ethnl_tunnel_info_dump_ctx *ctx = (void *)cb->ctx;
244c7d759ebSJakub Kicinski 	struct net *net = sock_net(skb->sk);
24584e00d9bSJakub Kicinski 	struct net_device *dev;
246c7d759ebSJakub Kicinski 	int ret = 0;
247c7d759ebSJakub Kicinski 	void *ehdr;
248c7d759ebSJakub Kicinski 
249c7d759ebSJakub Kicinski 	rtnl_lock();
25084e00d9bSJakub Kicinski 	for_each_netdev_dump(net, dev, ctx->ifindex) {
251c7d759ebSJakub Kicinski 		ehdr = ethnl_dump_put(skb, cb,
25219a83d36SMichal Kubecek 				      ETHTOOL_MSG_TUNNEL_INFO_GET_REPLY);
253c7d759ebSJakub Kicinski 		if (!ehdr) {
254c7d759ebSJakub Kicinski 			ret = -EMSGSIZE;
25584e00d9bSJakub Kicinski 			break;
256c7d759ebSJakub Kicinski 		}
257c7d759ebSJakub Kicinski 
25884e00d9bSJakub Kicinski 		ret = ethnl_fill_reply_header(skb, dev,
25984e00d9bSJakub Kicinski 					      ETHTOOL_A_TUNNEL_INFO_HEADER);
260c7d759ebSJakub Kicinski 		if (ret < 0) {
261c7d759ebSJakub Kicinski 			genlmsg_cancel(skb, ehdr);
26284e00d9bSJakub Kicinski 			break;
263c7d759ebSJakub Kicinski 		}
264c7d759ebSJakub Kicinski 
265c7d759ebSJakub Kicinski 		ctx->req_info.dev = dev;
266c7d759ebSJakub Kicinski 		ret = ethnl_tunnel_info_fill_reply(&ctx->req_info, skb);
267c7d759ebSJakub Kicinski 		ctx->req_info.dev = NULL;
268c7d759ebSJakub Kicinski 		if (ret < 0) {
269c7d759ebSJakub Kicinski 			genlmsg_cancel(skb, ehdr);
270c7d759ebSJakub Kicinski 			if (ret == -EOPNOTSUPP)
27184e00d9bSJakub Kicinski 				continue;
27284e00d9bSJakub Kicinski 			break;
273c7d759ebSJakub Kicinski 		}
274c7d759ebSJakub Kicinski 		genlmsg_end(skb, ehdr);
275c7d759ebSJakub Kicinski 	}
276c7d759ebSJakub Kicinski 	rtnl_unlock();
277c7d759ebSJakub Kicinski 
278c7d759ebSJakub Kicinski 	if (ret == -EMSGSIZE && skb->len)
279c7d759ebSJakub Kicinski 		return skb->len;
280c7d759ebSJakub Kicinski 	return ret;
281c7d759ebSJakub Kicinski }
282