1 /* 2 * Copyright (c) 2018 Cumulus Networks. All rights reserved. 3 * Copyright (c) 2018 David Ahern <dsa@cumulusnetworks.com> 4 * 5 * This software is licensed under the GNU General License Version 2, 6 * June 1991 as shown in the file COPYING in the top-level directory of this 7 * source tree. 8 * 9 * THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" 10 * WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, 11 * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 12 * FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE 13 * OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME 14 * THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 15 */ 16 17 #include <net/fib_notifier.h> 18 #include <net/ip_fib.h> 19 #include <net/ip6_fib.h> 20 #include <net/fib_rules.h> 21 #include <net/net_namespace.h> 22 23 #include "netdevsim.h" 24 25 struct nsim_fib_entry { 26 u64 max; 27 u64 num; 28 }; 29 30 struct nsim_per_fib_data { 31 struct nsim_fib_entry fib; 32 struct nsim_fib_entry rules; 33 }; 34 35 struct nsim_fib_data { 36 struct notifier_block fib_nb; 37 struct nsim_per_fib_data ipv4; 38 struct nsim_per_fib_data ipv6; 39 }; 40 41 u64 nsim_fib_get_val(struct nsim_fib_data *fib_data, 42 enum nsim_resource_id res_id, bool max) 43 { 44 struct nsim_fib_entry *entry; 45 46 switch (res_id) { 47 case NSIM_RESOURCE_IPV4_FIB: 48 entry = &fib_data->ipv4.fib; 49 break; 50 case NSIM_RESOURCE_IPV4_FIB_RULES: 51 entry = &fib_data->ipv4.rules; 52 break; 53 case NSIM_RESOURCE_IPV6_FIB: 54 entry = &fib_data->ipv6.fib; 55 break; 56 case NSIM_RESOURCE_IPV6_FIB_RULES: 57 entry = &fib_data->ipv6.rules; 58 break; 59 default: 60 return 0; 61 } 62 63 return max ? entry->max : entry->num; 64 } 65 66 static void nsim_fib_set_max(struct nsim_fib_data *fib_data, 67 enum nsim_resource_id res_id, u64 val) 68 { 69 struct nsim_fib_entry *entry; 70 71 switch (res_id) { 72 case NSIM_RESOURCE_IPV4_FIB: 73 entry = &fib_data->ipv4.fib; 74 break; 75 case NSIM_RESOURCE_IPV4_FIB_RULES: 76 entry = &fib_data->ipv4.rules; 77 break; 78 case NSIM_RESOURCE_IPV6_FIB: 79 entry = &fib_data->ipv6.fib; 80 break; 81 case NSIM_RESOURCE_IPV6_FIB_RULES: 82 entry = &fib_data->ipv6.rules; 83 break; 84 default: 85 WARN_ON(1); 86 return; 87 } 88 entry->max = val; 89 } 90 91 static int nsim_fib_rule_account(struct nsim_fib_entry *entry, bool add, 92 struct netlink_ext_ack *extack) 93 { 94 int err = 0; 95 96 if (add) { 97 if (entry->num < entry->max) { 98 entry->num++; 99 } else { 100 err = -ENOSPC; 101 NL_SET_ERR_MSG_MOD(extack, "Exceeded number of supported fib rule entries"); 102 } 103 } else { 104 entry->num--; 105 } 106 107 return err; 108 } 109 110 static int nsim_fib_rule_event(struct nsim_fib_data *data, 111 struct fib_notifier_info *info, bool add) 112 { 113 struct netlink_ext_ack *extack = info->extack; 114 int err = 0; 115 116 switch (info->family) { 117 case AF_INET: 118 err = nsim_fib_rule_account(&data->ipv4.rules, add, extack); 119 break; 120 case AF_INET6: 121 err = nsim_fib_rule_account(&data->ipv6.rules, add, extack); 122 break; 123 } 124 125 return err; 126 } 127 128 static int nsim_fib_account(struct nsim_fib_entry *entry, bool add, 129 struct netlink_ext_ack *extack) 130 { 131 int err = 0; 132 133 if (add) { 134 if (entry->num < entry->max) { 135 entry->num++; 136 } else { 137 err = -ENOSPC; 138 NL_SET_ERR_MSG_MOD(extack, "Exceeded number of supported fib entries"); 139 } 140 } else { 141 entry->num--; 142 } 143 144 return err; 145 } 146 147 static int nsim_fib_event(struct nsim_fib_data *data, 148 struct fib_notifier_info *info, bool add) 149 { 150 struct netlink_ext_ack *extack = info->extack; 151 int err = 0; 152 153 switch (info->family) { 154 case AF_INET: 155 err = nsim_fib_account(&data->ipv4.fib, add, extack); 156 break; 157 case AF_INET6: 158 err = nsim_fib_account(&data->ipv6.fib, add, extack); 159 break; 160 } 161 162 return err; 163 } 164 165 static int nsim_fib_event_nb(struct notifier_block *nb, unsigned long event, 166 void *ptr) 167 { 168 struct nsim_fib_data *data = container_of(nb, struct nsim_fib_data, 169 fib_nb); 170 struct fib_notifier_info *info = ptr; 171 int err = 0; 172 173 switch (event) { 174 case FIB_EVENT_RULE_ADD: /* fall through */ 175 case FIB_EVENT_RULE_DEL: 176 err = nsim_fib_rule_event(data, info, 177 event == FIB_EVENT_RULE_ADD); 178 break; 179 180 case FIB_EVENT_ENTRY_ADD: /* fall through */ 181 case FIB_EVENT_ENTRY_DEL: 182 err = nsim_fib_event(data, info, 183 event == FIB_EVENT_ENTRY_ADD); 184 break; 185 } 186 187 return notifier_from_errno(err); 188 } 189 190 /* inconsistent dump, trying again */ 191 static void nsim_fib_dump_inconsistent(struct notifier_block *nb) 192 { 193 struct nsim_fib_data *data = container_of(nb, struct nsim_fib_data, 194 fib_nb); 195 196 data->ipv4.fib.num = 0ULL; 197 data->ipv4.rules.num = 0ULL; 198 data->ipv6.fib.num = 0ULL; 199 data->ipv6.rules.num = 0ULL; 200 } 201 202 static u64 nsim_fib_ipv4_resource_occ_get(void *priv) 203 { 204 struct nsim_fib_data *data = priv; 205 206 return nsim_fib_get_val(data, NSIM_RESOURCE_IPV4_FIB, false); 207 } 208 209 static u64 nsim_fib_ipv4_rules_res_occ_get(void *priv) 210 { 211 struct nsim_fib_data *data = priv; 212 213 return nsim_fib_get_val(data, NSIM_RESOURCE_IPV4_FIB_RULES, false); 214 } 215 216 static u64 nsim_fib_ipv6_resource_occ_get(void *priv) 217 { 218 struct nsim_fib_data *data = priv; 219 220 return nsim_fib_get_val(data, NSIM_RESOURCE_IPV6_FIB, false); 221 } 222 223 static u64 nsim_fib_ipv6_rules_res_occ_get(void *priv) 224 { 225 struct nsim_fib_data *data = priv; 226 227 return nsim_fib_get_val(data, NSIM_RESOURCE_IPV6_FIB_RULES, false); 228 } 229 230 static void nsim_fib_set_max_all(struct nsim_fib_data *data, 231 struct devlink *devlink) 232 { 233 enum nsim_resource_id res_ids[] = { 234 NSIM_RESOURCE_IPV4_FIB, NSIM_RESOURCE_IPV4_FIB_RULES, 235 NSIM_RESOURCE_IPV6_FIB, NSIM_RESOURCE_IPV6_FIB_RULES 236 }; 237 int i; 238 239 for (i = 0; i < ARRAY_SIZE(res_ids); i++) { 240 int err; 241 u64 val; 242 243 err = devlink_resource_size_get(devlink, res_ids[i], &val); 244 if (err) 245 val = (u64) -1; 246 nsim_fib_set_max(data, res_ids[i], val); 247 } 248 } 249 250 struct nsim_fib_data *nsim_fib_create(struct devlink *devlink, 251 struct netlink_ext_ack *extack) 252 { 253 struct nsim_fib_data *data; 254 int err; 255 256 data = kzalloc(sizeof(*data), GFP_KERNEL); 257 if (!data) 258 return ERR_PTR(-ENOMEM); 259 260 nsim_fib_set_max_all(data, devlink); 261 262 data->fib_nb.notifier_call = nsim_fib_event_nb; 263 err = register_fib_notifier(devlink_net(devlink), &data->fib_nb, 264 nsim_fib_dump_inconsistent, extack); 265 if (err) { 266 pr_err("Failed to register fib notifier\n"); 267 goto err_out; 268 } 269 270 devlink_resource_occ_get_register(devlink, 271 NSIM_RESOURCE_IPV4_FIB, 272 nsim_fib_ipv4_resource_occ_get, 273 data); 274 devlink_resource_occ_get_register(devlink, 275 NSIM_RESOURCE_IPV4_FIB_RULES, 276 nsim_fib_ipv4_rules_res_occ_get, 277 data); 278 devlink_resource_occ_get_register(devlink, 279 NSIM_RESOURCE_IPV6_FIB, 280 nsim_fib_ipv6_resource_occ_get, 281 data); 282 devlink_resource_occ_get_register(devlink, 283 NSIM_RESOURCE_IPV6_FIB_RULES, 284 nsim_fib_ipv6_rules_res_occ_get, 285 data); 286 return data; 287 288 err_out: 289 kfree(data); 290 return ERR_PTR(err); 291 } 292 293 void nsim_fib_destroy(struct devlink *devlink, struct nsim_fib_data *data) 294 { 295 devlink_resource_occ_get_unregister(devlink, 296 NSIM_RESOURCE_IPV6_FIB_RULES); 297 devlink_resource_occ_get_unregister(devlink, 298 NSIM_RESOURCE_IPV6_FIB); 299 devlink_resource_occ_get_unregister(devlink, 300 NSIM_RESOURCE_IPV4_FIB_RULES); 301 devlink_resource_occ_get_unregister(devlink, 302 NSIM_RESOURCE_IPV4_FIB); 303 unregister_fib_notifier(devlink_net(devlink), &data->fib_nb); 304 kfree(data); 305 } 306