Implement RTM notifications for ILA routers. This adds support to
ILA LWT to send a netlink RTM message when a router is uses.

The ILA notify mechanism can be used in two contexts:

  - On an ILA forwarding cache a route prefix can be configured to
    do an ILA notification. This method is used when address
    resolution needs to be done on an address.
  - One an ILA router an ILA host route entry may include a
    noitification. The purpose of this is to get a notification
    to a userspace daemon to send and ILA redirect

This patch also adds a routing protocol number for ILA.

Signed-off-by: Tom Herbert <t...@quantonium.net>
---
 include/uapi/linux/ila.h       |   2 +
 include/uapi/linux/rtnetlink.h |   1 +
 net/ipv6/ila/ila_lwt.c         | 273 ++++++++++++++++++++++++++++-------------
 3 files changed, 191 insertions(+), 85 deletions(-)

diff --git a/include/uapi/linux/ila.h b/include/uapi/linux/ila.h
index db45d3e49a12..5675f3e71fac 100644
--- a/include/uapi/linux/ila.h
+++ b/include/uapi/linux/ila.h
@@ -19,6 +19,8 @@ enum {
        ILA_ATTR_CSUM_MODE,                     /* u8 */
        ILA_ATTR_IDENT_TYPE,                    /* u8 */
        ILA_ATTR_HOOK_TYPE,                     /* u8 */
+       ILA_ATTR_NOTIFY_DST,                    /* flag */
+       ILA_ATTR_NOTIFY_SRC,                    /* flag */
 
        __ILA_ATTR_MAX,
 };
diff --git a/include/uapi/linux/rtnetlink.h b/include/uapi/linux/rtnetlink.h
index ee955c7ca48a..5da035cce640 100644
--- a/include/uapi/linux/rtnetlink.h
+++ b/include/uapi/linux/rtnetlink.h
@@ -256,6 +256,7 @@ enum {
 #define RTPROT_NTK     15      /* Netsukuku */
 #define RTPROT_DHCP    16      /* DHCP client */
 #define RTPROT_MROUTED 17      /* Multicast daemon */
+#define RTPROT_ILA     18      /* ILA route */
 #define RTPROT_BABEL   42      /* Babel daemon */
 
 /* rtm_scope
diff --git a/net/ipv6/ila/ila_lwt.c b/net/ipv6/ila/ila_lwt.c
index 9f1e46a1468e..d0ddd6f2714f 100644
--- a/net/ipv6/ila/ila_lwt.c
+++ b/net/ipv6/ila/ila_lwt.c
@@ -19,10 +19,15 @@
 struct ila_lwt {
        struct ila_params p;
        struct dst_cache dst_cache;
+       u8 hook_type;
        u32 connected : 1;
-       u32 lwt_output : 1;
+       u32 xlat : 1;
+       u32 notify : 2;
 };
 
+#define ILA_NOTIFY_DST 1
+#define ILA_NOTIFY_SRC 2
+
 static inline struct ila_lwt *ila_lwt_lwtunnel(
        struct lwtunnel_state *lwt)
 {
@@ -35,6 +40,69 @@ static inline struct ila_params *ila_params_lwtunnel(
        return &ila_lwt_lwtunnel(lwt)->p;
 }
 
+static size_t ila_rslv_msgsize(void)
+{
+       size_t len =
+               NLMSG_ALIGN(sizeof(struct rtmsg))
+               + nla_total_size(16)     /* RTA_DST */
+               + nla_total_size(16)     /* RTA_SRC */
+               ;
+
+       return len;
+}
+
+static void ila_notify(struct net *net, struct sk_buff *skb,
+                      struct ila_lwt *lwt, struct rt6_info *rt)
+{
+       struct ipv6hdr *ip6h = ipv6_hdr(skb);
+       int flags = NLM_F_MULTI;
+       struct sk_buff *nlskb;
+       struct nlmsghdr *nlh;
+       struct rtmsg *rtm;
+       int err = 0;
+
+       /* Send ILA notification to user */
+       nlskb = nlmsg_new(ila_rslv_msgsize(), GFP_KERNEL);
+       if (!nlskb)
+               return;
+
+       nlh = nlmsg_put(nlskb, 0, 0, RTM_NOTIFYROUTE, sizeof(*rtm), flags);
+       if (!nlh) {
+               err = -EMSGSIZE;
+               goto errout;
+       }
+
+       rtm = nlmsg_data(nlh);
+       rtm->rtm_family   = AF_INET6;
+       rtm->rtm_dst_len  = 128;
+       rtm->rtm_src_len  = 0;
+       rtm->rtm_tos      = 0;
+       rtm->rtm_table    = RT6_TABLE_UNSPEC;
+       rtm->rtm_type     = RTN_UNICAST;
+       rtm->rtm_scope    = RT_SCOPE_UNIVERSE;
+       rtm->rtm_protocol = rt->rt6i_protocol;
+
+       if (((lwt->notify & ILA_NOTIFY_DST) &&
+            nla_put_in6_addr(nlskb, RTA_DST, &ip6h->daddr)) ||
+           ((lwt->notify & ILA_NOTIFY_SRC) &&
+            nla_put_in6_addr(nlskb, RTA_SRC, &ip6h->saddr))) {
+               nlmsg_cancel(nlskb, nlh);
+               err = -EMSGSIZE;
+               goto errout;
+       }
+
+       nlmsg_end(nlskb, nlh);
+
+       rtnl_notify(nlskb, net, 0, RTNLGRP_ROUTE_NOTIFY, NULL, GFP_ATOMIC);
+
+       return;
+
+errout:
+       kfree_skb(nlskb);
+       WARN_ON(err == -EMSGSIZE);
+       rtnl_set_sk_err(net, RTNLGRP_ROUTE_NOTIFY, err);
+}
+
 static int ila_output(struct net *net, struct sock *sk, struct sk_buff *skb)
 {
        struct dst_entry *orig_dst = skb_dst(skb);
@@ -46,11 +114,14 @@ static int ila_output(struct net *net, struct sock *sk, 
struct sk_buff *skb)
        if (skb->protocol != htons(ETH_P_IPV6))
                goto drop;
 
-       if (ilwt->lwt_output)
+       if (ilwt->xlat)
                ila_update_ipv6_locator(skb,
                                        ila_params_lwtunnel(orig_dst->lwtstate),
                                        true);
 
+       if (ilwt->notify)
+               ila_notify(net, skb, ilwt, rt);
+
        if (rt->rt6i_flags & (RTF_GATEWAY | RTF_CACHE)) {
                /* Already have a next hop address in route, no need for
                 * dest cache route.
@@ -101,16 +172,20 @@ static int ila_output(struct net *net, struct sock *sk, 
struct sk_buff *skb)
 static int ila_input(struct sk_buff *skb)
 {
        struct dst_entry *dst = skb_dst(skb);
+       struct rt6_info *rt = (struct rt6_info *)dst;
        struct ila_lwt *ilwt = ila_lwt_lwtunnel(dst->lwtstate);
 
        if (skb->protocol != htons(ETH_P_IPV6))
                goto drop;
 
-       if (!ilwt->lwt_output)
+       if (ilwt->xlat)
                ila_update_ipv6_locator(skb,
                                        ila_params_lwtunnel(dst->lwtstate),
                                        false);
 
+       if (ilwt->notify)
+               ila_notify(dev_net(dst->dev), skb, ilwt, rt);
+
        return dst->lwtstate->orig_input(skb);
 
 drop:
@@ -123,6 +198,8 @@ static const struct nla_policy ila_nl_policy[ILA_ATTR_MAX + 
1] = {
        [ILA_ATTR_CSUM_MODE] = { .type = NLA_U8, },
        [ILA_ATTR_IDENT_TYPE] = { .type = NLA_U8, },
        [ILA_ATTR_HOOK_TYPE] = { .type = NLA_U8, },
+       [ILA_ATTR_NOTIFY_DST] = { .type = NLA_FLAG },
+       [ILA_ATTR_NOTIFY_SRC] = { .type = NLA_FLAG },
 };
 
 static int ila_build_state(struct net *net, struct nlattr *nla,
@@ -130,64 +207,73 @@ static int ila_build_state(struct net *net, struct nlattr 
*nla,
                           struct lwtunnel_state **ts,
                           struct netlink_ext_ack *extack)
 {
-       struct ila_lwt *ilwt;
-       struct ila_params *p;
-       struct nlattr *tb[ILA_ATTR_MAX + 1];
-       struct lwtunnel_state *newts;
        const struct fib6_config *cfg6 = cfg;
-       struct ila_addr *iaddr;
+       struct ila_addr *iaddr = (struct ila_addr *)&cfg6->fc_dst;
        u8 ident_type = ILA_ATYPE_USE_FORMAT;
        u8 hook_type = ILA_HOOK_ROUTE_OUTPUT;
+       struct nlattr *tb[ILA_ATTR_MAX + 1];
        u8 csum_mode = ILA_CSUM_NO_ACTION;
-       bool lwt_output = true;
+       struct lwtunnel_state *newts;
+       struct ila_lwt *ilwt;
+       struct ila_params *p;
        u8 eff_ident_type;
-       int ret;
+       int err;
 
        if (family != AF_INET6)
                return -EINVAL;
 
-       ret = nla_parse_nested(tb, ILA_ATTR_MAX, nla, ila_nl_policy, extack);
-       if (ret < 0)
-               return ret;
+       err = nla_parse_nested(tb, ILA_ATTR_MAX, nla, ila_nl_policy, extack);
+       if (err < 0)
+               return err;
 
-       if (!tb[ILA_ATTR_LOCATOR])
-               return -EINVAL;
+       if (tb[ILA_ATTR_LOCATOR]) {
+               /* Doing ILA translation */
 
-       iaddr = (struct ila_addr *)&cfg6->fc_dst;
+               if (tb[ILA_ATTR_IDENT_TYPE])
+                       ident_type = nla_get_u8(tb[ILA_ATTR_IDENT_TYPE]);
 
-       if (tb[ILA_ATTR_IDENT_TYPE])
-               ident_type = nla_get_u8(tb[ILA_ATTR_IDENT_TYPE]);
+               if (ident_type == ILA_ATYPE_USE_FORMAT) {
+                       /* Infer identifier type from type field in formatted
+                        * identifier.
+                        */
 
-       if (ident_type == ILA_ATYPE_USE_FORMAT) {
-               /* Infer identifier type from type field in formatted
-                * identifier.
-                */
+                       if (cfg6->fc_dst_len < 8 *
+                           sizeof(struct ila_locator) + 3) {
+                               /* Need to have full locator and at least type
+                                * field included in destination
+                                */
+                               return -EINVAL;
+                       }
+
+                       eff_ident_type = iaddr->ident.type;
+               } else {
+                       eff_ident_type = ident_type;
+               }
 
-               if (cfg6->fc_dst_len < 8 * sizeof(struct ila_locator) + 3) {
-                       /* Need to have full locator and at least type field
-                        * included in destination
-                        */
+               switch (eff_ident_type) {
+               case ILA_ATYPE_IID:
+                       /* Don't allow ILA for IID type */
+                       return -EINVAL;
+               case ILA_ATYPE_LUID:
+                       break;
+               case ILA_ATYPE_VIRT_V4:
+               case ILA_ATYPE_VIRT_UNI_V6:
+               case ILA_ATYPE_VIRT_MULTI_V6:
+               case ILA_ATYPE_NONLOCAL_ADDR:
+                       /* These ILA formats are not supported yet. */
+               default:
                        return -EINVAL;
                }
 
-               eff_ident_type = iaddr->ident.type;
-       } else {
-               eff_ident_type = ident_type;
-       }
+               csum_mode = nla_get_u8(tb[ILA_ATTR_CSUM_MODE]);
 
-       switch (eff_ident_type) {
-       case ILA_ATYPE_IID:
-               /* Don't allow ILA for IID type */
-               return -EINVAL;
-       case ILA_ATYPE_LUID:
-               break;
-       case ILA_ATYPE_VIRT_V4:
-       case ILA_ATYPE_VIRT_UNI_V6:
-       case ILA_ATYPE_VIRT_MULTI_V6:
-       case ILA_ATYPE_NONLOCAL_ADDR:
-               /* These ILA formats are not supported yet. */
-       default:
-               return -EINVAL;
+               if (csum_mode == ILA_CSUM_NEUTRAL_MAP &&
+                   ila_csum_neutral_set(iaddr->ident)) {
+                       /* Don't allow translation if checksum neutral bit is
+                        * configured and it's set in the SIR address.
+                        */
+                       return -EINVAL;
+               }
        }
 
        if (tb[ILA_ATTR_HOOK_TYPE])
@@ -195,58 +281,62 @@ static int ila_build_state(struct net *net, struct nlattr 
*nla,
 
        switch (hook_type) {
        case ILA_HOOK_ROUTE_OUTPUT:
-               lwt_output = true;
-               break;
        case ILA_HOOK_ROUTE_INPUT:
-               lwt_output = false;
                break;
        default:
                return -EINVAL;
        }
 
-       if (tb[ILA_ATTR_CSUM_MODE])
-               csum_mode = nla_get_u8(tb[ILA_ATTR_CSUM_MODE]);
-
-       if (csum_mode == ILA_CSUM_NEUTRAL_MAP &&
-           ila_csum_neutral_set(iaddr->ident)) {
-               /* Don't allow translation if checksum neutral bit is
-                * configured and it's set in the SIR address.
-                */
-               return -EINVAL;
-       }
-
        newts = lwtunnel_state_alloc(sizeof(*ilwt));
        if (!newts)
                return -ENOMEM;
 
        ilwt = ila_lwt_lwtunnel(newts);
-       ret = dst_cache_init(&ilwt->dst_cache, GFP_ATOMIC);
-       if (ret) {
+
+       err = dst_cache_init(&ilwt->dst_cache, GFP_ATOMIC);
+       if (err) {
                kfree(newts);
-               return ret;
+               return err;
        }
 
-       ilwt->lwt_output = !!lwt_output;
+       newts->type = LWTUNNEL_ENCAP_ILA;
 
-       p = ila_params_lwtunnel(newts);
+       switch (hook_type) {
+       case ILA_HOOK_ROUTE_OUTPUT:
+               newts->flags |= LWTUNNEL_STATE_OUTPUT_REDIRECT;
+               break;
+       case ILA_HOOK_ROUTE_INPUT:
+               newts->flags |= LWTUNNEL_STATE_INPUT_REDIRECT;
+               break;
+       }
 
-       p->csum_mode = csum_mode;
-       p->ident_type = ident_type;
-       p->locator.v64 = (__force __be64)nla_get_u64(tb[ILA_ATTR_LOCATOR]);
+       ilwt->hook_type = hook_type;
 
-       /* Precompute checksum difference for translation since we
-        * know both the old locator and the new one.
-        */
-       p->locator_match = iaddr->loc;
+       if (tb[ILA_ATTR_NOTIFY_DST])
+               ilwt->notify |= ILA_NOTIFY_DST;
 
-       ila_init_saved_csum(p);
+       if (tb[ILA_ATTR_NOTIFY_SRC])
+               ilwt->notify |= ILA_NOTIFY_SRC;
 
-       newts->type = LWTUNNEL_ENCAP_ILA;
-       newts->flags |= LWTUNNEL_STATE_OUTPUT_REDIRECT |
-                       LWTUNNEL_STATE_INPUT_REDIRECT;
+       p = ila_params_lwtunnel(newts);
+
+       if (tb[ILA_ATTR_LOCATOR]) {
+               ilwt->xlat = true;
+               p->csum_mode = csum_mode;
+               p->ident_type = ident_type;
+               p->locator.v64 = (__force __be64)nla_get_u64(
+                                                       tb[ILA_ATTR_LOCATOR]);
+
+               /* Precompute checksum difference for translation since we
+                * know both the old locator and the new one.
+                */
+               p->locator_match = iaddr->loc;
 
-       if (cfg6->fc_dst_len == 8 * sizeof(struct in6_addr))
-               ilwt->connected = 1;
+               ila_init_saved_csum(p);
+
+               if (cfg6->fc_dst_len == 8 * sizeof(struct in6_addr))
+                       ilwt->connected = 1;
+       }
 
        *ts = newts;
 
@@ -264,21 +354,32 @@ static int ila_fill_encap_info(struct sk_buff *skb,
        struct ila_params *p = ila_params_lwtunnel(lwtstate);
        struct ila_lwt *ilwt = ila_lwt_lwtunnel(lwtstate);
 
-       if (nla_put_u64_64bit(skb, ILA_ATTR_LOCATOR, (__force 
u64)p->locator.v64,
-                             ILA_ATTR_PAD))
-               goto nla_put_failure;
+       if (ilwt->xlat) {
+               if (nla_put_u64_64bit(skb, ILA_ATTR_LOCATOR,
+                                     (__force u64)p->locator.v64,
+                                     ILA_ATTR_PAD))
+                       goto nla_put_failure;
 
-       if (nla_put_u8(skb, ILA_ATTR_CSUM_MODE, (__force u8)p->csum_mode))
-               goto nla_put_failure;
+               if (nla_put_u8(skb, ILA_ATTR_CSUM_MODE,
+                              (__force u8)p->csum_mode))
+                       goto nla_put_failure;
 
-       if (nla_put_u8(skb, ILA_ATTR_IDENT_TYPE, (__force u8)p->ident_type))
-               goto nla_put_failure;
+               if (nla_put_u8(skb, ILA_ATTR_IDENT_TYPE,
+                              (__force u8)p->ident_type))
+                       goto nla_put_failure;
+       }
 
-       if (nla_put_u8(skb, ILA_ATTR_HOOK_TYPE,
-                      ilwt->lwt_output ? ILA_HOOK_ROUTE_OUTPUT :
-                                         ILA_HOOK_ROUTE_INPUT))
+       if (nla_put_u8(skb, ILA_ATTR_HOOK_TYPE, ilwt->hook_type))
                goto nla_put_failure;
 
+       if (ilwt->notify & ILA_NOTIFY_DST)
+               if (nla_put_flag(skb, ILA_ATTR_NOTIFY_DST))
+                       goto nla_put_failure;
+
+       if (ilwt->notify & ILA_NOTIFY_SRC)
+               if (nla_put_flag(skb, ILA_ATTR_NOTIFY_SRC))
+                       goto nla_put_failure;
+
        return 0;
 
 nla_put_failure:
@@ -291,6 +392,8 @@ static int ila_encap_nlsize(struct lwtunnel_state *lwtstate)
               nla_total_size(sizeof(u8)) +        /* ILA_ATTR_CSUM_MODE */
               nla_total_size(sizeof(u8)) +        /* ILA_ATTR_IDENT_TYPE */
               nla_total_size(sizeof(u8)) +        /* ILA_ATTR_HOOK_TYPE */
+              nla_total_size(0) +                 /* ILA_ATTR_NOTIFY_DST */
+              nla_total_size(0) +                 /* ILA_ATTR_NOTIFY_SRC */
               0;
 }
 
-- 
2.11.0

Reply via email to