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_REPLACE: /* fall through */ 181 case FIB_EVENT_ENTRY_DEL: 182 err = nsim_fib_event(data, info, event != FIB_EVENT_ENTRY_DEL); 183 break; 184 } 185 186 return notifier_from_errno(err); 187 } 188 189 /* inconsistent dump, trying again */ 190 static void nsim_fib_dump_inconsistent(struct notifier_block *nb) 191 { 192 struct nsim_fib_data *data = container_of(nb, struct nsim_fib_data, 193 fib_nb); 194 195 data->ipv4.fib.num = 0ULL; 196 data->ipv4.rules.num = 0ULL; 197 data->ipv6.fib.num = 0ULL; 198 data->ipv6.rules.num = 0ULL; 199 } 200 201 static u64 nsim_fib_ipv4_resource_occ_get(void *priv) 202 { 203 struct nsim_fib_data *data = priv; 204 205 return nsim_fib_get_val(data, NSIM_RESOURCE_IPV4_FIB, false); 206 } 207 208 static u64 nsim_fib_ipv4_rules_res_occ_get(void *priv) 209 { 210 struct nsim_fib_data *data = priv; 211 212 return nsim_fib_get_val(data, NSIM_RESOURCE_IPV4_FIB_RULES, false); 213 } 214 215 static u64 nsim_fib_ipv6_resource_occ_get(void *priv) 216 { 217 struct nsim_fib_data *data = priv; 218 219 return nsim_fib_get_val(data, NSIM_RESOURCE_IPV6_FIB, false); 220 } 221 222 static u64 nsim_fib_ipv6_rules_res_occ_get(void *priv) 223 { 224 struct nsim_fib_data *data = priv; 225 226 return nsim_fib_get_val(data, NSIM_RESOURCE_IPV6_FIB_RULES, false); 227 } 228 229 static void nsim_fib_set_max_all(struct nsim_fib_data *data, 230 struct devlink *devlink) 231 { 232 enum nsim_resource_id res_ids[] = { 233 NSIM_RESOURCE_IPV4_FIB, NSIM_RESOURCE_IPV4_FIB_RULES, 234 NSIM_RESOURCE_IPV6_FIB, NSIM_RESOURCE_IPV6_FIB_RULES 235 }; 236 int i; 237 238 for (i = 0; i < ARRAY_SIZE(res_ids); i++) { 239 int err; 240 u64 val; 241 242 err = devlink_resource_size_get(devlink, res_ids[i], &val); 243 if (err) 244 val = (u64) -1; 245 nsim_fib_set_max(data, res_ids[i], val); 246 } 247 } 248 249 struct nsim_fib_data *nsim_fib_create(struct devlink *devlink, 250 struct netlink_ext_ack *extack) 251 { 252 struct nsim_fib_data *data; 253 int err; 254 255 data = kzalloc(sizeof(*data), GFP_KERNEL); 256 if (!data) 257 return ERR_PTR(-ENOMEM); 258 259 nsim_fib_set_max_all(data, devlink); 260 261 data->fib_nb.notifier_call = nsim_fib_event_nb; 262 err = register_fib_notifier(devlink_net(devlink), &data->fib_nb, 263 nsim_fib_dump_inconsistent, extack); 264 if (err) { 265 pr_err("Failed to register fib notifier\n"); 266 goto err_out; 267 } 268 269 devlink_resource_occ_get_register(devlink, 270 NSIM_RESOURCE_IPV4_FIB, 271 nsim_fib_ipv4_resource_occ_get, 272 data); 273 devlink_resource_occ_get_register(devlink, 274 NSIM_RESOURCE_IPV4_FIB_RULES, 275 nsim_fib_ipv4_rules_res_occ_get, 276 data); 277 devlink_resource_occ_get_register(devlink, 278 NSIM_RESOURCE_IPV6_FIB, 279 nsim_fib_ipv6_resource_occ_get, 280 data); 281 devlink_resource_occ_get_register(devlink, 282 NSIM_RESOURCE_IPV6_FIB_RULES, 283 nsim_fib_ipv6_rules_res_occ_get, 284 data); 285 return data; 286 287 err_out: 288 kfree(data); 289 return ERR_PTR(err); 290 } 291 292 void nsim_fib_destroy(struct devlink *devlink, struct nsim_fib_data *data) 293 { 294 devlink_resource_occ_get_unregister(devlink, 295 NSIM_RESOURCE_IPV6_FIB_RULES); 296 devlink_resource_occ_get_unregister(devlink, 297 NSIM_RESOURCE_IPV6_FIB); 298 devlink_resource_occ_get_unregister(devlink, 299 NSIM_RESOURCE_IPV4_FIB_RULES); 300 devlink_resource_occ_get_unregister(devlink, 301 NSIM_RESOURCE_IPV4_FIB); 302 unregister_fib_notifier(devlink_net(devlink), &data->fib_nb); 303 kfree(data); 304 } 305