Commit 0947c9fe authored by Thomas Graf's avatar Thomas Graf Committed by David S. Miller

[NET] fib_rules: goto rule action

This patch adds a new rule action FR_ACT_GOTO which allows
to skip a set of rules by jumping to another rule. The rule
to jump to is specified via the FRA_GOTO attribute which
carries a rule preference.

Referring to a rule which doesn't exists is explicitely allowed.
Such goto rules are marked with the flag FIB_RULE_UNRESOLVED
and will act like a rule with a non-matching selector. The rule
will become functional as soon as its target is present.

The goto action enables performance optimizations by reducing
the average number of rules that have to be passed per lookup.

Example:
0:      from all lookup local
40:     not from all to 192.168.23.128 goto 32766
41:     from all fwmark 0xa blackhole
42:     from all fwmark 0xff blackhole
32766:  from all lookup main
Signed-off-by: default avatarThomas Graf <tgraf@suug.ch>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent 2f7826c0
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
/* rule is permanent, and cannot be deleted */ /* rule is permanent, and cannot be deleted */
#define FIB_RULE_PERMANENT 1 #define FIB_RULE_PERMANENT 1
#define FIB_RULE_INVERT 2 #define FIB_RULE_INVERT 2
#define FIB_RULE_UNRESOLVED 4
struct fib_rule_hdr struct fib_rule_hdr
{ {
...@@ -29,7 +30,7 @@ enum ...@@ -29,7 +30,7 @@ enum
FRA_DST, /* destination address */ FRA_DST, /* destination address */
FRA_SRC, /* source address */ FRA_SRC, /* source address */
FRA_IFNAME, /* interface name */ FRA_IFNAME, /* interface name */
FRA_UNUSED1, FRA_GOTO, /* target to jump to (FR_ACT_GOTO) */
FRA_UNUSED2, FRA_UNUSED2,
FRA_PRIORITY, /* priority/preference */ FRA_PRIORITY, /* priority/preference */
FRA_UNUSED3, FRA_UNUSED3,
...@@ -51,7 +52,7 @@ enum ...@@ -51,7 +52,7 @@ enum
{ {
FR_ACT_UNSPEC, FR_ACT_UNSPEC,
FR_ACT_TO_TBL, /* Pass to fixed table */ FR_ACT_TO_TBL, /* Pass to fixed table */
FR_ACT_RES1, FR_ACT_GOTO, /* Jump to another rule */
FR_ACT_RES2, FR_ACT_RES2,
FR_ACT_RES3, FR_ACT_RES3,
FR_ACT_RES4, FR_ACT_RES4,
......
...@@ -19,6 +19,8 @@ struct fib_rule ...@@ -19,6 +19,8 @@ struct fib_rule
u32 flags; u32 flags;
u32 table; u32 table;
u8 action; u8 action;
u32 target;
struct fib_rule * ctarget;
struct rcu_head rcu; struct rcu_head rcu;
}; };
...@@ -35,6 +37,8 @@ struct fib_rules_ops ...@@ -35,6 +37,8 @@ struct fib_rules_ops
struct list_head list; struct list_head list;
int rule_size; int rule_size;
int addr_size; int addr_size;
int unresolved_rules;
int nr_goto_rules;
int (*action)(struct fib_rule *, int (*action)(struct fib_rule *,
struct flowi *, int, struct flowi *, int,
...@@ -66,7 +70,8 @@ struct fib_rules_ops ...@@ -66,7 +70,8 @@ struct fib_rules_ops
[FRA_PRIORITY] = { .type = NLA_U32 }, \ [FRA_PRIORITY] = { .type = NLA_U32 }, \
[FRA_FWMARK] = { .type = NLA_U32 }, \ [FRA_FWMARK] = { .type = NLA_U32 }, \
[FRA_FWMASK] = { .type = NLA_U32 }, \ [FRA_FWMASK] = { .type = NLA_U32 }, \
[FRA_TABLE] = { .type = NLA_U32 } [FRA_TABLE] = { .type = NLA_U32 }, \
[FRA_GOTO] = { .type = NLA_U32 }
static inline void fib_rule_get(struct fib_rule *rule) static inline void fib_rule_get(struct fib_rule *rule)
{ {
......
...@@ -132,10 +132,23 @@ int fib_rules_lookup(struct fib_rules_ops *ops, struct flowi *fl, ...@@ -132,10 +132,23 @@ int fib_rules_lookup(struct fib_rules_ops *ops, struct flowi *fl,
rcu_read_lock(); rcu_read_lock();
list_for_each_entry_rcu(rule, ops->rules_list, list) { list_for_each_entry_rcu(rule, ops->rules_list, list) {
jumped:
if (!fib_rule_match(rule, ops, fl, flags)) if (!fib_rule_match(rule, ops, fl, flags))
continue; continue;
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); err = ops->action(rule, fl, flags, arg);
if (err != -EAGAIN) { if (err != -EAGAIN) {
fib_rule_get(rule); fib_rule_get(rule);
arg->rule = rule; arg->rule = rule;
...@@ -180,7 +193,7 @@ static int fib_nl_newrule(struct sk_buff *skb, struct nlmsghdr* nlh, void *arg) ...@@ -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_rules_ops *ops = NULL;
struct fib_rule *rule, *r, *last = NULL; struct fib_rule *rule, *r, *last = NULL;
struct nlattr *tb[FRA_MAX+1]; struct nlattr *tb[FRA_MAX+1];
int err = -EINVAL; int err = -EINVAL, unresolved = 0;
if (nlh->nlmsg_len < nlmsg_msg_size(sizeof(*frh))) if (nlh->nlmsg_len < nlmsg_msg_size(sizeof(*frh)))
goto errout; goto errout;
...@@ -237,6 +250,28 @@ static int fib_nl_newrule(struct sk_buff *skb, struct nlmsghdr* nlh, void *arg) ...@@ -237,6 +250,28 @@ static int fib_nl_newrule(struct sk_buff *skb, struct nlmsghdr* nlh, void *arg)
if (!rule->pref && ops->default_pref) if (!rule->pref && ops->default_pref)
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); err = ops->configure(rule, skb, nlh, frh, tb);
if (err < 0) if (err < 0)
goto errout_free; goto errout_free;
...@@ -249,6 +284,28 @@ static int fib_nl_newrule(struct sk_buff *skb, struct nlmsghdr* nlh, void *arg) ...@@ -249,6 +284,28 @@ static int fib_nl_newrule(struct sk_buff *skb, struct nlmsghdr* nlh, void *arg)
fib_rule_get(rule); 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) if (last)
list_add_rcu(&rule->list, &last->list); list_add_rcu(&rule->list, &last->list);
else else
...@@ -269,7 +326,7 @@ static int fib_nl_delrule(struct sk_buff *skb, struct nlmsghdr* nlh, void *arg) ...@@ -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_rule_hdr *frh = nlmsg_data(nlh);
struct fib_rules_ops *ops = NULL; struct fib_rules_ops *ops = NULL;
struct fib_rule *rule; struct fib_rule *rule, *tmp;
struct nlattr *tb[FRA_MAX+1]; struct nlattr *tb[FRA_MAX+1];
int err = -EINVAL; int err = -EINVAL;
...@@ -322,6 +379,25 @@ static int fib_nl_delrule(struct sk_buff *skb, struct nlmsghdr* nlh, void *arg) ...@@ -322,6 +379,25 @@ static int fib_nl_delrule(struct sk_buff *skb, struct nlmsghdr* nlh, void *arg)
} }
list_del_rcu(&rule->list); 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(); synchronize_rcu();
notify_rule_change(RTM_DELRULE, rule, ops, nlh, notify_rule_change(RTM_DELRULE, rule, ops, nlh,
NETLINK_CB(skb).pid); NETLINK_CB(skb).pid);
...@@ -371,6 +447,9 @@ static int fib_nl_fill_rule(struct sk_buff *skb, struct fib_rule *rule, ...@@ -371,6 +447,9 @@ static int fib_nl_fill_rule(struct sk_buff *skb, struct fib_rule *rule,
frh->action = rule->action; frh->action = rule->action;
frh->flags = rule->flags; frh->flags = rule->flags;
if (rule->action == FR_ACT_GOTO && rule->ctarget == NULL)
frh->flags |= FIB_RULE_UNRESOLVED;
if (rule->ifname[0]) if (rule->ifname[0])
NLA_PUT_STRING(skb, FRA_IFNAME, rule->ifname); 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, ...@@ -383,6 +462,9 @@ static int fib_nl_fill_rule(struct sk_buff *skb, struct fib_rule *rule,
if (rule->mark_mask || rule->mark) if (rule->mark_mask || rule->mark)
NLA_PUT_U32(skb, FRA_FWMASK, rule->mark_mask); 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) if (ops->fill(rule, skb, nlh, frh) < 0)
goto nla_put_failure; goto nla_put_failure;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment