|
@@ -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;
|
|
|
|