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 22 #include "netdevsim.h" 23 24 struct nsim_fib_entry { 25 u64 max; 26 u64 num; 27 }; 28 29 struct nsim_per_fib_data { 30 struct nsim_fib_entry fib; 31 struct nsim_fib_entry rules; 32 }; 33 34 struct nsim_fib_data { 35 struct notifier_block fib_nb; 36 struct nsim_per_fib_data ipv4; 37 struct nsim_per_fib_data ipv6; 38 }; 39 40 u64 nsim_fib_get_val(struct nsim_fib_data *fib_data, 41 enum nsim_resource_id res_id, bool max) 42 { 43 struct nsim_fib_entry *entry; 44 45 switch (res_id) { 46 case NSIM_RESOURCE_IPV4_FIB: 47 entry = &fib_data->ipv4.fib; 48 break; 49 case NSIM_RESOURCE_IPV4_FIB_RULES: 50 entry = &fib_data->ipv4.rules; 51 break; 52 case NSIM_RESOURCE_IPV6_FIB: 53 entry = &fib_data->ipv6.fib; 54 break; 55 case NSIM_RESOURCE_IPV6_FIB_RULES: 56 entry = &fib_data->ipv6.rules; 57 break; 58 default: 59 return 0; 60 } 61 62 return max ? entry->max : entry->num; 63 } 64 65 int nsim_fib_set_max(struct nsim_fib_data *fib_data, 66 enum nsim_resource_id res_id, u64 val, 67 struct netlink_ext_ack *extack) 68 { 69 struct nsim_fib_entry *entry; 70 int err = 0; 71 72 switch (res_id) { 73 case NSIM_RESOURCE_IPV4_FIB: 74 entry = &fib_data->ipv4.fib; 75 break; 76 case NSIM_RESOURCE_IPV4_FIB_RULES: 77 entry = &fib_data->ipv4.rules; 78 break; 79 case NSIM_RESOURCE_IPV6_FIB: 80 entry = &fib_data->ipv6.fib; 81 break; 82 case NSIM_RESOURCE_IPV6_FIB_RULES: 83 entry = &fib_data->ipv6.rules; 84 break; 85 default: 86 return 0; 87 } 88 89 /* not allowing a new max to be less than curren occupancy 90 * --> no means of evicting entries 91 */ 92 if (val < entry->num) { 93 NL_SET_ERR_MSG_MOD(extack, "New size is less than current occupancy"); 94 err = -EINVAL; 95 } else { 96 entry->max = val; 97 } 98 99 return err; 100 } 101 102 static int nsim_fib_rule_account(struct nsim_fib_entry *entry, bool add, 103 struct netlink_ext_ack *extack) 104 { 105 int err = 0; 106 107 if (add) { 108 if (entry->num < entry->max) { 109 entry->num++; 110 } else { 111 err = -ENOSPC; 112 NL_SET_ERR_MSG_MOD(extack, "Exceeded number of supported fib rule entries"); 113 } 114 } else { 115 entry->num--; 116 } 117 118 return err; 119 } 120 121 static int nsim_fib_rule_event(struct nsim_fib_data *data, 122 struct fib_notifier_info *info, bool add) 123 { 124 struct netlink_ext_ack *extack = info->extack; 125 int err = 0; 126 127 switch (info->family) { 128 case AF_INET: 129 err = nsim_fib_rule_account(&data->ipv4.rules, add, extack); 130 break; 131 case AF_INET6: 132 err = nsim_fib_rule_account(&data->ipv6.rules, add, extack); 133 break; 134 } 135 136 return err; 137 } 138 139 static int nsim_fib_account(struct nsim_fib_entry *entry, bool add, 140 struct netlink_ext_ack *extack) 141 { 142 int err = 0; 143 144 if (add) { 145 if (entry->num < entry->max) { 146 entry->num++; 147 } else { 148 err = -ENOSPC; 149 NL_SET_ERR_MSG_MOD(extack, "Exceeded number of supported fib entries"); 150 } 151 } else { 152 entry->num--; 153 } 154 155 return err; 156 } 157 158 static int nsim_fib_event(struct nsim_fib_data *data, 159 struct fib_notifier_info *info, bool add) 160 { 161 struct netlink_ext_ack *extack = info->extack; 162 int err = 0; 163 164 switch (info->family) { 165 case AF_INET: 166 err = nsim_fib_account(&data->ipv4.fib, add, extack); 167 break; 168 case AF_INET6: 169 err = nsim_fib_account(&data->ipv6.fib, add, extack); 170 break; 171 } 172 173 return err; 174 } 175 176 static int nsim_fib_event_nb(struct notifier_block *nb, unsigned long event, 177 void *ptr) 178 { 179 struct nsim_fib_data *data = container_of(nb, struct nsim_fib_data, 180 fib_nb); 181 struct fib_notifier_info *info = ptr; 182 int err = 0; 183 184 switch (event) { 185 case FIB_EVENT_RULE_ADD: /* fall through */ 186 case FIB_EVENT_RULE_DEL: 187 err = nsim_fib_rule_event(data, info, 188 event == FIB_EVENT_RULE_ADD); 189 break; 190 191 case FIB_EVENT_ENTRY_ADD: /* fall through */ 192 case FIB_EVENT_ENTRY_DEL: 193 err = nsim_fib_event(data, info, 194 event == FIB_EVENT_ENTRY_ADD); 195 break; 196 } 197 198 return notifier_from_errno(err); 199 } 200 201 /* inconsistent dump, trying again */ 202 static void nsim_fib_dump_inconsistent(struct notifier_block *nb) 203 { 204 struct nsim_fib_data *data = container_of(nb, struct nsim_fib_data, 205 fib_nb); 206 207 data->ipv4.fib.num = 0ULL; 208 data->ipv4.rules.num = 0ULL; 209 data->ipv6.fib.num = 0ULL; 210 data->ipv6.rules.num = 0ULL; 211 } 212 213 struct nsim_fib_data *nsim_fib_create(void) 214 { 215 struct nsim_fib_data *data; 216 int err; 217 218 data = kzalloc(sizeof(*data), GFP_KERNEL); 219 if (!data) 220 return ERR_PTR(-ENOMEM); 221 222 data->ipv4.fib.max = (u64)-1; 223 data->ipv4.rules.max = (u64)-1; 224 225 data->ipv6.fib.max = (u64)-1; 226 data->ipv6.rules.max = (u64)-1; 227 228 data->fib_nb.notifier_call = nsim_fib_event_nb; 229 err = register_fib_notifier(&data->fib_nb, nsim_fib_dump_inconsistent); 230 if (err) { 231 pr_err("Failed to register fib notifier\n"); 232 goto err_out; 233 } 234 235 return data; 236 237 err_out: 238 kfree(data); 239 return ERR_PTR(err); 240 } 241 242 void nsim_fib_destroy(struct nsim_fib_data *data) 243 { 244 unregister_fib_notifier(&data->fib_nb); 245 kfree(data); 246 } 247