diff options
Diffstat (limited to 'net/core')
-rw-r--r-- | net/core/fib_rules.c | 88 |
1 files changed, 85 insertions, 3 deletions
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 | ||