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