tunnels.c (7196B)
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 11const struct nla_policy ethnl_tunnel_info_get_policy[] = { 12 [ETHTOOL_A_TUNNEL_INFO_HEADER] = 13 NLA_POLICY_NESTED(ethnl_header_policy), 14}; 15 16static_assert(ETHTOOL_UDP_TUNNEL_TYPE_VXLAN == ilog2(UDP_TUNNEL_TYPE_VXLAN)); 17static_assert(ETHTOOL_UDP_TUNNEL_TYPE_GENEVE == ilog2(UDP_TUNNEL_TYPE_GENEVE)); 18static_assert(ETHTOOL_UDP_TUNNEL_TYPE_VXLAN_GPE == 19 ilog2(UDP_TUNNEL_TYPE_VXLAN_GPE)); 20 21static 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 35static ssize_t 36ethnl_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(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(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(req_base->dev, i); 65 } 66 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 78 return size; 79} 80 81static int 82ethnl_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, 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 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); 139 140 if (nla_put_be16(skb, ETHTOOL_A_TUNNEL_UDP_ENTRY_PORT, 141 htons(IANA_VXLAN_UDP_PORT)) || 142 nla_put_u32(skb, ETHTOOL_A_TUNNEL_UDP_ENTRY_TYPE, 143 ilog2(UDP_TUNNEL_TYPE_VXLAN))) 144 goto err_cancel_entry; 145 146 nla_nest_end(skb, entry); 147 nla_nest_end(skb, table); 148 } 149 150 nla_nest_end(skb, ports); 151 152 return 0; 153 154err_cancel_entry: 155 nla_nest_cancel(skb, entry); 156err_cancel_table: 157 nla_nest_cancel(skb, table); 158err_cancel_ports: 159 nla_nest_cancel(skb, ports); 160 return -EMSGSIZE; 161} 162 163int ethnl_tunnel_info_doit(struct sk_buff *skb, struct genl_info *info) 164{ 165 struct ethnl_req_info req_info = {}; 166 struct nlattr **tb = info->attrs; 167 struct sk_buff *rskb; 168 void *reply_payload; 169 int reply_len; 170 int ret; 171 172 ret = ethnl_parse_header_dev_get(&req_info, 173 tb[ETHTOOL_A_TUNNEL_INFO_HEADER], 174 genl_info_net(info), info->extack, 175 true); 176 if (ret < 0) 177 return ret; 178 179 rtnl_lock(); 180 ret = ethnl_tunnel_info_reply_size(&req_info, info->extack); 181 if (ret < 0) 182 goto err_unlock_rtnl; 183 reply_len = ret + ethnl_reply_header_size(); 184 185 rskb = ethnl_reply_init(reply_len, req_info.dev, 186 ETHTOOL_MSG_TUNNEL_INFO_GET_REPLY, 187 ETHTOOL_A_TUNNEL_INFO_HEADER, 188 info, &reply_payload); 189 if (!rskb) { 190 ret = -ENOMEM; 191 goto err_unlock_rtnl; 192 } 193 194 ret = ethnl_tunnel_info_fill_reply(&req_info, rskb); 195 if (ret) 196 goto err_free_msg; 197 rtnl_unlock(); 198 ethnl_parse_header_dev_put(&req_info); 199 genlmsg_end(rskb, reply_payload); 200 201 return genlmsg_reply(rskb, info); 202 203err_free_msg: 204 nlmsg_free(rskb); 205err_unlock_rtnl: 206 rtnl_unlock(); 207 ethnl_parse_header_dev_put(&req_info); 208 return ret; 209} 210 211struct ethnl_tunnel_info_dump_ctx { 212 struct ethnl_req_info req_info; 213 int pos_hash; 214 int pos_idx; 215}; 216 217int ethnl_tunnel_info_start(struct netlink_callback *cb) 218{ 219 const struct genl_dumpit_info *info = genl_dumpit_info(cb); 220 struct ethnl_tunnel_info_dump_ctx *ctx = (void *)cb->ctx; 221 struct nlattr **tb = info->attrs; 222 int ret; 223 224 BUILD_BUG_ON(sizeof(*ctx) > sizeof(cb->ctx)); 225 226 memset(ctx, 0, sizeof(*ctx)); 227 228 ret = ethnl_parse_header_dev_get(&ctx->req_info, 229 tb[ETHTOOL_A_TUNNEL_INFO_HEADER], 230 sock_net(cb->skb->sk), cb->extack, 231 false); 232 if (ctx->req_info.dev) { 233 ethnl_parse_header_dev_put(&ctx->req_info); 234 ctx->req_info.dev = NULL; 235 } 236 237 return ret; 238} 239 240int ethnl_tunnel_info_dumpit(struct sk_buff *skb, struct netlink_callback *cb) 241{ 242 struct ethnl_tunnel_info_dump_ctx *ctx = (void *)cb->ctx; 243 struct net *net = sock_net(skb->sk); 244 int s_idx = ctx->pos_idx; 245 int h, idx = 0; 246 int ret = 0; 247 void *ehdr; 248 249 rtnl_lock(); 250 cb->seq = net->dev_base_seq; 251 for (h = ctx->pos_hash; h < NETDEV_HASHENTRIES; h++, s_idx = 0) { 252 struct hlist_head *head; 253 struct net_device *dev; 254 255 head = &net->dev_index_head[h]; 256 idx = 0; 257 hlist_for_each_entry(dev, head, index_hlist) { 258 if (idx < s_idx) 259 goto cont; 260 261 ehdr = ethnl_dump_put(skb, cb, 262 ETHTOOL_MSG_TUNNEL_INFO_GET_REPLY); 263 if (!ehdr) { 264 ret = -EMSGSIZE; 265 goto out; 266 } 267 268 ret = ethnl_fill_reply_header(skb, dev, ETHTOOL_A_TUNNEL_INFO_HEADER); 269 if (ret < 0) { 270 genlmsg_cancel(skb, ehdr); 271 goto out; 272 } 273 274 ctx->req_info.dev = dev; 275 ret = ethnl_tunnel_info_fill_reply(&ctx->req_info, skb); 276 ctx->req_info.dev = NULL; 277 if (ret < 0) { 278 genlmsg_cancel(skb, ehdr); 279 if (ret == -EOPNOTSUPP) 280 goto cont; 281 goto out; 282 } 283 genlmsg_end(skb, ehdr); 284cont: 285 idx++; 286 } 287 } 288out: 289 rtnl_unlock(); 290 291 ctx->pos_hash = h; 292 ctx->pos_idx = idx; 293 nl_dump_check_consistent(cb, nlmsg_hdr(skb)); 294 295 if (ret == -EMSGSIZE && skb->len) 296 return skb->len; 297 return ret; 298}