Implement an ILA resolver. This uses LWT to implement the hook to a
userspace resolver and tracks pending unresolved address using the
backend net resolver.

The idea is that the kernel sets an ILA resolver route to the
SIR prefix, something like:

ip route add 3333::/64 encap ila-resolve \
     via 2401:db00:20:911a::27:0 dev eth0

When a packet hits the route the address is looked up in a resolver
table. If the entry is created (no entry with the address already
exists) then an rtnl message is generated with group
RTNLGRP_ILA_NOTIFY and type RTM_ADDR_RESOLVE. A userspace
daemon can listen for such messages and perform an ILA resolution
protocol to determine the ILA mapping. If the mapping is resolved
then a /128 ila encap router is set so that host can perform
ILA translation and send directly to destination.

Signed-off-by: Tom Herbert <t...@quantonium.net>
---
 include/uapi/linux/ila.h       |   9 ++
 include/uapi/linux/lwtunnel.h  |   1 +
 include/uapi/linux/rtnetlink.h |   8 +-
 net/core/lwtunnel.c            |   2 +
 net/ipv6/Kconfig               |   1 +
 net/ipv6/ila/Makefile          |   2 +-
 net/ipv6/ila/ila.h             |  11 ++
 net/ipv6/ila/ila_lwt.c         |   8 ++
 net/ipv6/ila/ila_main.c        |  14 +++
 net/ipv6/ila/ila_resolver.c    | 244 +++++++++++++++++++++++++++++++++++++++++
 10 files changed, 298 insertions(+), 2 deletions(-)
 create mode 100644 net/ipv6/ila/ila_resolver.c

diff --git a/include/uapi/linux/ila.h b/include/uapi/linux/ila.h
index db45d3e49a12..66557265bf5b 100644
--- a/include/uapi/linux/ila.h
+++ b/include/uapi/linux/ila.h
@@ -65,4 +65,13 @@ enum {
        ILA_HOOK_ROUTE_INPUT,
 };
 
+enum {
+       ILA_NOTIFY_ATTR_UNSPEC,
+       ILA_NOTIFY_ATTR_TIMEOUT,                /* u32 */
+
+       __ILA_NOTIFY_ATTR_MAX,
+};
+
+#define ILA_NOTIFY_ATTR_MAX    (__ILA_NOTIFY_ATTR_MAX - 1)
+
 #endif /* _UAPI_LINUX_ILA_H */
diff --git a/include/uapi/linux/lwtunnel.h b/include/uapi/linux/lwtunnel.h
index de696ca12f2c..2eac16f8323f 100644
--- a/include/uapi/linux/lwtunnel.h
+++ b/include/uapi/linux/lwtunnel.h
@@ -13,6 +13,7 @@ enum lwtunnel_encap_types {
        LWTUNNEL_ENCAP_SEG6,
        LWTUNNEL_ENCAP_BPF,
        LWTUNNEL_ENCAP_SEG6_LOCAL,
+       LWTUNNEL_ENCAP_ILA_NOTIFY,
        __LWTUNNEL_ENCAP_MAX,
 };
 
diff --git a/include/uapi/linux/rtnetlink.h b/include/uapi/linux/rtnetlink.h
index d8b5f80c2ea6..8d358a300d8a 100644
--- a/include/uapi/linux/rtnetlink.h
+++ b/include/uapi/linux/rtnetlink.h
@@ -13,7 +13,8 @@
  */
 #define RTNL_FAMILY_IPMR               128
 #define RTNL_FAMILY_IP6MR              129
-#define RTNL_FAMILY_MAX                        129
+#define RTNL_FAMILY_ILA                        130
+#define RTNL_FAMILY_MAX                        130
 
 /****
  *             Routing/neighbour discovery messages.
@@ -150,6 +151,9 @@ enum {
        RTM_NEWCACHEREPORT = 96,
 #define RTM_NEWCACHEREPORT RTM_NEWCACHEREPORT
 
+       RTM_ADDR_RESOLVE = 98,
+#define RTM_ADDR_RESOLVE RTM_ADDR_RESOLVE
+
        __RTM_MAX,
 #define RTM_MAX                (((__RTM_MAX + 3) & ~3) - 1)
 };
@@ -676,6 +680,8 @@ enum rtnetlink_groups {
 #define RTNLGRP_IPV4_MROUTE_R  RTNLGRP_IPV4_MROUTE_R
        RTNLGRP_IPV6_MROUTE_R,
 #define RTNLGRP_IPV6_MROUTE_R  RTNLGRP_IPV6_MROUTE_R
+       RTNLGRP_ILA_NOTIFY,
+#define RTNLGRP_ILA_NOTIFY     RTNLGRP_ILA_NOTIFY
        __RTNLGRP_MAX
 };
 #define RTNLGRP_MAX    (__RTNLGRP_MAX - 1)
diff --git a/net/core/lwtunnel.c b/net/core/lwtunnel.c
index b3f2f77dfe72..16b04d05e9b9 100644
--- a/net/core/lwtunnel.c
+++ b/net/core/lwtunnel.c
@@ -46,6 +46,8 @@ static const char *lwtunnel_encap_str(enum 
lwtunnel_encap_types encap_type)
                return "BPF";
        case LWTUNNEL_ENCAP_SEG6_LOCAL:
                return "SEG6LOCAL";
+       case LWTUNNEL_ENCAP_ILA_NOTIFY:
+               return "ILA-NOTIFY";
        case LWTUNNEL_ENCAP_IP6:
        case LWTUNNEL_ENCAP_IP:
        case LWTUNNEL_ENCAP_NONE:
diff --git a/net/ipv6/Kconfig b/net/ipv6/Kconfig
index ea71e4b0ab7a..5b0a6e1bd7cc 100644
--- a/net/ipv6/Kconfig
+++ b/net/ipv6/Kconfig
@@ -110,6 +110,7 @@ config IPV6_ILA
        tristate "IPv6: Identifier Locator Addressing (ILA)"
        depends on NETFILTER
        select LWTUNNEL
+       select NET_RESOLVER
        ---help---
          Support for IPv6 Identifier Locator Addressing (ILA).
 
diff --git a/net/ipv6/ila/Makefile b/net/ipv6/ila/Makefile
index b7739aba6e68..3ec2d65ceee2 100644
--- a/net/ipv6/ila/Makefile
+++ b/net/ipv6/ila/Makefile
@@ -4,4 +4,4 @@
 
 obj-$(CONFIG_IPV6_ILA) += ila.o
 
-ila-objs := ila_main.o ila_common.o ila_lwt.o ila_xlat.o
+ila-objs := ila_main.o ila_common.o ila_lwt.o ila_xlat.o ila_resolver.o
diff --git a/net/ipv6/ila/ila.h b/net/ipv6/ila/ila.h
index 1f747bcbec29..02a800c71796 100644
--- a/net/ipv6/ila/ila.h
+++ b/net/ipv6/ila/ila.h
@@ -15,6 +15,7 @@
 #include <linux/ip.h>
 #include <linux/kernel.h>
 #include <linux/module.h>
+#include <linux/rhashtable.h>
 #include <linux/socket.h>
 #include <linux/skbuff.h>
 #include <linux/types.h>
@@ -112,6 +113,9 @@ struct ila_net {
                unsigned int locks_mask;
                bool hooks_registered;
        } xlat;
+       struct {
+               struct net_rslv *nrslv;
+       } rslv;
 };
 
 int ila_lwt_init(void);
@@ -120,6 +124,11 @@ void ila_lwt_fini(void);
 int ila_xlat_init_net(struct net *net);
 void ila_xlat_exit_net(struct net *net);
 
+int ila_rslv_init(void);
+void ila_rslv_fini(void);
+int ila_rslv_init_net(struct net *net);
+void ila_rslv_exit_net(struct net *net);
+
 int ila_xlat_nl_cmd_add_mapping(struct sk_buff *skb, struct genl_info *info);
 int ila_xlat_nl_cmd_del_mapping(struct sk_buff *skb, struct genl_info *info);
 int ila_xlat_nl_cmd_get_mapping(struct sk_buff *skb, struct genl_info *info);
@@ -132,4 +141,6 @@ extern unsigned int ila_net_id;
 
 extern struct genl_family ila_nl_family;
 
+void ila_rslv_resolved(struct ila_net *ilan, struct ila_addr *iaddr);
+
 #endif /* __ILA_H */
diff --git a/net/ipv6/ila/ila_lwt.c b/net/ipv6/ila/ila_lwt.c
index 9f1e46a1468e..20dddc7ea8d0 100644
--- a/net/ipv6/ila/ila_lwt.c
+++ b/net/ipv6/ila/ila_lwt.c
@@ -12,6 +12,7 @@
 #include <net/ip6_fib.h>
 #include <net/ip6_route.h>
 #include <net/lwtunnel.h>
+#include <net/netns/generic.h>
 #include <net/protocol.h>
 #include <uapi/linux/ila.h>
 #include "ila.h"
@@ -250,6 +251,13 @@ static int ila_build_state(struct net *net, struct nlattr 
*nla,
 
        *ts = newts;
 
+       if (cfg6->fc_dst_len >= sizeof(struct ila_addr)) {
+               struct ila_net *ilan = net_generic(net, ila_net_id);
+
+               /* Cancel any pending resolution on this address */
+               ila_rslv_resolved(ilan, iaddr);
+       }
+
        return 0;
 }
 
diff --git a/net/ipv6/ila/ila_main.c b/net/ipv6/ila/ila_main.c
index 18fac76b9520..411d3d112157 100644
--- a/net/ipv6/ila/ila_main.c
+++ b/net/ipv6/ila/ila_main.c
@@ -64,14 +64,21 @@ static __net_init int ila_init_net(struct net *net)
        if (err)
                goto ila_xlat_init_fail;
 
+       err = ila_rslv_init_net(net);
+       if (err)
+               goto resolver_init_fail;
+
        return 0;
 
 ila_xlat_init_fail:
+       ila_xlat_exit_net(net);
+resolver_init_fail:
        return err;
 }
 
 static __net_exit void ila_exit_net(struct net *net)
 {
+       ila_rslv_exit_net(net);
        ila_xlat_exit_net(net);
 }
 
@@ -98,8 +105,14 @@ static int __init ila_init(void)
        if (ret)
                goto fail_lwt;
 
+       ret = ila_rslv_init();
+       if (ret)
+               goto fail_rslv;
+
        return 0;
 
+fail_rslv:
+       ila_lwt_fini();
 fail_lwt:
        genl_unregister_family(&ila_nl_family);
 register_family_fail:
@@ -110,6 +123,7 @@ static int __init ila_init(void)
 
 static void __exit ila_fini(void)
 {
+       ila_rslv_fini();
        ila_lwt_fini();
        genl_unregister_family(&ila_nl_family);
        unregister_pernet_device(&ila_net_ops);
diff --git a/net/ipv6/ila/ila_resolver.c b/net/ipv6/ila/ila_resolver.c
new file mode 100644
index 000000000000..8b9a3c5305a4
--- /dev/null
+++ b/net/ipv6/ila/ila_resolver.c
@@ -0,0 +1,244 @@
+/*
+ * net/core/ila_resolver.c - ILA address resolver
+ *
+ * Copyright (c) 2017 Tom Herbert <t...@quantonium.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+#include <linux/errno.h>
+#include <linux/ip.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/netlink.h>
+#include <linux/skbuff.h>
+#include <linux/types.h>
+#include <net/ip6_fib.h>
+#include <net/lwtunnel.h>
+#include <net/netns/generic.h>
+#include <net/resolver.h>
+#include <uapi/linux/ila.h>
+#include "ila.h"
+
+struct ila_notify_params {
+       unsigned int timeout;
+};
+
+static inline struct ila_notify_params *ila_notify_params_lwtunnel(
+       struct lwtunnel_state *lwstate)
+{
+       return (struct ila_notify_params *)lwstate->data;
+}
+
+static int ila_fill_notify(struct sk_buff *skb, struct in6_addr *addr,
+                          u32 pid, u32 seq, int event, int flags)
+{
+       struct nlmsghdr *nlh;
+       struct rtmsg *rtm;
+
+       nlh = nlmsg_put(skb, pid, seq, event, sizeof(*rtm), flags);
+       if (!nlh)
+               return -EMSGSIZE;
+
+       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;
+
+       if (nla_put_in6_addr(skb, RTA_DST, addr)) {
+               nlmsg_cancel(skb, nlh);
+               return -EMSGSIZE;
+       }
+
+       nlmsg_end(skb, nlh);
+       return 0;
+}
+
+static size_t ila_rslv_msgsize(void)
+{
+       size_t len =
+               NLMSG_ALIGN(sizeof(struct rtmsg))
+               + nla_total_size(16)     /* RTA_DST */
+               ;
+
+       return len;
+}
+
+void ila_rslv_notify(struct net *net, struct sk_buff *skb)
+{
+       struct ipv6hdr *ip6h = ipv6_hdr(skb);
+       struct sk_buff *nlskb;
+       int err = 0;
+
+       /* Send ILA notification to user */
+       nlskb = nlmsg_new(ila_rslv_msgsize(), GFP_KERNEL);
+       if (!nlskb)
+               goto errout;
+
+       err = ila_fill_notify(nlskb, &ip6h->daddr, 0, 0, RTM_ADDR_RESOLVE,
+                             NLM_F_MULTI);
+       if (err < 0) {
+               WARN_ON(err == -EMSGSIZE);
+               kfree_skb(nlskb);
+               goto errout;
+       }
+       rtnl_notify(nlskb, net, 0, RTNLGRP_ILA_NOTIFY, NULL, GFP_ATOMIC);
+       return;
+
+errout:
+       if (err < 0)
+               rtnl_set_sk_err(net, RTNLGRP_ILA_NOTIFY, err);
+}
+
+static int ila_rslv_output(struct net *net, struct sock *sk,
+                          struct sk_buff *skb)
+{
+       struct ila_net *ilan = net_generic(net, ila_net_id);
+       struct dst_entry *dst = skb_dst(skb);
+       struct ipv6hdr *ip6h = ipv6_hdr(skb);
+       struct ila_notify_params *p;
+
+       p = ila_notify_params_lwtunnel(dst->lwtstate);
+
+       /* Net resolver create function returns zero only when a new
+        * entry is create (returns -EEXIST is entry already in table)..
+        */
+       if (!net_rslv_lookup_and_create(ilan->rslv.nrslv, &ip6h->daddr,
+                                       p->timeout))
+               ila_rslv_notify(net, skb);
+
+       return dst->lwtstate->orig_output(net, sk, skb);
+}
+
+void ila_rslv_resolved(struct ila_net *ilan, struct ila_addr *iaddr)
+{
+       if (ilan->rslv.nrslv)
+               net_rslv_resolved(ilan->rslv.nrslv, iaddr);
+}
+
+static int ila_rslv_input(struct sk_buff *skb)
+{
+       struct dst_entry *dst = skb_dst(skb);
+
+       return dst->lwtstate->orig_input(skb);
+}
+
+static const struct nla_policy ila_notify_nl_policy[ILA_NOTIFY_ATTR_MAX + 1] = 
{
+       [ILA_NOTIFY_ATTR_TIMEOUT] = { .type = NLA_U32, },
+};
+
+static int ila_rslv_build_state(struct net *net, struct nlattr *nla,
+                               unsigned int family, const void *cfg,
+                               struct lwtunnel_state **ts,
+                               struct netlink_ext_ack *extack)
+{
+       struct ila_notify_params *p;
+       struct nlattr *tb[ILA_NOTIFY_ATTR_MAX + 1];
+       struct lwtunnel_state *newts;
+       size_t encap_len = sizeof(*p);
+       int ret;
+
+       if (family != AF_INET6)
+               return -EINVAL;
+
+       ret = nla_parse_nested(tb, ILA_NOTIFY_ATTR_MAX, nla,
+                              ila_notify_nl_policy, extack);
+
+       if (ret < 0)
+               return ret;
+
+       newts = lwtunnel_state_alloc(encap_len);
+       if (!newts)
+               return -ENOMEM;
+
+       newts->type = LWTUNNEL_ENCAP_ILA_NOTIFY;
+       newts->flags |= LWTUNNEL_STATE_OUTPUT_REDIRECT |
+                       LWTUNNEL_STATE_INPUT_REDIRECT;
+
+       p = ila_notify_params_lwtunnel(newts);
+
+       if (tb[ILA_NOTIFY_ATTR_TIMEOUT])
+               p->timeout = msecs_to_jiffies(nla_get_u32(
+                       tb[ILA_NOTIFY_ATTR_TIMEOUT]));
+
+       *ts = newts;
+
+       return 0;
+}
+
+static int ila_rslv_fill_encap_info(struct sk_buff *skb,
+                                   struct lwtunnel_state *lwtstate)
+{
+       struct ila_notify_params *p = ila_notify_params_lwtunnel(lwtstate);
+
+       if (nla_put_u32(skb, ILA_NOTIFY_ATTR_TIMEOUT,
+                       (__force u32)jiffies_to_msecs(p->timeout)))
+               goto nla_put_failure;
+
+       return 0;
+
+nla_put_failure:
+       return -EMSGSIZE;
+}
+
+static int ila_rslv_nlsize(struct lwtunnel_state *lwtstate)
+{
+       return nla_total_size(sizeof(u32)) + /* ILA_NOTIFY_ATTR_TIMEOUT */
+              0;
+}
+
+static int ila_rslv_cmp(struct lwtunnel_state *a, struct lwtunnel_state *b)
+{
+       return 0;
+}
+
+static const struct lwtunnel_encap_ops ila_rslv_ops = {
+       .build_state = ila_rslv_build_state,
+       .output = ila_rslv_output,
+       .input = ila_rslv_input,
+       .fill_encap = ila_rslv_fill_encap_info,
+       .get_encap_size = ila_rslv_nlsize,
+       .cmp_encap = ila_rslv_cmp,
+};
+
+#define ILA_MAX_SIZE 8192
+
+int ila_rslv_init_net(struct net *net)
+{
+       struct ila_net *ilan = net_generic(net, ila_net_id);
+       struct net_rslv *nrslv;
+
+       nrslv = net_rslv_create(sizeof(struct ila_addr),
+                               sizeof(struct ila_addr), ILA_MAX_SIZE, NULL);
+
+       if (IS_ERR(nrslv))
+               return PTR_ERR(nrslv);
+
+       ilan->rslv.nrslv = nrslv;
+
+       return 0;
+}
+
+void ila_rslv_exit_net(struct net *net)
+{
+       struct ila_net *ilan = net_generic(net, ila_net_id);
+
+       if (ilan->rslv.nrslv)
+               net_rslv_destroy(ilan->rslv.nrslv);
+}
+
+int ila_rslv_init(void)
+{
+       return lwtunnel_encap_add_ops(&ila_rslv_ops, LWTUNNEL_ENCAP_ILA_NOTIFY);
+}
+
+void ila_rslv_fini(void)
+{
+       lwtunnel_encap_del_ops(&ila_rslv_ops, LWTUNNEL_ENCAP_ILA_NOTIFY);
+}
-- 
2.11.0

Reply via email to