Commit | Line | Data |
---|---|---|
c7d759eb JK |
1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | ||
3 | #include <linux/ethtool_netlink.h> | |
4 | #include <net/udp_tunnel.h> | |
966e5059 | 5 | #include <net/vxlan.h> |
c7d759eb JK |
6 | |
7 | #include "bitset.h" | |
8 | #include "common.h" | |
9 | #include "netlink.h" | |
10 | ||
ff419afa | 11 | const struct nla_policy ethnl_tunnel_info_get_policy[] = { |
329d9c33 JK |
12 | [ETHTOOL_A_TUNNEL_INFO_HEADER] = |
13 | NLA_POLICY_NESTED(ethnl_header_policy), | |
c7d759eb JK |
14 | }; |
15 | ||
16 | static_assert(ETHTOOL_UDP_TUNNEL_TYPE_VXLAN == ilog2(UDP_TUNNEL_TYPE_VXLAN)); | |
17 | static_assert(ETHTOOL_UDP_TUNNEL_TYPE_GENEVE == ilog2(UDP_TUNNEL_TYPE_GENEVE)); | |
18 | static_assert(ETHTOOL_UDP_TUNNEL_TYPE_VXLAN_GPE == | |
19 | ilog2(UDP_TUNNEL_TYPE_VXLAN_GPE)); | |
20 | ||
966e5059 JK |
21 | static ssize_t ethnl_udp_table_reply_size(unsigned int types, bool compact) |
22 | { | |
23 | ssize_t size; | |
24 | ||
25 | size = ethnl_bitset32_size(&types, NULL, __ETHTOOL_UDP_TUNNEL_TYPE_CNT, | |
26 | udp_tunnel_type_names, compact); | |
27 | if (size < 0) | |
28 | return size; | |
29 | ||
30 | return size + | |
31 | nla_total_size(0) + /* _UDP_TABLE */ | |
32 | nla_total_size(sizeof(u32)); /* _UDP_TABLE_SIZE */ | |
33 | } | |
34 | ||
c7d759eb JK |
35 | static ssize_t |
36 | ethnl_tunnel_info_reply_size(const struct ethnl_req_info *req_base, | |
37 | struct netlink_ext_ack *extack) | |
38 | { | |
39 | bool compact = req_base->flags & ETHTOOL_FLAG_COMPACT_BITSETS; | |
40 | const struct udp_tunnel_nic_info *info; | |
41 | unsigned int i; | |
966e5059 | 42 | ssize_t ret; |
c7d759eb | 43 | size_t size; |
c7d759eb JK |
44 | |
45 | info = req_base->dev->udp_tunnel_nic_info; | |
46 | if (!info) { | |
47 | NL_SET_ERR_MSG(extack, | |
48 | "device does not report tunnel offload info"); | |
49 | return -EOPNOTSUPP; | |
50 | } | |
51 | ||
52 | size = nla_total_size(0); /* _INFO_UDP_PORTS */ | |
53 | ||
54 | for (i = 0; i < UDP_TUNNEL_NIC_MAX_TABLES; i++) { | |
55 | if (!info->tables[i].n_entries) | |
966e5059 | 56 | break; |
c7d759eb | 57 | |
966e5059 JK |
58 | ret = ethnl_udp_table_reply_size(info->tables[i].tunnel_types, |
59 | compact); | |
c7d759eb JK |
60 | if (ret < 0) |
61 | return ret; | |
62 | size += ret; | |
63 | ||
64 | size += udp_tunnel_nic_dump_size(req_base->dev, i); | |
65 | } | |
66 | ||
966e5059 JK |
67 | if (info->flags & UDP_TUNNEL_NIC_INFO_STATIC_IANA_VXLAN) { |
68 | ret = ethnl_udp_table_reply_size(0, compact); | |
69 | if (ret < 0) | |
70 | return ret; | |
71 | size += ret; | |
72 | ||
73 | size += nla_total_size(0) + /* _TABLE_ENTRY */ | |
74 | nla_total_size(sizeof(__be16)) + /* _ENTRY_PORT */ | |
75 | nla_total_size(sizeof(u32)); /* _ENTRY_TYPE */ | |
76 | } | |
77 | ||
c7d759eb JK |
78 | return size; |
79 | } | |
80 | ||
81 | static int | |
82 | ethnl_tunnel_info_fill_reply(const struct ethnl_req_info *req_base, | |
83 | struct sk_buff *skb) | |
84 | { | |
85 | bool compact = req_base->flags & ETHTOOL_FLAG_COMPACT_BITSETS; | |
86 | const struct udp_tunnel_nic_info *info; | |
966e5059 | 87 | struct nlattr *ports, *table, *entry; |
c7d759eb JK |
88 | unsigned int i; |
89 | ||
90 | info = req_base->dev->udp_tunnel_nic_info; | |
91 | if (!info) | |
92 | return -EOPNOTSUPP; | |
93 | ||
94 | ports = nla_nest_start(skb, ETHTOOL_A_TUNNEL_INFO_UDP_PORTS); | |
95 | if (!ports) | |
96 | return -EMSGSIZE; | |
97 | ||
98 | for (i = 0; i < UDP_TUNNEL_NIC_MAX_TABLES; i++) { | |
99 | if (!info->tables[i].n_entries) | |
100 | break; | |
101 | ||
102 | table = nla_nest_start(skb, ETHTOOL_A_TUNNEL_UDP_TABLE); | |
103 | if (!table) | |
104 | goto err_cancel_ports; | |
105 | ||
106 | if (nla_put_u32(skb, ETHTOOL_A_TUNNEL_UDP_TABLE_SIZE, | |
107 | info->tables[i].n_entries)) | |
108 | goto err_cancel_table; | |
109 | ||
110 | if (ethnl_put_bitset32(skb, ETHTOOL_A_TUNNEL_UDP_TABLE_TYPES, | |
111 | &info->tables[i].tunnel_types, NULL, | |
112 | __ETHTOOL_UDP_TUNNEL_TYPE_CNT, | |
113 | udp_tunnel_type_names, compact)) | |
114 | goto err_cancel_table; | |
115 | ||
116 | if (udp_tunnel_nic_dump_write(req_base->dev, i, skb)) | |
117 | goto err_cancel_table; | |
118 | ||
119 | nla_nest_end(skb, table); | |
120 | } | |
121 | ||
966e5059 JK |
122 | if (info->flags & UDP_TUNNEL_NIC_INFO_STATIC_IANA_VXLAN) { |
123 | u32 zero = 0; | |
124 | ||
125 | table = nla_nest_start(skb, ETHTOOL_A_TUNNEL_UDP_TABLE); | |
126 | if (!table) | |
127 | goto err_cancel_ports; | |
128 | ||
129 | if (nla_put_u32(skb, ETHTOOL_A_TUNNEL_UDP_TABLE_SIZE, 1)) | |
130 | goto err_cancel_table; | |
131 | ||
132 | if (ethnl_put_bitset32(skb, ETHTOOL_A_TUNNEL_UDP_TABLE_TYPES, | |
133 | &zero, NULL, | |
134 | __ETHTOOL_UDP_TUNNEL_TYPE_CNT, | |
135 | udp_tunnel_type_names, compact)) | |
136 | goto err_cancel_table; | |
137 | ||
138 | entry = nla_nest_start(skb, ETHTOOL_A_TUNNEL_UDP_TABLE_ENTRY); | |
05cd8238 LZ |
139 | if (!entry) |
140 | goto err_cancel_entry; | |
966e5059 JK |
141 | |
142 | if (nla_put_be16(skb, ETHTOOL_A_TUNNEL_UDP_ENTRY_PORT, | |
143 | htons(IANA_VXLAN_UDP_PORT)) || | |
144 | nla_put_u32(skb, ETHTOOL_A_TUNNEL_UDP_ENTRY_TYPE, | |
145 | ilog2(UDP_TUNNEL_TYPE_VXLAN))) | |
146 | goto err_cancel_entry; | |
147 | ||
148 | nla_nest_end(skb, entry); | |
149 | nla_nest_end(skb, table); | |
150 | } | |
151 | ||
c7d759eb JK |
152 | nla_nest_end(skb, ports); |
153 | ||
154 | return 0; | |
155 | ||
966e5059 JK |
156 | err_cancel_entry: |
157 | nla_nest_cancel(skb, entry); | |
c7d759eb JK |
158 | err_cancel_table: |
159 | nla_nest_cancel(skb, table); | |
160 | err_cancel_ports: | |
161 | nla_nest_cancel(skb, ports); | |
162 | return -EMSGSIZE; | |
163 | } | |
164 | ||
c7d759eb JK |
165 | int ethnl_tunnel_info_doit(struct sk_buff *skb, struct genl_info *info) |
166 | { | |
167 | struct ethnl_req_info req_info = {}; | |
4f30974f | 168 | struct nlattr **tb = info->attrs; |
c7d759eb JK |
169 | struct sk_buff *rskb; |
170 | void *reply_payload; | |
171 | int reply_len; | |
172 | int ret; | |
173 | ||
4f30974f JK |
174 | ret = ethnl_parse_header_dev_get(&req_info, |
175 | tb[ETHTOOL_A_TUNNEL_INFO_HEADER], | |
176 | genl_info_net(info), info->extack, | |
177 | true); | |
c7d759eb JK |
178 | if (ret < 0) |
179 | return ret; | |
180 | ||
181 | rtnl_lock(); | |
182 | ret = ethnl_tunnel_info_reply_size(&req_info, info->extack); | |
183 | if (ret < 0) | |
184 | goto err_unlock_rtnl; | |
185 | reply_len = ret + ethnl_reply_header_size(); | |
186 | ||
187 | rskb = ethnl_reply_init(reply_len, req_info.dev, | |
19a83d36 | 188 | ETHTOOL_MSG_TUNNEL_INFO_GET_REPLY, |
c7d759eb JK |
189 | ETHTOOL_A_TUNNEL_INFO_HEADER, |
190 | info, &reply_payload); | |
191 | if (!rskb) { | |
192 | ret = -ENOMEM; | |
193 | goto err_unlock_rtnl; | |
194 | } | |
195 | ||
196 | ret = ethnl_tunnel_info_fill_reply(&req_info, rskb); | |
197 | if (ret) | |
198 | goto err_free_msg; | |
199 | rtnl_unlock(); | |
34ac17ec | 200 | ethnl_parse_header_dev_put(&req_info); |
c7d759eb JK |
201 | genlmsg_end(rskb, reply_payload); |
202 | ||
203 | return genlmsg_reply(rskb, info); | |
204 | ||
205 | err_free_msg: | |
206 | nlmsg_free(rskb); | |
207 | err_unlock_rtnl: | |
208 | rtnl_unlock(); | |
34ac17ec | 209 | ethnl_parse_header_dev_put(&req_info); |
c7d759eb JK |
210 | return ret; |
211 | } | |
212 | ||
213 | struct ethnl_tunnel_info_dump_ctx { | |
214 | struct ethnl_req_info req_info; | |
215 | int pos_hash; | |
216 | int pos_idx; | |
217 | }; | |
218 | ||
219 | int ethnl_tunnel_info_start(struct netlink_callback *cb) | |
220 | { | |
4f30974f | 221 | const struct genl_dumpit_info *info = genl_dumpit_info(cb); |
c7d759eb | 222 | struct ethnl_tunnel_info_dump_ctx *ctx = (void *)cb->ctx; |
4f30974f | 223 | struct nlattr **tb = info->attrs; |
c7d759eb JK |
224 | int ret; |
225 | ||
226 | BUILD_BUG_ON(sizeof(*ctx) > sizeof(cb->ctx)); | |
227 | ||
228 | memset(ctx, 0, sizeof(*ctx)); | |
229 | ||
4f30974f JK |
230 | ret = ethnl_parse_header_dev_get(&ctx->req_info, |
231 | tb[ETHTOOL_A_TUNNEL_INFO_HEADER], | |
232 | sock_net(cb->skb->sk), cb->extack, | |
233 | false); | |
c7d759eb | 234 | if (ctx->req_info.dev) { |
34ac17ec | 235 | ethnl_parse_header_dev_put(&ctx->req_info); |
c7d759eb JK |
236 | ctx->req_info.dev = NULL; |
237 | } | |
238 | ||
239 | return ret; | |
240 | } | |
241 | ||
242 | int ethnl_tunnel_info_dumpit(struct sk_buff *skb, struct netlink_callback *cb) | |
243 | { | |
244 | struct ethnl_tunnel_info_dump_ctx *ctx = (void *)cb->ctx; | |
245 | struct net *net = sock_net(skb->sk); | |
246 | int s_idx = ctx->pos_idx; | |
247 | int h, idx = 0; | |
248 | int ret = 0; | |
249 | void *ehdr; | |
250 | ||
251 | rtnl_lock(); | |
252 | cb->seq = net->dev_base_seq; | |
253 | for (h = ctx->pos_hash; h < NETDEV_HASHENTRIES; h++, s_idx = 0) { | |
254 | struct hlist_head *head; | |
255 | struct net_device *dev; | |
256 | ||
257 | head = &net->dev_index_head[h]; | |
258 | idx = 0; | |
259 | hlist_for_each_entry(dev, head, index_hlist) { | |
260 | if (idx < s_idx) | |
261 | goto cont; | |
262 | ||
263 | ehdr = ethnl_dump_put(skb, cb, | |
19a83d36 | 264 | ETHTOOL_MSG_TUNNEL_INFO_GET_REPLY); |
c7d759eb JK |
265 | if (!ehdr) { |
266 | ret = -EMSGSIZE; | |
267 | goto out; | |
268 | } | |
269 | ||
270 | ret = ethnl_fill_reply_header(skb, dev, ETHTOOL_A_TUNNEL_INFO_HEADER); | |
271 | if (ret < 0) { | |
272 | genlmsg_cancel(skb, ehdr); | |
273 | goto out; | |
274 | } | |
275 | ||
276 | ctx->req_info.dev = dev; | |
277 | ret = ethnl_tunnel_info_fill_reply(&ctx->req_info, skb); | |
278 | ctx->req_info.dev = NULL; | |
279 | if (ret < 0) { | |
280 | genlmsg_cancel(skb, ehdr); | |
281 | if (ret == -EOPNOTSUPP) | |
282 | goto cont; | |
283 | goto out; | |
284 | } | |
285 | genlmsg_end(skb, ehdr); | |
286 | cont: | |
287 | idx++; | |
288 | } | |
289 | } | |
290 | out: | |
291 | rtnl_unlock(); | |
292 | ||
293 | ctx->pos_hash = h; | |
294 | ctx->pos_idx = idx; | |
295 | nl_dump_check_consistent(cb, nlmsg_hdr(skb)); | |
296 | ||
297 | if (ret == -EMSGSIZE && skb->len) | |
298 | return skb->len; | |
299 | return ret; | |
300 | } |