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;
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;
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;
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
{
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;
}
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);
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);
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;