net: fib_rules: support for match on ip_proto, sport and dport
authorRoopa Prabhu <roopa@cumulusnetworks.com>
Thu, 1 Mar 2018 03:40:16 +0000 (22:40 -0500)
committerDavid S. Miller <davem@davemloft.net>
Thu, 1 Mar 2018 03:44:43 +0000 (22:44 -0500)
uapi for ip_proto, sport and dport range match
in fib rules.

Signed-off-by: Roopa Prabhu <roopa@cumulusnetworks.com>
Acked-by: Nikolay Aleksandrov <nikolay@cumulusnetworks.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
include/net/fib_rules.h
include/uapi/linux/fib_rules.h
net/core/fib_rules.c

index b3d2162..6dd0a00 100644 (file)
@@ -27,7 +27,7 @@ struct fib_rule {
        u8                      action;
        u8                      l3mdev;
        u8                      proto;
-       /* 1 byte hole, try to use */
+       u8                      ip_proto;
        u32                     target;
        __be64                  tun_id;
        struct fib_rule __rcu   *ctarget;
@@ -40,6 +40,8 @@ struct fib_rule {
        char                    iifname[IFNAMSIZ];
        char                    oifname[IFNAMSIZ];
        struct fib_kuid_range   uid_range;
+       struct fib_rule_port_range      sport_range;
+       struct fib_rule_port_range      dport_range;
        struct rcu_head         rcu;
 };
 
@@ -144,6 +146,38 @@ static inline u32 frh_get_table(struct fib_rule_hdr *frh, struct nlattr **nla)
        return frh->table;
 }
 
+static inline bool fib_rule_port_range_set(const struct fib_rule_port_range *range)
+{
+       return range->start != 0 && range->end != 0;
+}
+
+static inline bool fib_rule_port_inrange(const struct fib_rule_port_range *a,
+                                        __be16 port)
+{
+       return ntohs(port) >= a->start &&
+               ntohs(port) <= a->end;
+}
+
+static inline bool fib_rule_port_range_valid(const struct fib_rule_port_range *a)
+{
+       return a->start != 0 && a->end != 0 && a->end < 0xffff &&
+               a->start <= a->end;
+}
+
+static inline bool fib_rule_port_range_compare(struct fib_rule_port_range *a,
+                                              struct fib_rule_port_range *b)
+{
+       return a->start == b->start &&
+               a->end == b->end;
+}
+
+static inline bool fib_rule_requires_fldissect(struct fib_rule *rule)
+{
+       return rule->ip_proto ||
+               fib_rule_port_range_set(&rule->sport_range) ||
+               fib_rule_port_range_set(&rule->dport_range);
+}
+
 struct fib_rules_ops *fib_rules_register(const struct fib_rules_ops *,
                                         struct net *);
 void fib_rules_unregister(struct fib_rules_ops *);
index 77d90ae..232df14 100644 (file)
@@ -35,6 +35,11 @@ struct fib_rule_uid_range {
        __u32           end;
 };
 
+struct fib_rule_port_range {
+       __u16           start;
+       __u16           end;
+};
+
 enum {
        FRA_UNSPEC,
        FRA_DST,        /* destination address */
@@ -59,6 +64,9 @@ enum {
        FRA_L3MDEV,     /* iif or oif is l3mdev goto its table */
        FRA_UID_RANGE,  /* UID range */
        FRA_PROTOCOL,   /* Originator of the rule */
+       FRA_IP_PROTO,   /* ip proto */
+       FRA_SPORT_RANGE, /* sport */
+       FRA_DPORT_RANGE, /* dport */
        __FRA_MAX
 };
 
index a6aea80..f6f04fc 100644 (file)
@@ -33,6 +33,10 @@ bool fib_rule_matchall(const struct fib_rule *rule)
        if (!uid_eq(rule->uid_range.start, fib_kuid_range_unset.start) ||
            !uid_eq(rule->uid_range.end, fib_kuid_range_unset.end))
                return false;
+       if (fib_rule_port_range_set(&rule->sport_range))
+               return false;
+       if (fib_rule_port_range_set(&rule->dport_range))
+               return false;
        return true;
 }
 EXPORT_SYMBOL_GPL(fib_rule_matchall);
@@ -221,6 +225,26 @@ static int nla_put_uid_range(struct sk_buff *skb, struct fib_kuid_range *range)
        return nla_put(skb, FRA_UID_RANGE, sizeof(out), &out);
 }
 
+static int nla_get_port_range(struct nlattr *pattr,
+                             struct fib_rule_port_range *port_range)
+{
+       const struct fib_rule_port_range *pr = nla_data(pattr);
+
+       if (!fib_rule_port_range_valid(pr))
+               return -EINVAL;
+
+       port_range->start = pr->start;
+       port_range->end = pr->end;
+
+       return 0;
+}
+
+static int nla_put_port_range(struct sk_buff *skb, int attrtype,
+                             struct fib_rule_port_range *range)
+{
+       return nla_put(skb, attrtype, sizeof(*range), range);
+}
+
 static int fib_rule_match(struct fib_rule *rule, struct fib_rules_ops *ops,
                          struct flowi *fl, int flags,
                          struct fib_lookup_arg *arg)
@@ -425,6 +449,17 @@ static int rule_exists(struct fib_rules_ops *ops, struct fib_rule_hdr *frh,
                    !uid_eq(r->uid_range.end, rule->uid_range.end))
                        continue;
 
+               if (r->ip_proto != rule->ip_proto)
+                       continue;
+
+               if (!fib_rule_port_range_compare(&r->sport_range,
+                                                &rule->sport_range))
+                       continue;
+
+               if (!fib_rule_port_range_compare(&r->dport_range,
+                                                &rule->dport_range))
+                       continue;
+
                if (!ops->compare(r, frh, tb))
                        continue;
                return 1;
@@ -569,6 +604,23 @@ int fib_nl_newrule(struct sk_buff *skb, struct nlmsghdr *nlh,
                rule->uid_range = fib_kuid_range_unset;
        }
 
+       if (tb[FRA_IP_PROTO])
+               rule->ip_proto = nla_get_u8(tb[FRA_IP_PROTO]);
+
+       if (tb[FRA_SPORT_RANGE]) {
+               err = nla_get_port_range(tb[FRA_SPORT_RANGE],
+                                        &rule->sport_range);
+               if (err)
+                       goto errout_free;
+       }
+
+       if (tb[FRA_DPORT_RANGE]) {
+               err = nla_get_port_range(tb[FRA_DPORT_RANGE],
+                                        &rule->dport_range);
+               if (err)
+                       goto errout_free;
+       }
+
        if ((nlh->nlmsg_flags & NLM_F_EXCL) &&
            rule_exists(ops, frh, tb, rule)) {
                err = -EEXIST;
@@ -634,6 +686,8 @@ int fib_nl_delrule(struct sk_buff *skb, struct nlmsghdr *nlh,
 {
        struct net *net = sock_net(skb->sk);
        struct fib_rule_hdr *frh = nlmsg_data(nlh);
+       struct fib_rule_port_range sprange = {0, 0};
+       struct fib_rule_port_range dprange = {0, 0};
        struct fib_rules_ops *ops = NULL;
        struct fib_rule *rule, *r;
        struct nlattr *tb[FRA_MAX+1];
@@ -667,6 +721,20 @@ int fib_nl_delrule(struct sk_buff *skb, struct nlmsghdr *nlh,
                range = fib_kuid_range_unset;
        }
 
+       if (tb[FRA_SPORT_RANGE]) {
+               err = nla_get_port_range(tb[FRA_SPORT_RANGE],
+                                        &sprange);
+               if (err)
+                       goto errout;
+       }
+
+       if (tb[FRA_DPORT_RANGE]) {
+               err = nla_get_port_range(tb[FRA_DPORT_RANGE],
+                                        &dprange);
+               if (err)
+                       goto errout;
+       }
+
        list_for_each_entry(rule, &ops->rules_list, list) {
                if (tb[FRA_PROTOCOL] &&
                    (rule->proto != nla_get_u8(tb[FRA_PROTOCOL])))
@@ -712,6 +780,18 @@ int fib_nl_delrule(struct sk_buff *skb, struct nlmsghdr *nlh,
                     !uid_eq(rule->uid_range.end, range.end)))
                        continue;
 
+               if (tb[FRA_IP_PROTO] &&
+                   (rule->ip_proto != nla_get_u8(tb[FRA_IP_PROTO])))
+                       continue;
+
+               if (fib_rule_port_range_set(&sprange) &&
+                   !fib_rule_port_range_compare(&rule->sport_range, &sprange))
+                       continue;
+
+               if (fib_rule_port_range_set(&dprange) &&
+                   !fib_rule_port_range_compare(&rule->dport_range, &dprange))
+                       continue;
+
                if (!ops->compare(rule, frh, tb))
                        continue;
 
@@ -790,7 +870,10 @@ static inline size_t fib_rule_nlmsg_size(struct fib_rules_ops *ops,
                         + nla_total_size(4) /* FRA_FWMASK */
                         + nla_total_size_64bit(8) /* FRA_TUN_ID */
                         + nla_total_size(sizeof(struct fib_kuid_range))
-                        + nla_total_size(1); /* FRA_PROTOCOL */
+                        + nla_total_size(1) /* FRA_PROTOCOL */
+                        + nla_total_size(1) /* FRA_IP_PROTO */
+                        + nla_total_size(sizeof(struct fib_rule_port_range)) /* FRA_SPORT_RANGE */
+                        + nla_total_size(sizeof(struct fib_rule_port_range)); /* FRA_DPORT_RANGE */
 
        if (ops->nlmsg_payload)
                payload += ops->nlmsg_payload(rule);
@@ -855,7 +938,12 @@ static int fib_nl_fill_rule(struct sk_buff *skb, struct fib_rule *rule,
            (rule->l3mdev &&
             nla_put_u8(skb, FRA_L3MDEV, rule->l3mdev)) ||
            (uid_range_set(&rule->uid_range) &&
-            nla_put_uid_range(skb, &rule->uid_range)))
+            nla_put_uid_range(skb, &rule->uid_range)) ||
+           (fib_rule_port_range_set(&rule->sport_range) &&
+            nla_put_port_range(skb, FRA_SPORT_RANGE, &rule->sport_range)) ||
+           (fib_rule_port_range_set(&rule->dport_range) &&
+            nla_put_port_range(skb, FRA_DPORT_RANGE, &rule->dport_range)) ||
+           (rule->ip_proto && nla_put_u8(skb, FRA_IP_PROTO, rule->ip_proto)))
                goto nla_put_failure;
 
        if (rule->suppress_ifgroup != -1) {