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