18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * xfrm6_policy.c: based on xfrm4_policy.c
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci * Authors:
68c2ecf20Sopenharmony_ci *	Mitsuru KANDA @USAGI
78c2ecf20Sopenharmony_ci *	Kazunori MIYAZAWA @USAGI
88c2ecf20Sopenharmony_ci *	Kunihiro Ishiguro <kunihiro@ipinfusion.com>
98c2ecf20Sopenharmony_ci *		IPv6 support
108c2ecf20Sopenharmony_ci *	YOSHIFUJI Hideaki
118c2ecf20Sopenharmony_ci *		Split up af-specific portion
128c2ecf20Sopenharmony_ci *
138c2ecf20Sopenharmony_ci */
148c2ecf20Sopenharmony_ci
158c2ecf20Sopenharmony_ci#include <linux/err.h>
168c2ecf20Sopenharmony_ci#include <linux/kernel.h>
178c2ecf20Sopenharmony_ci#include <linux/netdevice.h>
188c2ecf20Sopenharmony_ci#include <net/addrconf.h>
198c2ecf20Sopenharmony_ci#include <net/dst.h>
208c2ecf20Sopenharmony_ci#include <net/xfrm.h>
218c2ecf20Sopenharmony_ci#include <net/ip.h>
228c2ecf20Sopenharmony_ci#include <net/ipv6.h>
238c2ecf20Sopenharmony_ci#include <net/ip6_route.h>
248c2ecf20Sopenharmony_ci#include <net/l3mdev.h>
258c2ecf20Sopenharmony_ci
268c2ecf20Sopenharmony_cistatic struct dst_entry *xfrm6_dst_lookup(struct net *net, int tos, int oif,
278c2ecf20Sopenharmony_ci					  const xfrm_address_t *saddr,
288c2ecf20Sopenharmony_ci					  const xfrm_address_t *daddr,
298c2ecf20Sopenharmony_ci					  u32 mark)
308c2ecf20Sopenharmony_ci{
318c2ecf20Sopenharmony_ci	struct flowi6 fl6;
328c2ecf20Sopenharmony_ci	struct dst_entry *dst;
338c2ecf20Sopenharmony_ci	int err;
348c2ecf20Sopenharmony_ci
358c2ecf20Sopenharmony_ci	memset(&fl6, 0, sizeof(fl6));
368c2ecf20Sopenharmony_ci	fl6.flowi6_oif = l3mdev_master_ifindex_by_index(net, oif);
378c2ecf20Sopenharmony_ci	fl6.flowi6_flags = FLOWI_FLAG_SKIP_NH_OIF;
388c2ecf20Sopenharmony_ci	fl6.flowi6_mark = mark;
398c2ecf20Sopenharmony_ci	memcpy(&fl6.daddr, daddr, sizeof(fl6.daddr));
408c2ecf20Sopenharmony_ci	if (saddr)
418c2ecf20Sopenharmony_ci		memcpy(&fl6.saddr, saddr, sizeof(fl6.saddr));
428c2ecf20Sopenharmony_ci
438c2ecf20Sopenharmony_ci	dst = ip6_route_output(net, NULL, &fl6);
448c2ecf20Sopenharmony_ci
458c2ecf20Sopenharmony_ci	err = dst->error;
468c2ecf20Sopenharmony_ci	if (dst->error) {
478c2ecf20Sopenharmony_ci		dst_release(dst);
488c2ecf20Sopenharmony_ci		dst = ERR_PTR(err);
498c2ecf20Sopenharmony_ci	}
508c2ecf20Sopenharmony_ci
518c2ecf20Sopenharmony_ci	return dst;
528c2ecf20Sopenharmony_ci}
538c2ecf20Sopenharmony_ci
548c2ecf20Sopenharmony_cistatic int xfrm6_get_saddr(struct net *net, int oif,
558c2ecf20Sopenharmony_ci			   xfrm_address_t *saddr, xfrm_address_t *daddr,
568c2ecf20Sopenharmony_ci			   u32 mark)
578c2ecf20Sopenharmony_ci{
588c2ecf20Sopenharmony_ci	struct dst_entry *dst;
598c2ecf20Sopenharmony_ci	struct net_device *dev;
608c2ecf20Sopenharmony_ci	struct inet6_dev *idev;
618c2ecf20Sopenharmony_ci
628c2ecf20Sopenharmony_ci	dst = xfrm6_dst_lookup(net, 0, oif, NULL, daddr, mark);
638c2ecf20Sopenharmony_ci	if (IS_ERR(dst))
648c2ecf20Sopenharmony_ci		return -EHOSTUNREACH;
658c2ecf20Sopenharmony_ci
668c2ecf20Sopenharmony_ci	idev = ip6_dst_idev(dst);
678c2ecf20Sopenharmony_ci	if (!idev) {
688c2ecf20Sopenharmony_ci		dst_release(dst);
698c2ecf20Sopenharmony_ci		return -EHOSTUNREACH;
708c2ecf20Sopenharmony_ci	}
718c2ecf20Sopenharmony_ci	dev = idev->dev;
728c2ecf20Sopenharmony_ci	ipv6_dev_get_saddr(dev_net(dev), dev, &daddr->in6, 0, &saddr->in6);
738c2ecf20Sopenharmony_ci	dst_release(dst);
748c2ecf20Sopenharmony_ci	return 0;
758c2ecf20Sopenharmony_ci}
768c2ecf20Sopenharmony_ci
778c2ecf20Sopenharmony_cistatic int xfrm6_fill_dst(struct xfrm_dst *xdst, struct net_device *dev,
788c2ecf20Sopenharmony_ci			  const struct flowi *fl)
798c2ecf20Sopenharmony_ci{
808c2ecf20Sopenharmony_ci	struct rt6_info *rt = (struct rt6_info *)xdst->route;
818c2ecf20Sopenharmony_ci
828c2ecf20Sopenharmony_ci	xdst->u.dst.dev = dev;
838c2ecf20Sopenharmony_ci	dev_hold(dev);
848c2ecf20Sopenharmony_ci
858c2ecf20Sopenharmony_ci	xdst->u.rt6.rt6i_idev = in6_dev_get(dev);
868c2ecf20Sopenharmony_ci	if (!xdst->u.rt6.rt6i_idev) {
878c2ecf20Sopenharmony_ci		dev_put(dev);
888c2ecf20Sopenharmony_ci		return -ENODEV;
898c2ecf20Sopenharmony_ci	}
908c2ecf20Sopenharmony_ci
918c2ecf20Sopenharmony_ci	/* Sheit... I remember I did this right. Apparently,
928c2ecf20Sopenharmony_ci	 * it was magically lost, so this code needs audit */
938c2ecf20Sopenharmony_ci	xdst->u.rt6.rt6i_flags = rt->rt6i_flags & (RTF_ANYCAST |
948c2ecf20Sopenharmony_ci						   RTF_LOCAL);
958c2ecf20Sopenharmony_ci	xdst->route_cookie = rt6_get_cookie(rt);
968c2ecf20Sopenharmony_ci	xdst->u.rt6.rt6i_gateway = rt->rt6i_gateway;
978c2ecf20Sopenharmony_ci	xdst->u.rt6.rt6i_dst = rt->rt6i_dst;
988c2ecf20Sopenharmony_ci	xdst->u.rt6.rt6i_src = rt->rt6i_src;
998c2ecf20Sopenharmony_ci	INIT_LIST_HEAD(&xdst->u.rt6.rt6i_uncached);
1008c2ecf20Sopenharmony_ci	rt6_uncached_list_add(&xdst->u.rt6);
1018c2ecf20Sopenharmony_ci	atomic_inc(&dev_net(dev)->ipv6.rt6_stats->fib_rt_uncache);
1028c2ecf20Sopenharmony_ci
1038c2ecf20Sopenharmony_ci	return 0;
1048c2ecf20Sopenharmony_ci}
1058c2ecf20Sopenharmony_ci
1068c2ecf20Sopenharmony_cistatic void xfrm6_update_pmtu(struct dst_entry *dst, struct sock *sk,
1078c2ecf20Sopenharmony_ci			      struct sk_buff *skb, u32 mtu,
1088c2ecf20Sopenharmony_ci			      bool confirm_neigh)
1098c2ecf20Sopenharmony_ci{
1108c2ecf20Sopenharmony_ci	struct xfrm_dst *xdst = (struct xfrm_dst *)dst;
1118c2ecf20Sopenharmony_ci	struct dst_entry *path = xdst->route;
1128c2ecf20Sopenharmony_ci
1138c2ecf20Sopenharmony_ci	path->ops->update_pmtu(path, sk, skb, mtu, confirm_neigh);
1148c2ecf20Sopenharmony_ci}
1158c2ecf20Sopenharmony_ci
1168c2ecf20Sopenharmony_cistatic void xfrm6_redirect(struct dst_entry *dst, struct sock *sk,
1178c2ecf20Sopenharmony_ci			   struct sk_buff *skb)
1188c2ecf20Sopenharmony_ci{
1198c2ecf20Sopenharmony_ci	struct xfrm_dst *xdst = (struct xfrm_dst *)dst;
1208c2ecf20Sopenharmony_ci	struct dst_entry *path = xdst->route;
1218c2ecf20Sopenharmony_ci
1228c2ecf20Sopenharmony_ci	path->ops->redirect(path, sk, skb);
1238c2ecf20Sopenharmony_ci}
1248c2ecf20Sopenharmony_ci
1258c2ecf20Sopenharmony_cistatic void xfrm6_dst_destroy(struct dst_entry *dst)
1268c2ecf20Sopenharmony_ci{
1278c2ecf20Sopenharmony_ci	struct xfrm_dst *xdst = (struct xfrm_dst *)dst;
1288c2ecf20Sopenharmony_ci
1298c2ecf20Sopenharmony_ci	dst_destroy_metrics_generic(dst);
1308c2ecf20Sopenharmony_ci	if (xdst->u.rt6.rt6i_uncached_list)
1318c2ecf20Sopenharmony_ci		rt6_uncached_list_del(&xdst->u.rt6);
1328c2ecf20Sopenharmony_ci	if (likely(xdst->u.rt6.rt6i_idev))
1338c2ecf20Sopenharmony_ci		in6_dev_put(xdst->u.rt6.rt6i_idev);
1348c2ecf20Sopenharmony_ci	xfrm_dst_destroy(xdst);
1358c2ecf20Sopenharmony_ci}
1368c2ecf20Sopenharmony_ci
1378c2ecf20Sopenharmony_cistatic void xfrm6_dst_ifdown(struct dst_entry *dst, struct net_device *dev,
1388c2ecf20Sopenharmony_ci			     int unregister)
1398c2ecf20Sopenharmony_ci{
1408c2ecf20Sopenharmony_ci	struct xfrm_dst *xdst;
1418c2ecf20Sopenharmony_ci
1428c2ecf20Sopenharmony_ci	if (!unregister)
1438c2ecf20Sopenharmony_ci		return;
1448c2ecf20Sopenharmony_ci
1458c2ecf20Sopenharmony_ci	xdst = (struct xfrm_dst *)dst;
1468c2ecf20Sopenharmony_ci	if (xdst->u.rt6.rt6i_idev->dev == dev) {
1478c2ecf20Sopenharmony_ci		struct inet6_dev *loopback_idev =
1488c2ecf20Sopenharmony_ci			in6_dev_get(dev_net(dev)->loopback_dev);
1498c2ecf20Sopenharmony_ci
1508c2ecf20Sopenharmony_ci		do {
1518c2ecf20Sopenharmony_ci			in6_dev_put(xdst->u.rt6.rt6i_idev);
1528c2ecf20Sopenharmony_ci			xdst->u.rt6.rt6i_idev = loopback_idev;
1538c2ecf20Sopenharmony_ci			in6_dev_hold(loopback_idev);
1548c2ecf20Sopenharmony_ci			xdst = (struct xfrm_dst *)xfrm_dst_child(&xdst->u.dst);
1558c2ecf20Sopenharmony_ci		} while (xdst->u.dst.xfrm);
1568c2ecf20Sopenharmony_ci
1578c2ecf20Sopenharmony_ci		__in6_dev_put(loopback_idev);
1588c2ecf20Sopenharmony_ci	}
1598c2ecf20Sopenharmony_ci
1608c2ecf20Sopenharmony_ci	xfrm_dst_ifdown(dst, dev);
1618c2ecf20Sopenharmony_ci}
1628c2ecf20Sopenharmony_ci
1638c2ecf20Sopenharmony_cistatic struct dst_ops xfrm6_dst_ops_template = {
1648c2ecf20Sopenharmony_ci	.family =		AF_INET6,
1658c2ecf20Sopenharmony_ci	.update_pmtu =		xfrm6_update_pmtu,
1668c2ecf20Sopenharmony_ci	.redirect =		xfrm6_redirect,
1678c2ecf20Sopenharmony_ci	.cow_metrics =		dst_cow_metrics_generic,
1688c2ecf20Sopenharmony_ci	.destroy =		xfrm6_dst_destroy,
1698c2ecf20Sopenharmony_ci	.ifdown =		xfrm6_dst_ifdown,
1708c2ecf20Sopenharmony_ci	.local_out =		__ip6_local_out,
1718c2ecf20Sopenharmony_ci	.gc_thresh =		32768,
1728c2ecf20Sopenharmony_ci};
1738c2ecf20Sopenharmony_ci
1748c2ecf20Sopenharmony_cistatic const struct xfrm_policy_afinfo xfrm6_policy_afinfo = {
1758c2ecf20Sopenharmony_ci	.dst_ops =		&xfrm6_dst_ops_template,
1768c2ecf20Sopenharmony_ci	.dst_lookup =		xfrm6_dst_lookup,
1778c2ecf20Sopenharmony_ci	.get_saddr =		xfrm6_get_saddr,
1788c2ecf20Sopenharmony_ci	.fill_dst =		xfrm6_fill_dst,
1798c2ecf20Sopenharmony_ci	.blackhole_route =	ip6_blackhole_route,
1808c2ecf20Sopenharmony_ci};
1818c2ecf20Sopenharmony_ci
1828c2ecf20Sopenharmony_cistatic int __init xfrm6_policy_init(void)
1838c2ecf20Sopenharmony_ci{
1848c2ecf20Sopenharmony_ci	return xfrm_policy_register_afinfo(&xfrm6_policy_afinfo, AF_INET6);
1858c2ecf20Sopenharmony_ci}
1868c2ecf20Sopenharmony_ci
1878c2ecf20Sopenharmony_cistatic void xfrm6_policy_fini(void)
1888c2ecf20Sopenharmony_ci{
1898c2ecf20Sopenharmony_ci	xfrm_policy_unregister_afinfo(&xfrm6_policy_afinfo);
1908c2ecf20Sopenharmony_ci}
1918c2ecf20Sopenharmony_ci
1928c2ecf20Sopenharmony_ci#ifdef CONFIG_SYSCTL
1938c2ecf20Sopenharmony_cistatic struct ctl_table xfrm6_policy_table[] = {
1948c2ecf20Sopenharmony_ci	{
1958c2ecf20Sopenharmony_ci		.procname       = "xfrm6_gc_thresh",
1968c2ecf20Sopenharmony_ci		.data		= &init_net.xfrm.xfrm6_dst_ops.gc_thresh,
1978c2ecf20Sopenharmony_ci		.maxlen		= sizeof(int),
1988c2ecf20Sopenharmony_ci		.mode		= 0644,
1998c2ecf20Sopenharmony_ci		.proc_handler   = proc_dointvec,
2008c2ecf20Sopenharmony_ci	},
2018c2ecf20Sopenharmony_ci	{ }
2028c2ecf20Sopenharmony_ci};
2038c2ecf20Sopenharmony_ci
2048c2ecf20Sopenharmony_cistatic int __net_init xfrm6_net_sysctl_init(struct net *net)
2058c2ecf20Sopenharmony_ci{
2068c2ecf20Sopenharmony_ci	struct ctl_table *table;
2078c2ecf20Sopenharmony_ci	struct ctl_table_header *hdr;
2088c2ecf20Sopenharmony_ci
2098c2ecf20Sopenharmony_ci	table = xfrm6_policy_table;
2108c2ecf20Sopenharmony_ci	if (!net_eq(net, &init_net)) {
2118c2ecf20Sopenharmony_ci		table = kmemdup(table, sizeof(xfrm6_policy_table), GFP_KERNEL);
2128c2ecf20Sopenharmony_ci		if (!table)
2138c2ecf20Sopenharmony_ci			goto err_alloc;
2148c2ecf20Sopenharmony_ci
2158c2ecf20Sopenharmony_ci		table[0].data = &net->xfrm.xfrm6_dst_ops.gc_thresh;
2168c2ecf20Sopenharmony_ci	}
2178c2ecf20Sopenharmony_ci
2188c2ecf20Sopenharmony_ci	hdr = register_net_sysctl(net, "net/ipv6", table);
2198c2ecf20Sopenharmony_ci	if (!hdr)
2208c2ecf20Sopenharmony_ci		goto err_reg;
2218c2ecf20Sopenharmony_ci
2228c2ecf20Sopenharmony_ci	net->ipv6.sysctl.xfrm6_hdr = hdr;
2238c2ecf20Sopenharmony_ci	return 0;
2248c2ecf20Sopenharmony_ci
2258c2ecf20Sopenharmony_cierr_reg:
2268c2ecf20Sopenharmony_ci	if (!net_eq(net, &init_net))
2278c2ecf20Sopenharmony_ci		kfree(table);
2288c2ecf20Sopenharmony_cierr_alloc:
2298c2ecf20Sopenharmony_ci	return -ENOMEM;
2308c2ecf20Sopenharmony_ci}
2318c2ecf20Sopenharmony_ci
2328c2ecf20Sopenharmony_cistatic void __net_exit xfrm6_net_sysctl_exit(struct net *net)
2338c2ecf20Sopenharmony_ci{
2348c2ecf20Sopenharmony_ci	struct ctl_table *table;
2358c2ecf20Sopenharmony_ci
2368c2ecf20Sopenharmony_ci	if (!net->ipv6.sysctl.xfrm6_hdr)
2378c2ecf20Sopenharmony_ci		return;
2388c2ecf20Sopenharmony_ci
2398c2ecf20Sopenharmony_ci	table = net->ipv6.sysctl.xfrm6_hdr->ctl_table_arg;
2408c2ecf20Sopenharmony_ci	unregister_net_sysctl_table(net->ipv6.sysctl.xfrm6_hdr);
2418c2ecf20Sopenharmony_ci	if (!net_eq(net, &init_net))
2428c2ecf20Sopenharmony_ci		kfree(table);
2438c2ecf20Sopenharmony_ci}
2448c2ecf20Sopenharmony_ci#else /* CONFIG_SYSCTL */
2458c2ecf20Sopenharmony_cistatic inline int xfrm6_net_sysctl_init(struct net *net)
2468c2ecf20Sopenharmony_ci{
2478c2ecf20Sopenharmony_ci	return 0;
2488c2ecf20Sopenharmony_ci}
2498c2ecf20Sopenharmony_ci
2508c2ecf20Sopenharmony_cistatic inline void xfrm6_net_sysctl_exit(struct net *net)
2518c2ecf20Sopenharmony_ci{
2528c2ecf20Sopenharmony_ci}
2538c2ecf20Sopenharmony_ci#endif
2548c2ecf20Sopenharmony_ci
2558c2ecf20Sopenharmony_cistatic int __net_init xfrm6_net_init(struct net *net)
2568c2ecf20Sopenharmony_ci{
2578c2ecf20Sopenharmony_ci	int ret;
2588c2ecf20Sopenharmony_ci
2598c2ecf20Sopenharmony_ci	memcpy(&net->xfrm.xfrm6_dst_ops, &xfrm6_dst_ops_template,
2608c2ecf20Sopenharmony_ci	       sizeof(xfrm6_dst_ops_template));
2618c2ecf20Sopenharmony_ci	ret = dst_entries_init(&net->xfrm.xfrm6_dst_ops);
2628c2ecf20Sopenharmony_ci	if (ret)
2638c2ecf20Sopenharmony_ci		return ret;
2648c2ecf20Sopenharmony_ci
2658c2ecf20Sopenharmony_ci	ret = xfrm6_net_sysctl_init(net);
2668c2ecf20Sopenharmony_ci	if (ret)
2678c2ecf20Sopenharmony_ci		dst_entries_destroy(&net->xfrm.xfrm6_dst_ops);
2688c2ecf20Sopenharmony_ci
2698c2ecf20Sopenharmony_ci	return ret;
2708c2ecf20Sopenharmony_ci}
2718c2ecf20Sopenharmony_ci
2728c2ecf20Sopenharmony_cistatic void __net_exit xfrm6_net_exit(struct net *net)
2738c2ecf20Sopenharmony_ci{
2748c2ecf20Sopenharmony_ci	xfrm6_net_sysctl_exit(net);
2758c2ecf20Sopenharmony_ci	dst_entries_destroy(&net->xfrm.xfrm6_dst_ops);
2768c2ecf20Sopenharmony_ci}
2778c2ecf20Sopenharmony_ci
2788c2ecf20Sopenharmony_cistatic struct pernet_operations xfrm6_net_ops = {
2798c2ecf20Sopenharmony_ci	.init	= xfrm6_net_init,
2808c2ecf20Sopenharmony_ci	.exit	= xfrm6_net_exit,
2818c2ecf20Sopenharmony_ci};
2828c2ecf20Sopenharmony_ci
2838c2ecf20Sopenharmony_ciint __init xfrm6_init(void)
2848c2ecf20Sopenharmony_ci{
2858c2ecf20Sopenharmony_ci	int ret;
2868c2ecf20Sopenharmony_ci
2878c2ecf20Sopenharmony_ci	ret = xfrm6_policy_init();
2888c2ecf20Sopenharmony_ci	if (ret)
2898c2ecf20Sopenharmony_ci		goto out;
2908c2ecf20Sopenharmony_ci	ret = xfrm6_state_init();
2918c2ecf20Sopenharmony_ci	if (ret)
2928c2ecf20Sopenharmony_ci		goto out_policy;
2938c2ecf20Sopenharmony_ci
2948c2ecf20Sopenharmony_ci	ret = xfrm6_protocol_init();
2958c2ecf20Sopenharmony_ci	if (ret)
2968c2ecf20Sopenharmony_ci		goto out_state;
2978c2ecf20Sopenharmony_ci
2988c2ecf20Sopenharmony_ci	ret = register_pernet_subsys(&xfrm6_net_ops);
2998c2ecf20Sopenharmony_ci	if (ret)
3008c2ecf20Sopenharmony_ci		goto out_protocol;
3018c2ecf20Sopenharmony_ciout:
3028c2ecf20Sopenharmony_ci	return ret;
3038c2ecf20Sopenharmony_ciout_protocol:
3048c2ecf20Sopenharmony_ci	xfrm6_protocol_fini();
3058c2ecf20Sopenharmony_ciout_state:
3068c2ecf20Sopenharmony_ci	xfrm6_state_fini();
3078c2ecf20Sopenharmony_ciout_policy:
3088c2ecf20Sopenharmony_ci	xfrm6_policy_fini();
3098c2ecf20Sopenharmony_ci	goto out;
3108c2ecf20Sopenharmony_ci}
3118c2ecf20Sopenharmony_ci
3128c2ecf20Sopenharmony_civoid xfrm6_fini(void)
3138c2ecf20Sopenharmony_ci{
3148c2ecf20Sopenharmony_ci	unregister_pernet_subsys(&xfrm6_net_ops);
3158c2ecf20Sopenharmony_ci	xfrm6_protocol_fini();
3168c2ecf20Sopenharmony_ci	xfrm6_policy_fini();
3178c2ecf20Sopenharmony_ci	xfrm6_state_fini();
3188c2ecf20Sopenharmony_ci}
319