18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 28c2ecf20Sopenharmony_ci#include <linux/errno.h> 38c2ecf20Sopenharmony_ci#include <linux/ip.h> 48c2ecf20Sopenharmony_ci#include <linux/kernel.h> 58c2ecf20Sopenharmony_ci#include <linux/module.h> 68c2ecf20Sopenharmony_ci#include <linux/skbuff.h> 78c2ecf20Sopenharmony_ci#include <linux/socket.h> 88c2ecf20Sopenharmony_ci#include <linux/types.h> 98c2ecf20Sopenharmony_ci#include <net/checksum.h> 108c2ecf20Sopenharmony_ci#include <net/dst_cache.h> 118c2ecf20Sopenharmony_ci#include <net/ip.h> 128c2ecf20Sopenharmony_ci#include <net/ip6_fib.h> 138c2ecf20Sopenharmony_ci#include <net/ip6_route.h> 148c2ecf20Sopenharmony_ci#include <net/lwtunnel.h> 158c2ecf20Sopenharmony_ci#include <net/protocol.h> 168c2ecf20Sopenharmony_ci#include <uapi/linux/ila.h> 178c2ecf20Sopenharmony_ci#include "ila.h" 188c2ecf20Sopenharmony_ci 198c2ecf20Sopenharmony_cistruct ila_lwt { 208c2ecf20Sopenharmony_ci struct ila_params p; 218c2ecf20Sopenharmony_ci struct dst_cache dst_cache; 228c2ecf20Sopenharmony_ci u32 connected : 1; 238c2ecf20Sopenharmony_ci u32 lwt_output : 1; 248c2ecf20Sopenharmony_ci}; 258c2ecf20Sopenharmony_ci 268c2ecf20Sopenharmony_cistatic inline struct ila_lwt *ila_lwt_lwtunnel( 278c2ecf20Sopenharmony_ci struct lwtunnel_state *lwt) 288c2ecf20Sopenharmony_ci{ 298c2ecf20Sopenharmony_ci return (struct ila_lwt *)lwt->data; 308c2ecf20Sopenharmony_ci} 318c2ecf20Sopenharmony_ci 328c2ecf20Sopenharmony_cistatic inline struct ila_params *ila_params_lwtunnel( 338c2ecf20Sopenharmony_ci struct lwtunnel_state *lwt) 348c2ecf20Sopenharmony_ci{ 358c2ecf20Sopenharmony_ci return &ila_lwt_lwtunnel(lwt)->p; 368c2ecf20Sopenharmony_ci} 378c2ecf20Sopenharmony_ci 388c2ecf20Sopenharmony_cistatic int ila_output(struct net *net, struct sock *sk, struct sk_buff *skb) 398c2ecf20Sopenharmony_ci{ 408c2ecf20Sopenharmony_ci struct dst_entry *orig_dst = skb_dst(skb); 418c2ecf20Sopenharmony_ci struct rt6_info *rt = (struct rt6_info *)orig_dst; 428c2ecf20Sopenharmony_ci struct ila_lwt *ilwt = ila_lwt_lwtunnel(orig_dst->lwtstate); 438c2ecf20Sopenharmony_ci struct dst_entry *dst; 448c2ecf20Sopenharmony_ci int err = -EINVAL; 458c2ecf20Sopenharmony_ci 468c2ecf20Sopenharmony_ci if (skb->protocol != htons(ETH_P_IPV6)) 478c2ecf20Sopenharmony_ci goto drop; 488c2ecf20Sopenharmony_ci 498c2ecf20Sopenharmony_ci if (ilwt->lwt_output) 508c2ecf20Sopenharmony_ci ila_update_ipv6_locator(skb, 518c2ecf20Sopenharmony_ci ila_params_lwtunnel(orig_dst->lwtstate), 528c2ecf20Sopenharmony_ci true); 538c2ecf20Sopenharmony_ci 548c2ecf20Sopenharmony_ci if (rt->rt6i_flags & (RTF_GATEWAY | RTF_CACHE)) { 558c2ecf20Sopenharmony_ci /* Already have a next hop address in route, no need for 568c2ecf20Sopenharmony_ci * dest cache route. 578c2ecf20Sopenharmony_ci */ 588c2ecf20Sopenharmony_ci return orig_dst->lwtstate->orig_output(net, sk, skb); 598c2ecf20Sopenharmony_ci } 608c2ecf20Sopenharmony_ci 618c2ecf20Sopenharmony_ci dst = dst_cache_get(&ilwt->dst_cache); 628c2ecf20Sopenharmony_ci if (unlikely(!dst)) { 638c2ecf20Sopenharmony_ci struct ipv6hdr *ip6h = ipv6_hdr(skb); 648c2ecf20Sopenharmony_ci struct flowi6 fl6; 658c2ecf20Sopenharmony_ci 668c2ecf20Sopenharmony_ci /* Lookup a route for the new destination. Take into 678c2ecf20Sopenharmony_ci * account that the base route may already have a gateway. 688c2ecf20Sopenharmony_ci */ 698c2ecf20Sopenharmony_ci 708c2ecf20Sopenharmony_ci memset(&fl6, 0, sizeof(fl6)); 718c2ecf20Sopenharmony_ci fl6.flowi6_oif = orig_dst->dev->ifindex; 728c2ecf20Sopenharmony_ci fl6.flowi6_iif = LOOPBACK_IFINDEX; 738c2ecf20Sopenharmony_ci fl6.daddr = *rt6_nexthop((struct rt6_info *)orig_dst, 748c2ecf20Sopenharmony_ci &ip6h->daddr); 758c2ecf20Sopenharmony_ci 768c2ecf20Sopenharmony_ci dst = ip6_route_output(net, NULL, &fl6); 778c2ecf20Sopenharmony_ci if (dst->error) { 788c2ecf20Sopenharmony_ci err = -EHOSTUNREACH; 798c2ecf20Sopenharmony_ci dst_release(dst); 808c2ecf20Sopenharmony_ci goto drop; 818c2ecf20Sopenharmony_ci } 828c2ecf20Sopenharmony_ci 838c2ecf20Sopenharmony_ci dst = xfrm_lookup(net, dst, flowi6_to_flowi(&fl6), NULL, 0); 848c2ecf20Sopenharmony_ci if (IS_ERR(dst)) { 858c2ecf20Sopenharmony_ci err = PTR_ERR(dst); 868c2ecf20Sopenharmony_ci goto drop; 878c2ecf20Sopenharmony_ci } 888c2ecf20Sopenharmony_ci 898c2ecf20Sopenharmony_ci if (ilwt->connected) 908c2ecf20Sopenharmony_ci dst_cache_set_ip6(&ilwt->dst_cache, dst, &fl6.saddr); 918c2ecf20Sopenharmony_ci } 928c2ecf20Sopenharmony_ci 938c2ecf20Sopenharmony_ci skb_dst_set(skb, dst); 948c2ecf20Sopenharmony_ci return dst_output(net, sk, skb); 958c2ecf20Sopenharmony_ci 968c2ecf20Sopenharmony_cidrop: 978c2ecf20Sopenharmony_ci kfree_skb(skb); 988c2ecf20Sopenharmony_ci return err; 998c2ecf20Sopenharmony_ci} 1008c2ecf20Sopenharmony_ci 1018c2ecf20Sopenharmony_cistatic int ila_input(struct sk_buff *skb) 1028c2ecf20Sopenharmony_ci{ 1038c2ecf20Sopenharmony_ci struct dst_entry *dst = skb_dst(skb); 1048c2ecf20Sopenharmony_ci struct ila_lwt *ilwt = ila_lwt_lwtunnel(dst->lwtstate); 1058c2ecf20Sopenharmony_ci 1068c2ecf20Sopenharmony_ci if (skb->protocol != htons(ETH_P_IPV6)) 1078c2ecf20Sopenharmony_ci goto drop; 1088c2ecf20Sopenharmony_ci 1098c2ecf20Sopenharmony_ci if (!ilwt->lwt_output) 1108c2ecf20Sopenharmony_ci ila_update_ipv6_locator(skb, 1118c2ecf20Sopenharmony_ci ila_params_lwtunnel(dst->lwtstate), 1128c2ecf20Sopenharmony_ci false); 1138c2ecf20Sopenharmony_ci 1148c2ecf20Sopenharmony_ci return dst->lwtstate->orig_input(skb); 1158c2ecf20Sopenharmony_ci 1168c2ecf20Sopenharmony_cidrop: 1178c2ecf20Sopenharmony_ci kfree_skb(skb); 1188c2ecf20Sopenharmony_ci return -EINVAL; 1198c2ecf20Sopenharmony_ci} 1208c2ecf20Sopenharmony_ci 1218c2ecf20Sopenharmony_cistatic const struct nla_policy ila_nl_policy[ILA_ATTR_MAX + 1] = { 1228c2ecf20Sopenharmony_ci [ILA_ATTR_LOCATOR] = { .type = NLA_U64, }, 1238c2ecf20Sopenharmony_ci [ILA_ATTR_CSUM_MODE] = { .type = NLA_U8, }, 1248c2ecf20Sopenharmony_ci [ILA_ATTR_IDENT_TYPE] = { .type = NLA_U8, }, 1258c2ecf20Sopenharmony_ci [ILA_ATTR_HOOK_TYPE] = { .type = NLA_U8, }, 1268c2ecf20Sopenharmony_ci}; 1278c2ecf20Sopenharmony_ci 1288c2ecf20Sopenharmony_cistatic int ila_build_state(struct net *net, struct nlattr *nla, 1298c2ecf20Sopenharmony_ci unsigned int family, const void *cfg, 1308c2ecf20Sopenharmony_ci struct lwtunnel_state **ts, 1318c2ecf20Sopenharmony_ci struct netlink_ext_ack *extack) 1328c2ecf20Sopenharmony_ci{ 1338c2ecf20Sopenharmony_ci struct ila_lwt *ilwt; 1348c2ecf20Sopenharmony_ci struct ila_params *p; 1358c2ecf20Sopenharmony_ci struct nlattr *tb[ILA_ATTR_MAX + 1]; 1368c2ecf20Sopenharmony_ci struct lwtunnel_state *newts; 1378c2ecf20Sopenharmony_ci const struct fib6_config *cfg6 = cfg; 1388c2ecf20Sopenharmony_ci struct ila_addr *iaddr; 1398c2ecf20Sopenharmony_ci u8 ident_type = ILA_ATYPE_USE_FORMAT; 1408c2ecf20Sopenharmony_ci u8 hook_type = ILA_HOOK_ROUTE_OUTPUT; 1418c2ecf20Sopenharmony_ci u8 csum_mode = ILA_CSUM_NO_ACTION; 1428c2ecf20Sopenharmony_ci bool lwt_output = true; 1438c2ecf20Sopenharmony_ci u8 eff_ident_type; 1448c2ecf20Sopenharmony_ci int ret; 1458c2ecf20Sopenharmony_ci 1468c2ecf20Sopenharmony_ci if (family != AF_INET6) 1478c2ecf20Sopenharmony_ci return -EINVAL; 1488c2ecf20Sopenharmony_ci 1498c2ecf20Sopenharmony_ci ret = nla_parse_nested_deprecated(tb, ILA_ATTR_MAX, nla, 1508c2ecf20Sopenharmony_ci ila_nl_policy, extack); 1518c2ecf20Sopenharmony_ci if (ret < 0) 1528c2ecf20Sopenharmony_ci return ret; 1538c2ecf20Sopenharmony_ci 1548c2ecf20Sopenharmony_ci if (!tb[ILA_ATTR_LOCATOR]) 1558c2ecf20Sopenharmony_ci return -EINVAL; 1568c2ecf20Sopenharmony_ci 1578c2ecf20Sopenharmony_ci iaddr = (struct ila_addr *)&cfg6->fc_dst; 1588c2ecf20Sopenharmony_ci 1598c2ecf20Sopenharmony_ci if (tb[ILA_ATTR_IDENT_TYPE]) 1608c2ecf20Sopenharmony_ci ident_type = nla_get_u8(tb[ILA_ATTR_IDENT_TYPE]); 1618c2ecf20Sopenharmony_ci 1628c2ecf20Sopenharmony_ci if (ident_type == ILA_ATYPE_USE_FORMAT) { 1638c2ecf20Sopenharmony_ci /* Infer identifier type from type field in formatted 1648c2ecf20Sopenharmony_ci * identifier. 1658c2ecf20Sopenharmony_ci */ 1668c2ecf20Sopenharmony_ci 1678c2ecf20Sopenharmony_ci if (cfg6->fc_dst_len < 8 * sizeof(struct ila_locator) + 3) { 1688c2ecf20Sopenharmony_ci /* Need to have full locator and at least type field 1698c2ecf20Sopenharmony_ci * included in destination 1708c2ecf20Sopenharmony_ci */ 1718c2ecf20Sopenharmony_ci return -EINVAL; 1728c2ecf20Sopenharmony_ci } 1738c2ecf20Sopenharmony_ci 1748c2ecf20Sopenharmony_ci eff_ident_type = iaddr->ident.type; 1758c2ecf20Sopenharmony_ci } else { 1768c2ecf20Sopenharmony_ci eff_ident_type = ident_type; 1778c2ecf20Sopenharmony_ci } 1788c2ecf20Sopenharmony_ci 1798c2ecf20Sopenharmony_ci switch (eff_ident_type) { 1808c2ecf20Sopenharmony_ci case ILA_ATYPE_IID: 1818c2ecf20Sopenharmony_ci /* Don't allow ILA for IID type */ 1828c2ecf20Sopenharmony_ci return -EINVAL; 1838c2ecf20Sopenharmony_ci case ILA_ATYPE_LUID: 1848c2ecf20Sopenharmony_ci break; 1858c2ecf20Sopenharmony_ci case ILA_ATYPE_VIRT_V4: 1868c2ecf20Sopenharmony_ci case ILA_ATYPE_VIRT_UNI_V6: 1878c2ecf20Sopenharmony_ci case ILA_ATYPE_VIRT_MULTI_V6: 1888c2ecf20Sopenharmony_ci case ILA_ATYPE_NONLOCAL_ADDR: 1898c2ecf20Sopenharmony_ci /* These ILA formats are not supported yet. */ 1908c2ecf20Sopenharmony_ci default: 1918c2ecf20Sopenharmony_ci return -EINVAL; 1928c2ecf20Sopenharmony_ci } 1938c2ecf20Sopenharmony_ci 1948c2ecf20Sopenharmony_ci if (tb[ILA_ATTR_HOOK_TYPE]) 1958c2ecf20Sopenharmony_ci hook_type = nla_get_u8(tb[ILA_ATTR_HOOK_TYPE]); 1968c2ecf20Sopenharmony_ci 1978c2ecf20Sopenharmony_ci switch (hook_type) { 1988c2ecf20Sopenharmony_ci case ILA_HOOK_ROUTE_OUTPUT: 1998c2ecf20Sopenharmony_ci lwt_output = true; 2008c2ecf20Sopenharmony_ci break; 2018c2ecf20Sopenharmony_ci case ILA_HOOK_ROUTE_INPUT: 2028c2ecf20Sopenharmony_ci lwt_output = false; 2038c2ecf20Sopenharmony_ci break; 2048c2ecf20Sopenharmony_ci default: 2058c2ecf20Sopenharmony_ci return -EINVAL; 2068c2ecf20Sopenharmony_ci } 2078c2ecf20Sopenharmony_ci 2088c2ecf20Sopenharmony_ci if (tb[ILA_ATTR_CSUM_MODE]) 2098c2ecf20Sopenharmony_ci csum_mode = nla_get_u8(tb[ILA_ATTR_CSUM_MODE]); 2108c2ecf20Sopenharmony_ci 2118c2ecf20Sopenharmony_ci if (csum_mode == ILA_CSUM_NEUTRAL_MAP && 2128c2ecf20Sopenharmony_ci ila_csum_neutral_set(iaddr->ident)) { 2138c2ecf20Sopenharmony_ci /* Don't allow translation if checksum neutral bit is 2148c2ecf20Sopenharmony_ci * configured and it's set in the SIR address. 2158c2ecf20Sopenharmony_ci */ 2168c2ecf20Sopenharmony_ci return -EINVAL; 2178c2ecf20Sopenharmony_ci } 2188c2ecf20Sopenharmony_ci 2198c2ecf20Sopenharmony_ci newts = lwtunnel_state_alloc(sizeof(*ilwt)); 2208c2ecf20Sopenharmony_ci if (!newts) 2218c2ecf20Sopenharmony_ci return -ENOMEM; 2228c2ecf20Sopenharmony_ci 2238c2ecf20Sopenharmony_ci ilwt = ila_lwt_lwtunnel(newts); 2248c2ecf20Sopenharmony_ci ret = dst_cache_init(&ilwt->dst_cache, GFP_ATOMIC); 2258c2ecf20Sopenharmony_ci if (ret) { 2268c2ecf20Sopenharmony_ci kfree(newts); 2278c2ecf20Sopenharmony_ci return ret; 2288c2ecf20Sopenharmony_ci } 2298c2ecf20Sopenharmony_ci 2308c2ecf20Sopenharmony_ci ilwt->lwt_output = !!lwt_output; 2318c2ecf20Sopenharmony_ci 2328c2ecf20Sopenharmony_ci p = ila_params_lwtunnel(newts); 2338c2ecf20Sopenharmony_ci 2348c2ecf20Sopenharmony_ci p->csum_mode = csum_mode; 2358c2ecf20Sopenharmony_ci p->ident_type = ident_type; 2368c2ecf20Sopenharmony_ci p->locator.v64 = (__force __be64)nla_get_u64(tb[ILA_ATTR_LOCATOR]); 2378c2ecf20Sopenharmony_ci 2388c2ecf20Sopenharmony_ci /* Precompute checksum difference for translation since we 2398c2ecf20Sopenharmony_ci * know both the old locator and the new one. 2408c2ecf20Sopenharmony_ci */ 2418c2ecf20Sopenharmony_ci p->locator_match = iaddr->loc; 2428c2ecf20Sopenharmony_ci 2438c2ecf20Sopenharmony_ci ila_init_saved_csum(p); 2448c2ecf20Sopenharmony_ci 2458c2ecf20Sopenharmony_ci newts->type = LWTUNNEL_ENCAP_ILA; 2468c2ecf20Sopenharmony_ci newts->flags |= LWTUNNEL_STATE_OUTPUT_REDIRECT | 2478c2ecf20Sopenharmony_ci LWTUNNEL_STATE_INPUT_REDIRECT; 2488c2ecf20Sopenharmony_ci 2498c2ecf20Sopenharmony_ci if (cfg6->fc_dst_len == 8 * sizeof(struct in6_addr)) 2508c2ecf20Sopenharmony_ci ilwt->connected = 1; 2518c2ecf20Sopenharmony_ci 2528c2ecf20Sopenharmony_ci *ts = newts; 2538c2ecf20Sopenharmony_ci 2548c2ecf20Sopenharmony_ci return 0; 2558c2ecf20Sopenharmony_ci} 2568c2ecf20Sopenharmony_ci 2578c2ecf20Sopenharmony_cistatic void ila_destroy_state(struct lwtunnel_state *lwt) 2588c2ecf20Sopenharmony_ci{ 2598c2ecf20Sopenharmony_ci dst_cache_destroy(&ila_lwt_lwtunnel(lwt)->dst_cache); 2608c2ecf20Sopenharmony_ci} 2618c2ecf20Sopenharmony_ci 2628c2ecf20Sopenharmony_cistatic int ila_fill_encap_info(struct sk_buff *skb, 2638c2ecf20Sopenharmony_ci struct lwtunnel_state *lwtstate) 2648c2ecf20Sopenharmony_ci{ 2658c2ecf20Sopenharmony_ci struct ila_params *p = ila_params_lwtunnel(lwtstate); 2668c2ecf20Sopenharmony_ci struct ila_lwt *ilwt = ila_lwt_lwtunnel(lwtstate); 2678c2ecf20Sopenharmony_ci 2688c2ecf20Sopenharmony_ci if (nla_put_u64_64bit(skb, ILA_ATTR_LOCATOR, (__force u64)p->locator.v64, 2698c2ecf20Sopenharmony_ci ILA_ATTR_PAD)) 2708c2ecf20Sopenharmony_ci goto nla_put_failure; 2718c2ecf20Sopenharmony_ci 2728c2ecf20Sopenharmony_ci if (nla_put_u8(skb, ILA_ATTR_CSUM_MODE, (__force u8)p->csum_mode)) 2738c2ecf20Sopenharmony_ci goto nla_put_failure; 2748c2ecf20Sopenharmony_ci 2758c2ecf20Sopenharmony_ci if (nla_put_u8(skb, ILA_ATTR_IDENT_TYPE, (__force u8)p->ident_type)) 2768c2ecf20Sopenharmony_ci goto nla_put_failure; 2778c2ecf20Sopenharmony_ci 2788c2ecf20Sopenharmony_ci if (nla_put_u8(skb, ILA_ATTR_HOOK_TYPE, 2798c2ecf20Sopenharmony_ci ilwt->lwt_output ? ILA_HOOK_ROUTE_OUTPUT : 2808c2ecf20Sopenharmony_ci ILA_HOOK_ROUTE_INPUT)) 2818c2ecf20Sopenharmony_ci goto nla_put_failure; 2828c2ecf20Sopenharmony_ci 2838c2ecf20Sopenharmony_ci return 0; 2848c2ecf20Sopenharmony_ci 2858c2ecf20Sopenharmony_cinla_put_failure: 2868c2ecf20Sopenharmony_ci return -EMSGSIZE; 2878c2ecf20Sopenharmony_ci} 2888c2ecf20Sopenharmony_ci 2898c2ecf20Sopenharmony_cistatic int ila_encap_nlsize(struct lwtunnel_state *lwtstate) 2908c2ecf20Sopenharmony_ci{ 2918c2ecf20Sopenharmony_ci return nla_total_size_64bit(sizeof(u64)) + /* ILA_ATTR_LOCATOR */ 2928c2ecf20Sopenharmony_ci nla_total_size(sizeof(u8)) + /* ILA_ATTR_CSUM_MODE */ 2938c2ecf20Sopenharmony_ci nla_total_size(sizeof(u8)) + /* ILA_ATTR_IDENT_TYPE */ 2948c2ecf20Sopenharmony_ci nla_total_size(sizeof(u8)) + /* ILA_ATTR_HOOK_TYPE */ 2958c2ecf20Sopenharmony_ci 0; 2968c2ecf20Sopenharmony_ci} 2978c2ecf20Sopenharmony_ci 2988c2ecf20Sopenharmony_cistatic int ila_encap_cmp(struct lwtunnel_state *a, struct lwtunnel_state *b) 2998c2ecf20Sopenharmony_ci{ 3008c2ecf20Sopenharmony_ci struct ila_params *a_p = ila_params_lwtunnel(a); 3018c2ecf20Sopenharmony_ci struct ila_params *b_p = ila_params_lwtunnel(b); 3028c2ecf20Sopenharmony_ci 3038c2ecf20Sopenharmony_ci return (a_p->locator.v64 != b_p->locator.v64); 3048c2ecf20Sopenharmony_ci} 3058c2ecf20Sopenharmony_ci 3068c2ecf20Sopenharmony_cistatic const struct lwtunnel_encap_ops ila_encap_ops = { 3078c2ecf20Sopenharmony_ci .build_state = ila_build_state, 3088c2ecf20Sopenharmony_ci .destroy_state = ila_destroy_state, 3098c2ecf20Sopenharmony_ci .output = ila_output, 3108c2ecf20Sopenharmony_ci .input = ila_input, 3118c2ecf20Sopenharmony_ci .fill_encap = ila_fill_encap_info, 3128c2ecf20Sopenharmony_ci .get_encap_size = ila_encap_nlsize, 3138c2ecf20Sopenharmony_ci .cmp_encap = ila_encap_cmp, 3148c2ecf20Sopenharmony_ci .owner = THIS_MODULE, 3158c2ecf20Sopenharmony_ci}; 3168c2ecf20Sopenharmony_ci 3178c2ecf20Sopenharmony_ciint ila_lwt_init(void) 3188c2ecf20Sopenharmony_ci{ 3198c2ecf20Sopenharmony_ci return lwtunnel_encap_add_ops(&ila_encap_ops, LWTUNNEL_ENCAP_ILA); 3208c2ecf20Sopenharmony_ci} 3218c2ecf20Sopenharmony_ci 3228c2ecf20Sopenharmony_civoid ila_lwt_fini(void) 3238c2ecf20Sopenharmony_ci{ 3248c2ecf20Sopenharmony_ci lwtunnel_encap_del_ops(&ila_encap_ops, LWTUNNEL_ENCAP_ILA); 3258c2ecf20Sopenharmony_ci} 326