1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | |
3 | #include <linux/ethtool_netlink.h> |
4 | #include <net/udp_tunnel.h> |
5 | #include <net/vxlan.h> |
6 | |
7 | #include "bitset.h" |
8 | #include "common.h" |
9 | #include "netlink.h" |
10 | |
11 | const struct nla_policy ethnl_tunnel_info_get_policy[] = { |
12 | [ETHTOOL_A_TUNNEL_INFO_HEADER] = |
13 | NLA_POLICY_NESTED(ethnl_header_policy), |
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 | |
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(val: &types, NULL, nbits: __ETHTOOL_UDP_TUNNEL_TYPE_CNT, |
26 | names: udp_tunnel_type_names, compact); |
27 | if (size < 0) |
28 | return size; |
29 | |
30 | return size + |
31 | nla_total_size(payload: 0) + /* _UDP_TABLE */ |
32 | nla_total_size(payload: sizeof(u32)); /* _UDP_TABLE_SIZE */ |
33 | } |
34 | |
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; |
42 | ssize_t ret; |
43 | size_t size; |
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(payload: 0); /* _INFO_UDP_PORTS */ |
53 | |
54 | for (i = 0; i < UDP_TUNNEL_NIC_MAX_TABLES; i++) { |
55 | if (!info->tables[i].n_entries) |
56 | break; |
57 | |
58 | ret = ethnl_udp_table_reply_size(types: info->tables[i].tunnel_types, |
59 | compact); |
60 | if (ret < 0) |
61 | return ret; |
62 | size += ret; |
63 | |
64 | size += udp_tunnel_nic_dump_size(dev: req_base->dev, table: i); |
65 | } |
66 | |
67 | if (info->flags & UDP_TUNNEL_NIC_INFO_STATIC_IANA_VXLAN) { |
68 | ret = ethnl_udp_table_reply_size(types: 0, compact); |
69 | if (ret < 0) |
70 | return ret; |
71 | size += ret; |
72 | |
73 | size += nla_total_size(payload: 0) + /* _TABLE_ENTRY */ |
74 | nla_total_size(payload: sizeof(__be16)) + /* _ENTRY_PORT */ |
75 | nla_total_size(payload: sizeof(u32)); /* _ENTRY_TYPE */ |
76 | } |
77 | |
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; |
87 | struct nlattr *ports, *table, *entry; |
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, attrtype: 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, attrtype: ETHTOOL_A_TUNNEL_UDP_TABLE); |
103 | if (!table) |
104 | goto err_cancel_ports; |
105 | |
106 | if (nla_put_u32(skb, attrtype: ETHTOOL_A_TUNNEL_UDP_TABLE_SIZE, |
107 | value: info->tables[i].n_entries)) |
108 | goto err_cancel_table; |
109 | |
110 | if (ethnl_put_bitset32(skb, attrtype: ETHTOOL_A_TUNNEL_UDP_TABLE_TYPES, |
111 | val: &info->tables[i].tunnel_types, NULL, |
112 | nbits: __ETHTOOL_UDP_TUNNEL_TYPE_CNT, |
113 | names: udp_tunnel_type_names, compact)) |
114 | goto err_cancel_table; |
115 | |
116 | if (udp_tunnel_nic_dump_write(dev: req_base->dev, table: i, skb)) |
117 | goto err_cancel_table; |
118 | |
119 | nla_nest_end(skb, start: table); |
120 | } |
121 | |
122 | if (info->flags & UDP_TUNNEL_NIC_INFO_STATIC_IANA_VXLAN) { |
123 | u32 zero = 0; |
124 | |
125 | table = nla_nest_start(skb, attrtype: ETHTOOL_A_TUNNEL_UDP_TABLE); |
126 | if (!table) |
127 | goto err_cancel_ports; |
128 | |
129 | if (nla_put_u32(skb, attrtype: ETHTOOL_A_TUNNEL_UDP_TABLE_SIZE, value: 1)) |
130 | goto err_cancel_table; |
131 | |
132 | if (ethnl_put_bitset32(skb, attrtype: ETHTOOL_A_TUNNEL_UDP_TABLE_TYPES, |
133 | val: &zero, NULL, |
134 | nbits: __ETHTOOL_UDP_TUNNEL_TYPE_CNT, |
135 | names: udp_tunnel_type_names, compact)) |
136 | goto err_cancel_table; |
137 | |
138 | entry = nla_nest_start(skb, attrtype: ETHTOOL_A_TUNNEL_UDP_TABLE_ENTRY); |
139 | if (!entry) |
140 | goto err_cancel_entry; |
141 | |
142 | if (nla_put_be16(skb, attrtype: ETHTOOL_A_TUNNEL_UDP_ENTRY_PORT, |
143 | htons(IANA_VXLAN_UDP_PORT)) || |
144 | nla_put_u32(skb, attrtype: ETHTOOL_A_TUNNEL_UDP_ENTRY_TYPE, |
145 | ilog2(UDP_TUNNEL_TYPE_VXLAN))) |
146 | goto err_cancel_entry; |
147 | |
148 | nla_nest_end(skb, start: entry); |
149 | nla_nest_end(skb, start: table); |
150 | } |
151 | |
152 | nla_nest_end(skb, start: ports); |
153 | |
154 | return 0; |
155 | |
156 | err_cancel_entry: |
157 | nla_nest_cancel(skb, start: entry); |
158 | err_cancel_table: |
159 | nla_nest_cancel(skb, start: table); |
160 | err_cancel_ports: |
161 | nla_nest_cancel(skb, start: ports); |
162 | return -EMSGSIZE; |
163 | } |
164 | |
165 | int ethnl_tunnel_info_doit(struct sk_buff *skb, struct genl_info *info) |
166 | { |
167 | struct ethnl_req_info req_info = {}; |
168 | struct nlattr **tb = info->attrs; |
169 | struct sk_buff *rskb; |
170 | void *reply_payload; |
171 | int reply_len; |
172 | int ret; |
173 | |
174 | ret = ethnl_parse_header_dev_get(req_info: &req_info, |
175 | nest: tb[ETHTOOL_A_TUNNEL_INFO_HEADER], |
176 | net: genl_info_net(info), extack: info->extack, |
177 | require_dev: true); |
178 | if (ret < 0) |
179 | return ret; |
180 | |
181 | rtnl_lock(); |
182 | ret = ethnl_tunnel_info_reply_size(req_base: &req_info, extack: 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(payload: reply_len, dev: req_info.dev, |
188 | cmd: ETHTOOL_MSG_TUNNEL_INFO_GET_REPLY, |
189 | hdr_attrtype: ETHTOOL_A_TUNNEL_INFO_HEADER, |
190 | info, ehdrp: &reply_payload); |
191 | if (!rskb) { |
192 | ret = -ENOMEM; |
193 | goto err_unlock_rtnl; |
194 | } |
195 | |
196 | ret = ethnl_tunnel_info_fill_reply(req_base: &req_info, skb: rskb); |
197 | if (ret) |
198 | goto err_free_msg; |
199 | rtnl_unlock(); |
200 | ethnl_parse_header_dev_put(req_info: &req_info); |
201 | genlmsg_end(skb: rskb, hdr: reply_payload); |
202 | |
203 | return genlmsg_reply(skb: rskb, info); |
204 | |
205 | err_free_msg: |
206 | nlmsg_free(skb: rskb); |
207 | err_unlock_rtnl: |
208 | rtnl_unlock(); |
209 | ethnl_parse_header_dev_put(req_info: &req_info); |
210 | return ret; |
211 | } |
212 | |
213 | struct ethnl_tunnel_info_dump_ctx { |
214 | struct ethnl_req_info req_info; |
215 | unsigned long ifindex; |
216 | }; |
217 | |
218 | int ethnl_tunnel_info_start(struct netlink_callback *cb) |
219 | { |
220 | const struct genl_dumpit_info *info = genl_dumpit_info(cb); |
221 | struct ethnl_tunnel_info_dump_ctx *ctx = (void *)cb->ctx; |
222 | struct nlattr **tb = info->info.attrs; |
223 | int ret; |
224 | |
225 | BUILD_BUG_ON(sizeof(*ctx) > sizeof(cb->ctx)); |
226 | |
227 | memset(ctx, 0, sizeof(*ctx)); |
228 | |
229 | ret = ethnl_parse_header_dev_get(req_info: &ctx->req_info, |
230 | nest: tb[ETHTOOL_A_TUNNEL_INFO_HEADER], |
231 | net: sock_net(sk: cb->skb->sk), extack: cb->extack, |
232 | require_dev: false); |
233 | if (ctx->req_info.dev) { |
234 | ethnl_parse_header_dev_put(req_info: &ctx->req_info); |
235 | ctx->req_info.dev = NULL; |
236 | } |
237 | |
238 | return ret; |
239 | } |
240 | |
241 | int ethnl_tunnel_info_dumpit(struct sk_buff *skb, struct netlink_callback *cb) |
242 | { |
243 | struct ethnl_tunnel_info_dump_ctx *ctx = (void *)cb->ctx; |
244 | struct net *net = sock_net(sk: skb->sk); |
245 | struct net_device *dev; |
246 | int ret = 0; |
247 | void *ehdr; |
248 | |
249 | rtnl_lock(); |
250 | for_each_netdev_dump(net, dev, ctx->ifindex) { |
251 | ehdr = ethnl_dump_put(skb, cb, |
252 | cmd: ETHTOOL_MSG_TUNNEL_INFO_GET_REPLY); |
253 | if (!ehdr) { |
254 | ret = -EMSGSIZE; |
255 | break; |
256 | } |
257 | |
258 | ret = ethnl_fill_reply_header(skb, dev, |
259 | attrtype: ETHTOOL_A_TUNNEL_INFO_HEADER); |
260 | if (ret < 0) { |
261 | genlmsg_cancel(skb, hdr: ehdr); |
262 | break; |
263 | } |
264 | |
265 | ctx->req_info.dev = dev; |
266 | ret = ethnl_tunnel_info_fill_reply(req_base: &ctx->req_info, skb); |
267 | ctx->req_info.dev = NULL; |
268 | if (ret < 0) { |
269 | genlmsg_cancel(skb, hdr: ehdr); |
270 | if (ret == -EOPNOTSUPP) |
271 | continue; |
272 | break; |
273 | } |
274 | genlmsg_end(skb, hdr: ehdr); |
275 | } |
276 | rtnl_unlock(); |
277 | |
278 | if (ret == -EMSGSIZE && skb->len) |
279 | return skb->len; |
280 | return ret; |
281 | } |
282 | |