]> Pileus Git - ~andy/linux/blobdiff - net/core/fib_rules.c
[NET] fib_rules: goto rule action
[~andy/linux] / net / core / fib_rules.c
index fdf05af16ba50bd71a7cd03b0963692797799913..0d8bb2efb0c1ba8fac29b54668e6105c95bce5e6 100644 (file)
@@ -132,10 +132,23 @@ int fib_rules_lookup(struct fib_rules_ops *ops, struct flowi *fl,
        rcu_read_lock();
 
        list_for_each_entry_rcu(rule, ops->rules_list, list) {
+jumped:
                if (!fib_rule_match(rule, ops, fl, flags))
                        continue;
 
-               err = ops->action(rule, fl, flags, arg);
+               if (rule->action == FR_ACT_GOTO) {
+                       struct fib_rule *target;
+
+                       target = rcu_dereference(rule->ctarget);
+                       if (target == NULL) {
+                               continue;
+                       } else {
+                               rule = target;
+                               goto jumped;
+                       }
+               } else
+                       err = ops->action(rule, fl, flags, arg);
+
                if (err != -EAGAIN) {
                        fib_rule_get(rule);
                        arg->rule = rule;
@@ -180,7 +193,7 @@ static int fib_nl_newrule(struct sk_buff *skb, struct nlmsghdr* nlh, void *arg)
        struct fib_rules_ops *ops = NULL;
        struct fib_rule *rule, *r, *last = NULL;
        struct nlattr *tb[FRA_MAX+1];
-       int err = -EINVAL;
+       int err = -EINVAL, unresolved = 0;
 
        if (nlh->nlmsg_len < nlmsg_msg_size(sizeof(*frh)))
                goto errout;
@@ -237,6 +250,28 @@ static int fib_nl_newrule(struct sk_buff *skb, struct nlmsghdr* nlh, void *arg)
        if (!rule->pref && ops->default_pref)
                rule->pref = ops->default_pref();
 
+       err = -EINVAL;
+       if (tb[FRA_GOTO]) {
+               if (rule->action != FR_ACT_GOTO)
+                       goto errout_free;
+
+               rule->target = nla_get_u32(tb[FRA_GOTO]);
+               /* Backward jumps are prohibited to avoid endless loops */
+               if (rule->target <= rule->pref)
+                       goto errout_free;
+
+               list_for_each_entry(r, ops->rules_list, list) {
+                       if (r->pref == rule->target) {
+                               rule->ctarget = r;
+                               break;
+                       }
+               }
+
+               if (rule->ctarget == NULL)
+                       unresolved = 1;
+       } else if (rule->action == FR_ACT_GOTO)
+               goto errout_free;
+
        err = ops->configure(rule, skb, nlh, frh, tb);
        if (err < 0)
                goto errout_free;
@@ -249,6 +284,28 @@ static int fib_nl_newrule(struct sk_buff *skb, struct nlmsghdr* nlh, void *arg)
 
        fib_rule_get(rule);
 
+       if (ops->unresolved_rules) {
+               /*
+                * There are unresolved goto rules in the list, check if
+                * any of them are pointing to this new rule.
+                */
+               list_for_each_entry(r, ops->rules_list, list) {
+                       if (r->action == FR_ACT_GOTO &&
+                           r->target == rule->pref) {
+                               BUG_ON(r->ctarget != NULL);
+                               rcu_assign_pointer(r->ctarget, rule);
+                               if (--ops->unresolved_rules == 0)
+                                       break;
+                       }
+               }
+       }
+
+       if (rule->action == FR_ACT_GOTO)
+               ops->nr_goto_rules++;
+
+       if (unresolved)
+               ops->unresolved_rules++;
+
        if (last)
                list_add_rcu(&rule->list, &last->list);
        else
@@ -269,7 +326,7 @@ static int fib_nl_delrule(struct sk_buff *skb, struct nlmsghdr* nlh, void *arg)
 {
        struct fib_rule_hdr *frh = nlmsg_data(nlh);
        struct fib_rules_ops *ops = NULL;
-       struct fib_rule *rule;
+       struct fib_rule *rule, *tmp;
        struct nlattr *tb[FRA_MAX+1];
        int err = -EINVAL;
 
@@ -322,6 +379,25 @@ static int fib_nl_delrule(struct sk_buff *skb, struct nlmsghdr* nlh, void *arg)
                }
 
                list_del_rcu(&rule->list);
+
+               if (rule->action == FR_ACT_GOTO)
+                       ops->nr_goto_rules--;
+
+               /*
+                * Check if this rule is a target to any of them. If so,
+                * disable them. As this operation is eventually very
+                * expensive, it is only performed if goto rules have
+                * actually been added.
+                */
+               if (ops->nr_goto_rules > 0) {
+                       list_for_each_entry(tmp, ops->rules_list, list) {
+                               if (tmp->ctarget == rule) {
+                                       rcu_assign_pointer(tmp->ctarget, NULL);
+                                       ops->unresolved_rules++;
+                               }
+                       }
+               }
+
                synchronize_rcu();
                notify_rule_change(RTM_DELRULE, rule, ops, nlh,
                                   NETLINK_CB(skb).pid);
@@ -371,6 +447,9 @@ static int fib_nl_fill_rule(struct sk_buff *skb, struct fib_rule *rule,
        frh->action = rule->action;
        frh->flags = rule->flags;
 
+       if (rule->action == FR_ACT_GOTO && rule->ctarget == NULL)
+               frh->flags |= FIB_RULE_UNRESOLVED;
+
        if (rule->ifname[0])
                NLA_PUT_STRING(skb, FRA_IFNAME, rule->ifname);
 
@@ -383,6 +462,9 @@ static int fib_nl_fill_rule(struct sk_buff *skb, struct fib_rule *rule,
        if (rule->mark_mask || rule->mark)
                NLA_PUT_U32(skb, FRA_FWMASK, rule->mark_mask);
 
+       if (rule->target)
+               NLA_PUT_U32(skb, FRA_GOTO, rule->target);
+
        if (ops->fill(rule, skb, nlh, frh) < 0)
                goto nla_put_failure;