diff options
-rw-r--r-- | include/linux/fib_rules.h | 5 | ||||
-rw-r--r-- | include/net/fib_rules.h | 7 | ||||
-rw-r--r-- | net/core/fib_rules.c | 88 |
3 files changed, 94 insertions, 6 deletions
diff --git a/include/linux/fib_rules.h b/include/linux/fib_rules.h index 8270aac2aa5d..ec9c7b1d3e91 100644 --- a/include/linux/fib_rules.h +++ b/include/linux/fib_rules.h | |||
@@ -7,6 +7,7 @@ | |||
7 | /* rule is permanent, and cannot be deleted */ | 7 | /* rule is permanent, and cannot be deleted */ |
8 | #define FIB_RULE_PERMANENT 1 | 8 | #define FIB_RULE_PERMANENT 1 |
9 | #define FIB_RULE_INVERT 2 | 9 | #define FIB_RULE_INVERT 2 |
10 | #define FIB_RULE_UNRESOLVED 4 | ||
10 | 11 | ||
11 | struct fib_rule_hdr | 12 | struct fib_rule_hdr |
12 | { | 13 | { |
@@ -29,7 +30,7 @@ enum | |||
29 | FRA_DST, /* destination address */ | 30 | FRA_DST, /* destination address */ |
30 | FRA_SRC, /* source address */ | 31 | FRA_SRC, /* source address */ |
31 | FRA_IFNAME, /* interface name */ | 32 | FRA_IFNAME, /* interface name */ |
32 | FRA_UNUSED1, | 33 | FRA_GOTO, /* target to jump to (FR_ACT_GOTO) */ |
33 | FRA_UNUSED2, | 34 | FRA_UNUSED2, |
34 | FRA_PRIORITY, /* priority/preference */ | 35 | FRA_PRIORITY, /* priority/preference */ |
35 | FRA_UNUSED3, | 36 | FRA_UNUSED3, |
@@ -51,7 +52,7 @@ enum | |||
51 | { | 52 | { |
52 | FR_ACT_UNSPEC, | 53 | FR_ACT_UNSPEC, |
53 | FR_ACT_TO_TBL, /* Pass to fixed table */ | 54 | FR_ACT_TO_TBL, /* Pass to fixed table */ |
54 | FR_ACT_RES1, | 55 | FR_ACT_GOTO, /* Jump to another rule */ |
55 | FR_ACT_RES2, | 56 | FR_ACT_RES2, |
56 | FR_ACT_RES3, | 57 | FR_ACT_RES3, |
57 | FR_ACT_RES4, | 58 | FR_ACT_RES4, |
diff --git a/include/net/fib_rules.h b/include/net/fib_rules.h index ff3029fe9656..08bab8b6e575 100644 --- a/include/net/fib_rules.h +++ b/include/net/fib_rules.h | |||
@@ -19,6 +19,8 @@ struct fib_rule | |||
19 | u32 flags; | 19 | u32 flags; |
20 | u32 table; | 20 | u32 table; |
21 | u8 action; | 21 | u8 action; |
22 | u32 target; | ||
23 | struct fib_rule * ctarget; | ||
22 | struct rcu_head rcu; | 24 | struct rcu_head rcu; |
23 | }; | 25 | }; |
24 | 26 | ||
@@ -35,6 +37,8 @@ struct fib_rules_ops | |||
35 | struct list_head list; | 37 | struct list_head list; |
36 | int rule_size; | 38 | int rule_size; |
37 | int addr_size; | 39 | int addr_size; |
40 | int unresolved_rules; | ||
41 | int nr_goto_rules; | ||
38 | 42 | ||
39 | int (*action)(struct fib_rule *, | 43 | int (*action)(struct fib_rule *, |
40 | struct flowi *, int, | 44 | struct flowi *, int, |
@@ -66,7 +70,8 @@ struct fib_rules_ops | |||
66 | [FRA_PRIORITY] = { .type = NLA_U32 }, \ | 70 | [FRA_PRIORITY] = { .type = NLA_U32 }, \ |
67 | [FRA_FWMARK] = { .type = NLA_U32 }, \ | 71 | [FRA_FWMARK] = { .type = NLA_U32 }, \ |
68 | [FRA_FWMASK] = { .type = NLA_U32 }, \ | 72 | [FRA_FWMASK] = { .type = NLA_U32 }, \ |
69 | [FRA_TABLE] = { .type = NLA_U32 } | 73 | [FRA_TABLE] = { .type = NLA_U32 }, \ |
74 | [FRA_GOTO] = { .type = NLA_U32 } | ||
70 | 75 | ||
71 | static inline void fib_rule_get(struct fib_rule *rule) | 76 | static inline void fib_rule_get(struct fib_rule *rule) |
72 | { | 77 | { |
diff --git a/net/core/fib_rules.c b/net/core/fib_rules.c index fdf05af16ba5..0d8bb2efb0c1 100644 --- a/net/core/fib_rules.c +++ b/net/core/fib_rules.c | |||
@@ -132,10 +132,23 @@ int fib_rules_lookup(struct fib_rules_ops *ops, struct flowi *fl, | |||
132 | rcu_read_lock(); | 132 | rcu_read_lock(); |
133 | 133 | ||
134 | list_for_each_entry_rcu(rule, ops->rules_list, list) { | 134 | list_for_each_entry_rcu(rule, ops->rules_list, list) { |
135 | jumped: | ||
135 | if (!fib_rule_match(rule, ops, fl, flags)) | 136 | if (!fib_rule_match(rule, ops, fl, flags)) |
136 | continue; | 137 | continue; |
137 | 138 | ||
138 | err = ops->action(rule, fl, flags, arg); | 139 | if (rule->action == FR_ACT_GOTO) { |
140 | struct fib_rule *target; | ||
141 | |||
142 | target = rcu_dereference(rule->ctarget); | ||
143 | if (target == NULL) { | ||
144 | continue; | ||
145 | } else { | ||
146 | rule = target; | ||
147 | goto jumped; | ||
148 | } | ||
149 | } else | ||
150 | err = ops->action(rule, fl, flags, arg); | ||
151 | |||
139 | if (err != -EAGAIN) { | 152 | if (err != -EAGAIN) { |
140 | fib_rule_get(rule); | 153 | fib_rule_get(rule); |
141 | arg->rule = rule; | 154 | arg->rule = rule; |
@@ -180,7 +193,7 @@ static int fib_nl_newrule(struct sk_buff *skb, struct nlmsghdr* nlh, void *arg) | |||
180 | struct fib_rules_ops *ops = NULL; | 193 | struct fib_rules_ops *ops = NULL; |
181 | struct fib_rule *rule, *r, *last = NULL; | 194 | struct fib_rule *rule, *r, *last = NULL; |
182 | struct nlattr *tb[FRA_MAX+1]; | 195 | struct nlattr *tb[FRA_MAX+1]; |
183 | int err = -EINVAL; | 196 | int err = -EINVAL, unresolved = 0; |
184 | 197 | ||
185 | if (nlh->nlmsg_len < nlmsg_msg_size(sizeof(*frh))) | 198 | if (nlh->nlmsg_len < nlmsg_msg_size(sizeof(*frh))) |
186 | goto errout; | 199 | goto errout; |
@@ -237,6 +250,28 @@ static int fib_nl_newrule(struct sk_buff *skb, struct nlmsghdr* nlh, void *arg) | |||
237 | if (!rule->pref && ops->default_pref) | 250 | if (!rule->pref && ops->default_pref) |
238 | rule->pref = ops->default_pref(); | 251 | rule->pref = ops->default_pref(); |
239 | 252 | ||
253 | err = -EINVAL; | ||
254 | if (tb[FRA_GOTO]) { | ||
255 | if (rule->action != FR_ACT_GOTO) | ||
256 | goto errout_free; | ||
257 | |||
258 | rule->target = nla_get_u32(tb[FRA_GOTO]); | ||
259 | /* Backward jumps are prohibited to avoid endless loops */ | ||
260 | if (rule->target <= rule->pref) | ||
261 | goto errout_free; | ||
262 | |||
263 | list_for_each_entry(r, ops->rules_list, list) { | ||
264 | if (r->pref == rule->target) { | ||
265 | rule->ctarget = r; | ||
266 | break; | ||
267 | } | ||
268 | } | ||
269 | |||
270 | if (rule->ctarget == NULL) | ||
271 | unresolved = 1; | ||
272 | } else if (rule->action == FR_ACT_GOTO) | ||
273 | goto errout_free; | ||
274 | |||
240 | err = ops->configure(rule, skb, nlh, frh, tb); | 275 | err = ops->configure(rule, skb, nlh, frh, tb); |
241 | if (err < 0) | 276 | if (err < 0) |
242 | goto errout_free; | 277 | goto errout_free; |
@@ -249,6 +284,28 @@ static int fib_nl_newrule(struct sk_buff *skb, struct nlmsghdr* nlh, void *arg) | |||
249 | 284 | ||
250 | fib_rule_get(rule); | 285 | fib_rule_get(rule); |
251 | 286 | ||
287 | if (ops->unresolved_rules) { | ||
288 | /* | ||
289 | * There are unresolved goto rules in the list, check if | ||
290 | * any of them are pointing to this new rule. | ||
291 | */ | ||
292 | list_for_each_entry(r, ops->rules_list, list) { | ||
293 | if (r->action == FR_ACT_GOTO && | ||
294 | r->target == rule->pref) { | ||
295 | BUG_ON(r->ctarget != NULL); | ||
296 | rcu_assign_pointer(r->ctarget, rule); | ||
297 | if (--ops->unresolved_rules == 0) | ||
298 | break; | ||
299 | } | ||
300 | } | ||
301 | } | ||
302 | |||
303 | if (rule->action == FR_ACT_GOTO) | ||
304 | ops->nr_goto_rules++; | ||
305 | |||
306 | if (unresolved) | ||
307 | ops->unresolved_rules++; | ||
308 | |||
252 | if (last) | 309 | if (last) |
253 | list_add_rcu(&rule->list, &last->list); | 310 | list_add_rcu(&rule->list, &last->list); |
254 | else | 311 | else |
@@ -269,7 +326,7 @@ static int fib_nl_delrule(struct sk_buff *skb, struct nlmsghdr* nlh, void *arg) | |||
269 | { | 326 | { |
270 | struct fib_rule_hdr *frh = nlmsg_data(nlh); | 327 | struct fib_rule_hdr *frh = nlmsg_data(nlh); |
271 | struct fib_rules_ops *ops = NULL; | 328 | struct fib_rules_ops *ops = NULL; |
272 | struct fib_rule *rule; | 329 | struct fib_rule *rule, *tmp; |
273 | struct nlattr *tb[FRA_MAX+1]; | 330 | struct nlattr *tb[FRA_MAX+1]; |
274 | int err = -EINVAL; | 331 | int err = -EINVAL; |
275 | 332 | ||
@@ -322,6 +379,25 @@ static int fib_nl_delrule(struct sk_buff *skb, struct nlmsghdr* nlh, void *arg) | |||
322 | } | 379 | } |
323 | 380 | ||
324 | list_del_rcu(&rule->list); | 381 | list_del_rcu(&rule->list); |
382 | |||
383 | if (rule->action == FR_ACT_GOTO) | ||
384 | ops->nr_goto_rules--; | ||
385 | |||
386 | /* | ||
387 | * Check if this rule is a target to any of them. If so, | ||
388 | * disable them. As this operation is eventually very | ||
389 | * expensive, it is only performed if goto rules have | ||
390 | * actually been added. | ||
391 | */ | ||
392 | if (ops->nr_goto_rules > 0) { | ||
393 | list_for_each_entry(tmp, ops->rules_list, list) { | ||
394 | if (tmp->ctarget == rule) { | ||
395 | rcu_assign_pointer(tmp->ctarget, NULL); | ||
396 | ops->unresolved_rules++; | ||
397 | } | ||
398 | } | ||
399 | } | ||
400 | |||
325 | synchronize_rcu(); | 401 | synchronize_rcu(); |
326 | notify_rule_change(RTM_DELRULE, rule, ops, nlh, | 402 | notify_rule_change(RTM_DELRULE, rule, ops, nlh, |
327 | NETLINK_CB(skb).pid); | 403 | NETLINK_CB(skb).pid); |
@@ -371,6 +447,9 @@ static int fib_nl_fill_rule(struct sk_buff *skb, struct fib_rule *rule, | |||
371 | frh->action = rule->action; | 447 | frh->action = rule->action; |
372 | frh->flags = rule->flags; | 448 | frh->flags = rule->flags; |
373 | 449 | ||
450 | if (rule->action == FR_ACT_GOTO && rule->ctarget == NULL) | ||
451 | frh->flags |= FIB_RULE_UNRESOLVED; | ||
452 | |||
374 | if (rule->ifname[0]) | 453 | if (rule->ifname[0]) |
375 | NLA_PUT_STRING(skb, FRA_IFNAME, rule->ifname); | 454 | NLA_PUT_STRING(skb, FRA_IFNAME, rule->ifname); |
376 | 455 | ||
@@ -383,6 +462,9 @@ static int fib_nl_fill_rule(struct sk_buff *skb, struct fib_rule *rule, | |||
383 | if (rule->mark_mask || rule->mark) | 462 | if (rule->mark_mask || rule->mark) |
384 | NLA_PUT_U32(skb, FRA_FWMASK, rule->mark_mask); | 463 | NLA_PUT_U32(skb, FRA_FWMASK, rule->mark_mask); |
385 | 464 | ||
465 | if (rule->target) | ||
466 | NLA_PUT_U32(skb, FRA_GOTO, rule->target); | ||
467 | |||
386 | if (ops->fill(rule, skb, nlh, frh) < 0) | 468 | if (ops->fill(rule, skb, nlh, frh) < 0) |
387 | goto nla_put_failure; | 469 | goto nla_put_failure; |
388 | 470 | ||