| Thomas Graf | 14c0b97 | 2006-08-04 03:38:38 -0700 | [diff] [blame] | 1 | /* | 
|  | 2 | * net/core/fib_rules.c		Generic Routing Rules | 
|  | 3 | * | 
|  | 4 | *	This program is free software; you can redistribute it and/or | 
|  | 5 | *	modify it under the terms of the GNU General Public License as | 
|  | 6 | *	published by the Free Software Foundation, version 2. | 
|  | 7 | * | 
|  | 8 | * Authors:	Thomas Graf <tgraf@suug.ch> | 
|  | 9 | */ | 
|  | 10 |  | 
|  | 11 | #include <linux/config.h> | 
|  | 12 | #include <linux/types.h> | 
|  | 13 | #include <linux/kernel.h> | 
|  | 14 | #include <linux/list.h> | 
|  | 15 | #include <net/fib_rules.h> | 
|  | 16 |  | 
|  | 17 | static LIST_HEAD(rules_ops); | 
|  | 18 | static DEFINE_SPINLOCK(rules_mod_lock); | 
|  | 19 |  | 
|  | 20 | static void notify_rule_change(int event, struct fib_rule *rule, | 
|  | 21 | struct fib_rules_ops *ops); | 
|  | 22 |  | 
|  | 23 | static struct fib_rules_ops *lookup_rules_ops(int family) | 
|  | 24 | { | 
|  | 25 | struct fib_rules_ops *ops; | 
|  | 26 |  | 
|  | 27 | rcu_read_lock(); | 
|  | 28 | list_for_each_entry_rcu(ops, &rules_ops, list) { | 
|  | 29 | if (ops->family == family) { | 
|  | 30 | if (!try_module_get(ops->owner)) | 
|  | 31 | ops = NULL; | 
|  | 32 | rcu_read_unlock(); | 
|  | 33 | return ops; | 
|  | 34 | } | 
|  | 35 | } | 
|  | 36 | rcu_read_unlock(); | 
|  | 37 |  | 
|  | 38 | return NULL; | 
|  | 39 | } | 
|  | 40 |  | 
|  | 41 | static void rules_ops_put(struct fib_rules_ops *ops) | 
|  | 42 | { | 
|  | 43 | if (ops) | 
|  | 44 | module_put(ops->owner); | 
|  | 45 | } | 
|  | 46 |  | 
|  | 47 | int fib_rules_register(struct fib_rules_ops *ops) | 
|  | 48 | { | 
|  | 49 | int err = -EEXIST; | 
|  | 50 | struct fib_rules_ops *o; | 
|  | 51 |  | 
|  | 52 | if (ops->rule_size < sizeof(struct fib_rule)) | 
|  | 53 | return -EINVAL; | 
|  | 54 |  | 
|  | 55 | if (ops->match == NULL || ops->configure == NULL || | 
|  | 56 | ops->compare == NULL || ops->fill == NULL || | 
|  | 57 | ops->action == NULL) | 
|  | 58 | return -EINVAL; | 
|  | 59 |  | 
|  | 60 | spin_lock(&rules_mod_lock); | 
|  | 61 | list_for_each_entry(o, &rules_ops, list) | 
|  | 62 | if (ops->family == o->family) | 
|  | 63 | goto errout; | 
|  | 64 |  | 
|  | 65 | list_add_tail_rcu(&ops->list, &rules_ops); | 
|  | 66 | err = 0; | 
|  | 67 | errout: | 
|  | 68 | spin_unlock(&rules_mod_lock); | 
|  | 69 |  | 
|  | 70 | return err; | 
|  | 71 | } | 
|  | 72 |  | 
|  | 73 | EXPORT_SYMBOL_GPL(fib_rules_register); | 
|  | 74 |  | 
|  | 75 | static void cleanup_ops(struct fib_rules_ops *ops) | 
|  | 76 | { | 
|  | 77 | struct fib_rule *rule, *tmp; | 
|  | 78 |  | 
|  | 79 | list_for_each_entry_safe(rule, tmp, ops->rules_list, list) { | 
|  | 80 | list_del_rcu(&rule->list); | 
|  | 81 | fib_rule_put(rule); | 
|  | 82 | } | 
|  | 83 | } | 
|  | 84 |  | 
|  | 85 | int fib_rules_unregister(struct fib_rules_ops *ops) | 
|  | 86 | { | 
|  | 87 | int err = 0; | 
|  | 88 | struct fib_rules_ops *o; | 
|  | 89 |  | 
|  | 90 | spin_lock(&rules_mod_lock); | 
|  | 91 | list_for_each_entry(o, &rules_ops, list) { | 
|  | 92 | if (o == ops) { | 
|  | 93 | list_del_rcu(&o->list); | 
|  | 94 | cleanup_ops(ops); | 
|  | 95 | goto out; | 
|  | 96 | } | 
|  | 97 | } | 
|  | 98 |  | 
|  | 99 | err = -ENOENT; | 
|  | 100 | out: | 
|  | 101 | spin_unlock(&rules_mod_lock); | 
|  | 102 |  | 
|  | 103 | synchronize_rcu(); | 
|  | 104 |  | 
|  | 105 | return err; | 
|  | 106 | } | 
|  | 107 |  | 
|  | 108 | EXPORT_SYMBOL_GPL(fib_rules_unregister); | 
|  | 109 |  | 
|  | 110 | int fib_rules_lookup(struct fib_rules_ops *ops, struct flowi *fl, | 
|  | 111 | int flags, struct fib_lookup_arg *arg) | 
|  | 112 | { | 
|  | 113 | struct fib_rule *rule; | 
|  | 114 | int err; | 
|  | 115 |  | 
|  | 116 | rcu_read_lock(); | 
|  | 117 |  | 
|  | 118 | list_for_each_entry_rcu(rule, ops->rules_list, list) { | 
|  | 119 | if (rule->ifindex && (rule->ifindex != fl->iif)) | 
|  | 120 | continue; | 
|  | 121 |  | 
|  | 122 | if (!ops->match(rule, fl, flags)) | 
|  | 123 | continue; | 
|  | 124 |  | 
|  | 125 | err = ops->action(rule, fl, flags, arg); | 
|  | 126 | if (err != -EAGAIN) { | 
|  | 127 | fib_rule_get(rule); | 
|  | 128 | arg->rule = rule; | 
|  | 129 | goto out; | 
|  | 130 | } | 
|  | 131 | } | 
|  | 132 |  | 
|  | 133 | err = -ENETUNREACH; | 
|  | 134 | out: | 
|  | 135 | rcu_read_unlock(); | 
|  | 136 |  | 
|  | 137 | return err; | 
|  | 138 | } | 
|  | 139 |  | 
|  | 140 | EXPORT_SYMBOL_GPL(fib_rules_lookup); | 
|  | 141 |  | 
|  | 142 | int fib_nl_newrule(struct sk_buff *skb, struct nlmsghdr* nlh, void *arg) | 
|  | 143 | { | 
|  | 144 | struct fib_rule_hdr *frh = nlmsg_data(nlh); | 
|  | 145 | struct fib_rules_ops *ops = NULL; | 
|  | 146 | struct fib_rule *rule, *r, *last = NULL; | 
|  | 147 | struct nlattr *tb[FRA_MAX+1]; | 
|  | 148 | int err = -EINVAL; | 
|  | 149 |  | 
|  | 150 | if (nlh->nlmsg_len < nlmsg_msg_size(sizeof(*frh))) | 
|  | 151 | goto errout; | 
|  | 152 |  | 
|  | 153 | ops = lookup_rules_ops(frh->family); | 
|  | 154 | if (ops == NULL) { | 
|  | 155 | err = EAFNOSUPPORT; | 
|  | 156 | goto errout; | 
|  | 157 | } | 
|  | 158 |  | 
|  | 159 | err = nlmsg_parse(nlh, sizeof(*frh), tb, FRA_MAX, ops->policy); | 
|  | 160 | if (err < 0) | 
|  | 161 | goto errout; | 
|  | 162 |  | 
|  | 163 | if (tb[FRA_IFNAME] && nla_len(tb[FRA_IFNAME]) > IFNAMSIZ) | 
|  | 164 | goto errout; | 
|  | 165 |  | 
|  | 166 | rule = kzalloc(ops->rule_size, GFP_KERNEL); | 
|  | 167 | if (rule == NULL) { | 
|  | 168 | err = -ENOMEM; | 
|  | 169 | goto errout; | 
|  | 170 | } | 
|  | 171 |  | 
|  | 172 | if (tb[FRA_PRIORITY]) | 
|  | 173 | rule->pref = nla_get_u32(tb[FRA_PRIORITY]); | 
|  | 174 |  | 
|  | 175 | if (tb[FRA_IFNAME]) { | 
|  | 176 | struct net_device *dev; | 
|  | 177 |  | 
|  | 178 | rule->ifindex = -1; | 
|  | 179 | if (nla_strlcpy(rule->ifname, tb[FRA_IFNAME], | 
|  | 180 | IFNAMSIZ) >= IFNAMSIZ) | 
|  | 181 | goto errout_free; | 
|  | 182 |  | 
|  | 183 | dev = __dev_get_by_name(rule->ifname); | 
|  | 184 | if (dev) | 
|  | 185 | rule->ifindex = dev->ifindex; | 
|  | 186 | } | 
|  | 187 |  | 
|  | 188 | rule->action = frh->action; | 
|  | 189 | rule->flags = frh->flags; | 
| Patrick McHardy | 9e762a4 | 2006-08-10 23:09:48 -0700 | [diff] [blame^] | 190 | rule->table = frh_get_table(frh, tb); | 
| Thomas Graf | 14c0b97 | 2006-08-04 03:38:38 -0700 | [diff] [blame] | 191 |  | 
|  | 192 | if (!rule->pref && ops->default_pref) | 
|  | 193 | rule->pref = ops->default_pref(); | 
|  | 194 |  | 
|  | 195 | err = ops->configure(rule, skb, nlh, frh, tb); | 
|  | 196 | if (err < 0) | 
|  | 197 | goto errout_free; | 
|  | 198 |  | 
|  | 199 | list_for_each_entry(r, ops->rules_list, list) { | 
|  | 200 | if (r->pref > rule->pref) | 
|  | 201 | break; | 
|  | 202 | last = r; | 
|  | 203 | } | 
|  | 204 |  | 
|  | 205 | fib_rule_get(rule); | 
|  | 206 |  | 
|  | 207 | if (last) | 
|  | 208 | list_add_rcu(&rule->list, &last->list); | 
|  | 209 | else | 
|  | 210 | list_add_rcu(&rule->list, ops->rules_list); | 
|  | 211 |  | 
|  | 212 | notify_rule_change(RTM_NEWRULE, rule, ops); | 
|  | 213 | rules_ops_put(ops); | 
|  | 214 | return 0; | 
|  | 215 |  | 
|  | 216 | errout_free: | 
|  | 217 | kfree(rule); | 
|  | 218 | errout: | 
|  | 219 | rules_ops_put(ops); | 
|  | 220 | return err; | 
|  | 221 | } | 
|  | 222 |  | 
|  | 223 | int fib_nl_delrule(struct sk_buff *skb, struct nlmsghdr* nlh, void *arg) | 
|  | 224 | { | 
|  | 225 | struct fib_rule_hdr *frh = nlmsg_data(nlh); | 
|  | 226 | struct fib_rules_ops *ops = NULL; | 
|  | 227 | struct fib_rule *rule; | 
|  | 228 | struct nlattr *tb[FRA_MAX+1]; | 
|  | 229 | int err = -EINVAL; | 
|  | 230 |  | 
|  | 231 | if (nlh->nlmsg_len < nlmsg_msg_size(sizeof(*frh))) | 
|  | 232 | goto errout; | 
|  | 233 |  | 
|  | 234 | ops = lookup_rules_ops(frh->family); | 
|  | 235 | if (ops == NULL) { | 
|  | 236 | err = EAFNOSUPPORT; | 
|  | 237 | goto errout; | 
|  | 238 | } | 
|  | 239 |  | 
|  | 240 | err = nlmsg_parse(nlh, sizeof(*frh), tb, FRA_MAX, ops->policy); | 
|  | 241 | if (err < 0) | 
|  | 242 | goto errout; | 
|  | 243 |  | 
|  | 244 | list_for_each_entry(rule, ops->rules_list, list) { | 
|  | 245 | if (frh->action && (frh->action != rule->action)) | 
|  | 246 | continue; | 
|  | 247 |  | 
| Patrick McHardy | 9e762a4 | 2006-08-10 23:09:48 -0700 | [diff] [blame^] | 248 | if (frh->table && (frh_get_table(frh, tb) != rule->table)) | 
| Thomas Graf | 14c0b97 | 2006-08-04 03:38:38 -0700 | [diff] [blame] | 249 | continue; | 
|  | 250 |  | 
|  | 251 | if (tb[FRA_PRIORITY] && | 
|  | 252 | (rule->pref != nla_get_u32(tb[FRA_PRIORITY]))) | 
|  | 253 | continue; | 
|  | 254 |  | 
|  | 255 | if (tb[FRA_IFNAME] && | 
|  | 256 | nla_strcmp(tb[FRA_IFNAME], rule->ifname)) | 
|  | 257 | continue; | 
|  | 258 |  | 
|  | 259 | if (!ops->compare(rule, frh, tb)) | 
|  | 260 | continue; | 
|  | 261 |  | 
|  | 262 | if (rule->flags & FIB_RULE_PERMANENT) { | 
|  | 263 | err = -EPERM; | 
|  | 264 | goto errout; | 
|  | 265 | } | 
|  | 266 |  | 
|  | 267 | list_del_rcu(&rule->list); | 
|  | 268 | synchronize_rcu(); | 
|  | 269 | notify_rule_change(RTM_DELRULE, rule, ops); | 
|  | 270 | fib_rule_put(rule); | 
|  | 271 | rules_ops_put(ops); | 
|  | 272 | return 0; | 
|  | 273 | } | 
|  | 274 |  | 
|  | 275 | err = -ENOENT; | 
|  | 276 | errout: | 
|  | 277 | rules_ops_put(ops); | 
|  | 278 | return err; | 
|  | 279 | } | 
|  | 280 |  | 
|  | 281 | static int fib_nl_fill_rule(struct sk_buff *skb, struct fib_rule *rule, | 
|  | 282 | u32 pid, u32 seq, int type, int flags, | 
|  | 283 | struct fib_rules_ops *ops) | 
|  | 284 | { | 
|  | 285 | struct nlmsghdr *nlh; | 
|  | 286 | struct fib_rule_hdr *frh; | 
|  | 287 |  | 
|  | 288 | nlh = nlmsg_put(skb, pid, seq, type, sizeof(*frh), flags); | 
|  | 289 | if (nlh == NULL) | 
|  | 290 | return -1; | 
|  | 291 |  | 
|  | 292 | frh = nlmsg_data(nlh); | 
|  | 293 | frh->table = rule->table; | 
| Patrick McHardy | 9e762a4 | 2006-08-10 23:09:48 -0700 | [diff] [blame^] | 294 | NLA_PUT_U32(skb, FRA_TABLE, rule->table); | 
| Thomas Graf | 14c0b97 | 2006-08-04 03:38:38 -0700 | [diff] [blame] | 295 | frh->res1 = 0; | 
|  | 296 | frh->res2 = 0; | 
|  | 297 | frh->action = rule->action; | 
|  | 298 | frh->flags = rule->flags; | 
|  | 299 |  | 
|  | 300 | if (rule->ifname[0]) | 
|  | 301 | NLA_PUT_STRING(skb, FRA_IFNAME, rule->ifname); | 
|  | 302 |  | 
|  | 303 | if (rule->pref) | 
|  | 304 | NLA_PUT_U32(skb, FRA_PRIORITY, rule->pref); | 
|  | 305 |  | 
|  | 306 | if (ops->fill(rule, skb, nlh, frh) < 0) | 
|  | 307 | goto nla_put_failure; | 
|  | 308 |  | 
|  | 309 | return nlmsg_end(skb, nlh); | 
|  | 310 |  | 
|  | 311 | nla_put_failure: | 
|  | 312 | return nlmsg_cancel(skb, nlh); | 
|  | 313 | } | 
|  | 314 |  | 
|  | 315 | int fib_rules_dump(struct sk_buff *skb, struct netlink_callback *cb, int family) | 
|  | 316 | { | 
|  | 317 | int idx = 0; | 
|  | 318 | struct fib_rule *rule; | 
|  | 319 | struct fib_rules_ops *ops; | 
|  | 320 |  | 
|  | 321 | ops = lookup_rules_ops(family); | 
|  | 322 | if (ops == NULL) | 
|  | 323 | return -EAFNOSUPPORT; | 
|  | 324 |  | 
|  | 325 | rcu_read_lock(); | 
|  | 326 | list_for_each_entry(rule, ops->rules_list, list) { | 
|  | 327 | if (idx < cb->args[0]) | 
|  | 328 | goto skip; | 
|  | 329 |  | 
|  | 330 | if (fib_nl_fill_rule(skb, rule, NETLINK_CB(cb->skb).pid, | 
|  | 331 | cb->nlh->nlmsg_seq, RTM_NEWRULE, | 
|  | 332 | NLM_F_MULTI, ops) < 0) | 
|  | 333 | break; | 
|  | 334 | skip: | 
|  | 335 | idx++; | 
|  | 336 | } | 
|  | 337 | rcu_read_unlock(); | 
|  | 338 | cb->args[0] = idx; | 
|  | 339 | rules_ops_put(ops); | 
|  | 340 |  | 
|  | 341 | return skb->len; | 
|  | 342 | } | 
|  | 343 |  | 
|  | 344 | EXPORT_SYMBOL_GPL(fib_rules_dump); | 
|  | 345 |  | 
|  | 346 | static void notify_rule_change(int event, struct fib_rule *rule, | 
|  | 347 | struct fib_rules_ops *ops) | 
|  | 348 | { | 
|  | 349 | int size = nlmsg_total_size(sizeof(struct fib_rule_hdr) + 128); | 
|  | 350 | struct sk_buff *skb = alloc_skb(size, GFP_KERNEL); | 
|  | 351 |  | 
|  | 352 | if (skb == NULL) | 
|  | 353 | netlink_set_err(rtnl, 0, ops->nlgroup, ENOBUFS); | 
|  | 354 | else if (fib_nl_fill_rule(skb, rule, 0, 0, event, 0, ops) < 0) { | 
|  | 355 | kfree_skb(skb); | 
|  | 356 | netlink_set_err(rtnl, 0, ops->nlgroup, EINVAL); | 
|  | 357 | } else | 
|  | 358 | netlink_broadcast(rtnl, skb, 0, ops->nlgroup, GFP_KERNEL); | 
|  | 359 | } | 
|  | 360 |  | 
|  | 361 | static void attach_rules(struct list_head *rules, struct net_device *dev) | 
|  | 362 | { | 
|  | 363 | struct fib_rule *rule; | 
|  | 364 |  | 
|  | 365 | list_for_each_entry(rule, rules, list) { | 
|  | 366 | if (rule->ifindex == -1 && | 
|  | 367 | strcmp(dev->name, rule->ifname) == 0) | 
|  | 368 | rule->ifindex = dev->ifindex; | 
|  | 369 | } | 
|  | 370 | } | 
|  | 371 |  | 
|  | 372 | static void detach_rules(struct list_head *rules, struct net_device *dev) | 
|  | 373 | { | 
|  | 374 | struct fib_rule *rule; | 
|  | 375 |  | 
|  | 376 | list_for_each_entry(rule, rules, list) | 
|  | 377 | if (rule->ifindex == dev->ifindex) | 
|  | 378 | rule->ifindex = -1; | 
|  | 379 | } | 
|  | 380 |  | 
|  | 381 |  | 
|  | 382 | static int fib_rules_event(struct notifier_block *this, unsigned long event, | 
|  | 383 | void *ptr) | 
|  | 384 | { | 
|  | 385 | struct net_device *dev = ptr; | 
|  | 386 | struct fib_rules_ops *ops; | 
|  | 387 |  | 
|  | 388 | ASSERT_RTNL(); | 
|  | 389 | rcu_read_lock(); | 
|  | 390 |  | 
|  | 391 | switch (event) { | 
|  | 392 | case NETDEV_REGISTER: | 
|  | 393 | list_for_each_entry(ops, &rules_ops, list) | 
|  | 394 | attach_rules(ops->rules_list, dev); | 
|  | 395 | break; | 
|  | 396 |  | 
|  | 397 | case NETDEV_UNREGISTER: | 
|  | 398 | list_for_each_entry(ops, &rules_ops, list) | 
|  | 399 | detach_rules(ops->rules_list, dev); | 
|  | 400 | break; | 
|  | 401 | } | 
|  | 402 |  | 
|  | 403 | rcu_read_unlock(); | 
|  | 404 |  | 
|  | 405 | return NOTIFY_DONE; | 
|  | 406 | } | 
|  | 407 |  | 
|  | 408 | static struct notifier_block fib_rules_notifier = { | 
|  | 409 | .notifier_call = fib_rules_event, | 
|  | 410 | }; | 
|  | 411 |  | 
|  | 412 | static int __init fib_rules_init(void) | 
|  | 413 | { | 
|  | 414 | return register_netdevice_notifier(&fib_rules_notifier); | 
|  | 415 | } | 
|  | 416 |  | 
|  | 417 | subsys_initcall(fib_rules_init); |