2007-02-09 07:24:47 -07:00
|
|
|
/*
|
2005-04-16 16:20:36 -06:00
|
|
|
* xfrm4_policy.c
|
|
|
|
*
|
|
|
|
* Changes:
|
|
|
|
* Kazunori MIYAZAWA @USAGI
|
|
|
|
* YOSHIFUJI Hideaki @USAGI
|
|
|
|
* Split up af-specific portion
|
2007-02-09 07:24:47 -07:00
|
|
|
*
|
2005-04-16 16:20:36 -06:00
|
|
|
*/
|
|
|
|
|
2007-11-13 22:37:28 -07:00
|
|
|
#include <linux/err.h>
|
|
|
|
#include <linux/kernel.h>
|
2005-05-03 17:27:10 -06:00
|
|
|
#include <linux/inetdevice.h>
|
2007-11-13 22:35:32 -07:00
|
|
|
#include <net/dst.h>
|
2005-04-16 16:20:36 -06:00
|
|
|
#include <net/xfrm.h>
|
|
|
|
#include <net/ip.h>
|
|
|
|
|
|
|
|
static struct xfrm_policy_afinfo xfrm4_policy_afinfo;
|
|
|
|
|
2008-11-25 18:51:25 -07:00
|
|
|
static struct dst_entry *xfrm4_dst_lookup(struct net *net, int tos,
|
|
|
|
xfrm_address_t *saddr,
|
2007-11-13 22:37:28 -07:00
|
|
|
xfrm_address_t *daddr)
|
2005-04-16 16:20:36 -06:00
|
|
|
{
|
2007-11-13 22:37:28 -07:00
|
|
|
struct flowi fl = {
|
2006-09-19 13:57:34 -06:00
|
|
|
.nl_u = {
|
|
|
|
.ip4_u = {
|
2007-11-13 22:37:28 -07:00
|
|
|
.tos = tos,
|
2006-09-19 13:57:34 -06:00
|
|
|
.daddr = daddr->a4,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
};
|
2007-11-13 22:37:28 -07:00
|
|
|
struct dst_entry *dst;
|
|
|
|
struct rtable *rt;
|
|
|
|
int err;
|
2006-09-19 13:57:34 -06:00
|
|
|
|
2007-11-13 22:37:28 -07:00
|
|
|
if (saddr)
|
|
|
|
fl.fl4_src = saddr->a4;
|
|
|
|
|
2008-11-25 18:51:25 -07:00
|
|
|
err = __ip_route_output_key(net, &rt, &fl);
|
2010-06-11 00:31:35 -06:00
|
|
|
dst = &rt->dst;
|
2007-11-13 22:37:28 -07:00
|
|
|
if (err)
|
|
|
|
dst = ERR_PTR(err);
|
|
|
|
return dst;
|
|
|
|
}
|
|
|
|
|
2008-11-25 18:56:49 -07:00
|
|
|
static int xfrm4_get_saddr(struct net *net,
|
|
|
|
xfrm_address_t *saddr, xfrm_address_t *daddr)
|
2007-11-13 22:37:28 -07:00
|
|
|
{
|
|
|
|
struct dst_entry *dst;
|
|
|
|
struct rtable *rt;
|
|
|
|
|
2008-11-25 18:56:49 -07:00
|
|
|
dst = xfrm4_dst_lookup(net, 0, NULL, daddr);
|
2007-11-13 22:37:28 -07:00
|
|
|
if (IS_ERR(dst))
|
|
|
|
return -EHOSTUNREACH;
|
|
|
|
|
|
|
|
rt = (struct rtable *)dst;
|
|
|
|
saddr->a4 = rt->rt_src;
|
|
|
|
dst_release(dst);
|
|
|
|
return 0;
|
2006-09-19 13:57:34 -06:00
|
|
|
}
|
|
|
|
|
2007-12-11 10:32:34 -07:00
|
|
|
static int xfrm4_get_tos(struct flowi *fl)
|
2005-04-16 16:20:36 -06:00
|
|
|
{
|
2010-09-22 00:45:11 -06:00
|
|
|
return IPTOS_RT_MASK & fl->fl4_tos; /* Strip ECN bits */
|
2007-12-11 10:32:34 -07:00
|
|
|
}
|
2005-04-16 16:20:36 -06:00
|
|
|
|
2007-12-20 21:41:12 -07:00
|
|
|
static int xfrm4_init_path(struct xfrm_dst *path, struct dst_entry *dst,
|
|
|
|
int nfheader_len)
|
|
|
|
{
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2010-03-01 19:51:56 -07:00
|
|
|
static int xfrm4_fill_dst(struct xfrm_dst *xdst, struct net_device *dev,
|
|
|
|
struct flowi *fl)
|
2007-12-11 10:32:34 -07:00
|
|
|
{
|
|
|
|
struct rtable *rt = (struct rtable *)xdst->route;
|
2005-04-16 16:20:36 -06:00
|
|
|
|
2010-03-01 19:51:56 -07:00
|
|
|
xdst->u.rt.fl = *fl;
|
2005-04-16 16:20:36 -06:00
|
|
|
|
2007-12-11 10:32:34 -07:00
|
|
|
xdst->u.dst.dev = dev;
|
|
|
|
dev_hold(dev);
|
2007-02-06 15:27:32 -07:00
|
|
|
|
2007-12-11 10:32:34 -07:00
|
|
|
xdst->u.rt.idev = in_dev_get(dev);
|
|
|
|
if (!xdst->u.rt.idev)
|
|
|
|
return -ENODEV;
|
2005-04-16 16:20:36 -06:00
|
|
|
|
2007-12-11 10:32:34 -07:00
|
|
|
xdst->u.rt.peer = rt->peer;
|
|
|
|
if (rt->peer)
|
|
|
|
atomic_inc(&rt->peer->refcnt);
|
2007-11-13 22:37:28 -07:00
|
|
|
|
2007-12-11 10:32:34 -07:00
|
|
|
/* Sheit... I remember I did this right. Apparently,
|
|
|
|
* it was magically lost, so this code needs audit */
|
|
|
|
xdst->u.rt.rt_flags = rt->rt_flags & (RTCF_BROADCAST | RTCF_MULTICAST |
|
|
|
|
RTCF_LOCAL);
|
|
|
|
xdst->u.rt.rt_type = rt->rt_type;
|
|
|
|
xdst->u.rt.rt_src = rt->rt_src;
|
|
|
|
xdst->u.rt.rt_dst = rt->rt_dst;
|
|
|
|
xdst->u.rt.rt_gateway = rt->rt_gateway;
|
|
|
|
xdst->u.rt.rt_spec_dst = rt->rt_spec_dst;
|
2005-04-16 16:20:36 -06:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
2007-12-12 11:44:16 -07:00
|
|
|
_decode_session4(struct sk_buff *skb, struct flowi *fl, int reverse)
|
2005-04-16 16:20:36 -06:00
|
|
|
{
|
2007-04-20 23:47:35 -06:00
|
|
|
struct iphdr *iph = ip_hdr(skb);
|
2007-04-10 21:50:43 -06:00
|
|
|
u8 *xprth = skb_network_header(skb) + iph->ihl * 4;
|
2005-04-16 16:20:36 -06:00
|
|
|
|
|
|
|
memset(fl, 0, sizeof(struct flowi));
|
2010-07-02 01:47:55 -06:00
|
|
|
fl->mark = skb->mark;
|
|
|
|
|
2005-04-16 16:20:36 -06:00
|
|
|
if (!(iph->frag_off & htons(IP_MF | IP_OFFSET))) {
|
|
|
|
switch (iph->protocol) {
|
|
|
|
case IPPROTO_UDP:
|
2006-11-27 12:10:57 -07:00
|
|
|
case IPPROTO_UDPLITE:
|
2005-04-16 16:20:36 -06:00
|
|
|
case IPPROTO_TCP:
|
|
|
|
case IPPROTO_SCTP:
|
2005-12-19 15:03:46 -07:00
|
|
|
case IPPROTO_DCCP:
|
2009-07-02 10:57:23 -06:00
|
|
|
if (xprth + 4 < skb->data ||
|
|
|
|
pskb_may_pull(skb, xprth + 4 - skb->data)) {
|
2006-11-08 01:20:21 -07:00
|
|
|
__be16 *ports = (__be16 *)xprth;
|
2005-04-16 16:20:36 -06:00
|
|
|
|
2007-12-12 11:44:16 -07:00
|
|
|
fl->fl_ip_sport = ports[!!reverse];
|
|
|
|
fl->fl_ip_dport = ports[!reverse];
|
2005-04-16 16:20:36 -06:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case IPPROTO_ICMP:
|
|
|
|
if (pskb_may_pull(skb, xprth + 2 - skb->data)) {
|
|
|
|
u8 *icmp = xprth;
|
|
|
|
|
|
|
|
fl->fl_icmp_type = icmp[0];
|
|
|
|
fl->fl_icmp_code = icmp[1];
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case IPPROTO_ESP:
|
|
|
|
if (pskb_may_pull(skb, xprth + 4 - skb->data)) {
|
2006-09-27 19:49:07 -06:00
|
|
|
__be32 *ehdr = (__be32 *)xprth;
|
2005-04-16 16:20:36 -06:00
|
|
|
|
|
|
|
fl->fl_ipsec_spi = ehdr[0];
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case IPPROTO_AH:
|
|
|
|
if (pskb_may_pull(skb, xprth + 8 - skb->data)) {
|
2006-09-27 19:49:07 -06:00
|
|
|
__be32 *ah_hdr = (__be32*)xprth;
|
2005-04-16 16:20:36 -06:00
|
|
|
|
|
|
|
fl->fl_ipsec_spi = ah_hdr[1];
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case IPPROTO_COMP:
|
|
|
|
if (pskb_may_pull(skb, xprth + 4 - skb->data)) {
|
2006-09-27 19:49:07 -06:00
|
|
|
__be16 *ipcomp_hdr = (__be16 *)xprth;
|
2005-04-16 16:20:36 -06:00
|
|
|
|
2006-05-22 17:53:22 -06:00
|
|
|
fl->fl_ipsec_spi = htonl(ntohs(ipcomp_hdr[1]));
|
2005-04-16 16:20:36 -06:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
fl->fl_ipsec_spi = 0;
|
|
|
|
break;
|
2007-04-20 18:09:22 -06:00
|
|
|
}
|
2005-04-16 16:20:36 -06:00
|
|
|
}
|
|
|
|
fl->proto = iph->protocol;
|
2007-12-12 11:44:16 -07:00
|
|
|
fl->fl4_dst = reverse ? iph->saddr : iph->daddr;
|
|
|
|
fl->fl4_src = reverse ? iph->daddr : iph->saddr;
|
2006-02-23 17:19:26 -07:00
|
|
|
fl->fl4_tos = iph->tos;
|
2005-04-16 16:20:36 -06:00
|
|
|
}
|
|
|
|
|
2008-01-18 04:56:57 -07:00
|
|
|
static inline int xfrm4_garbage_collect(struct dst_ops *ops)
|
2005-04-16 16:20:36 -06:00
|
|
|
{
|
2010-01-24 23:47:53 -07:00
|
|
|
struct net *net = container_of(ops, struct net, xfrm.xfrm4_dst_ops);
|
|
|
|
|
|
|
|
xfrm4_policy_afinfo.garbage_collect(net);
|
2010-10-08 00:37:34 -06:00
|
|
|
return (dst_entries_get_slow(ops) > ops->gc_thresh * 2);
|
2005-04-16 16:20:36 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
static void xfrm4_update_pmtu(struct dst_entry *dst, u32 mtu)
|
|
|
|
{
|
|
|
|
struct xfrm_dst *xdst = (struct xfrm_dst *)dst;
|
|
|
|
struct dst_entry *path = xdst->route;
|
|
|
|
|
|
|
|
path->ops->update_pmtu(path, mtu);
|
|
|
|
}
|
|
|
|
|
2005-05-03 17:27:10 -06:00
|
|
|
static void xfrm4_dst_destroy(struct dst_entry *dst)
|
|
|
|
{
|
|
|
|
struct xfrm_dst *xdst = (struct xfrm_dst *)dst;
|
|
|
|
|
|
|
|
if (likely(xdst->u.rt.idev))
|
|
|
|
in_dev_put(xdst->u.rt.idev);
|
2007-10-17 22:34:46 -06:00
|
|
|
if (likely(xdst->u.rt.peer))
|
2006-12-07 00:45:15 -07:00
|
|
|
inet_putpeer(xdst->u.rt.peer);
|
2005-05-03 17:27:10 -06:00
|
|
|
xfrm_dst_destroy(xdst);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void xfrm4_dst_ifdown(struct dst_entry *dst, struct net_device *dev,
|
|
|
|
int unregister)
|
|
|
|
{
|
|
|
|
struct xfrm_dst *xdst;
|
|
|
|
|
|
|
|
if (!unregister)
|
|
|
|
return;
|
|
|
|
|
|
|
|
xdst = (struct xfrm_dst *)dst;
|
|
|
|
if (xdst->u.rt.idev->dev == dev) {
|
2007-12-07 01:38:10 -07:00
|
|
|
struct in_device *loopback_idev =
|
2008-03-25 06:47:49 -06:00
|
|
|
in_dev_get(dev_net(dev)->loopback_dev);
|
2005-05-03 17:27:10 -06:00
|
|
|
BUG_ON(!loopback_idev);
|
|
|
|
|
|
|
|
do {
|
|
|
|
in_dev_put(xdst->u.rt.idev);
|
|
|
|
xdst->u.rt.idev = loopback_idev;
|
|
|
|
in_dev_hold(loopback_idev);
|
|
|
|
xdst = (struct xfrm_dst *)xdst->u.dst.child;
|
|
|
|
} while (xdst->u.dst.xfrm);
|
|
|
|
|
|
|
|
__in_dev_put(loopback_idev);
|
|
|
|
}
|
|
|
|
|
|
|
|
xfrm_dst_ifdown(dst, dev);
|
|
|
|
}
|
|
|
|
|
2005-04-16 16:20:36 -06:00
|
|
|
static struct dst_ops xfrm4_dst_ops = {
|
|
|
|
.family = AF_INET,
|
2009-02-01 01:45:17 -07:00
|
|
|
.protocol = cpu_to_be16(ETH_P_IP),
|
2005-04-16 16:20:36 -06:00
|
|
|
.gc = xfrm4_garbage_collect,
|
|
|
|
.update_pmtu = xfrm4_update_pmtu,
|
2005-05-03 17:27:10 -06:00
|
|
|
.destroy = xfrm4_dst_destroy,
|
|
|
|
.ifdown = xfrm4_dst_ifdown,
|
2007-11-13 22:43:11 -07:00
|
|
|
.local_out = __ip_local_out,
|
2005-04-16 16:20:36 -06:00
|
|
|
.gc_thresh = 1024,
|
|
|
|
};
|
|
|
|
|
|
|
|
static struct xfrm_policy_afinfo xfrm4_policy_afinfo = {
|
|
|
|
.family = AF_INET,
|
|
|
|
.dst_ops = &xfrm4_dst_ops,
|
|
|
|
.dst_lookup = xfrm4_dst_lookup,
|
2006-09-19 13:57:34 -06:00
|
|
|
.get_saddr = xfrm4_get_saddr,
|
2005-04-16 16:20:36 -06:00
|
|
|
.decode_session = _decode_session4,
|
2007-12-11 10:32:34 -07:00
|
|
|
.get_tos = xfrm4_get_tos,
|
2007-12-20 21:41:12 -07:00
|
|
|
.init_path = xfrm4_init_path,
|
2007-12-11 10:32:34 -07:00
|
|
|
.fill_dst = xfrm4_fill_dst,
|
2005-04-16 16:20:36 -06:00
|
|
|
};
|
|
|
|
|
2009-08-04 21:18:33 -06:00
|
|
|
#ifdef CONFIG_SYSCTL
|
2009-07-27 02:22:46 -06:00
|
|
|
static struct ctl_table xfrm4_policy_table[] = {
|
|
|
|
{
|
|
|
|
.procname = "xfrm4_gc_thresh",
|
2010-01-24 23:47:53 -07:00
|
|
|
.data = &init_net.xfrm.xfrm4_dst_ops.gc_thresh,
|
2009-07-27 02:22:46 -06:00
|
|
|
.maxlen = sizeof(int),
|
|
|
|
.mode = 0644,
|
|
|
|
.proc_handler = proc_dointvec,
|
|
|
|
},
|
|
|
|
{ }
|
|
|
|
};
|
|
|
|
|
|
|
|
static struct ctl_table_header *sysctl_hdr;
|
2009-08-04 21:18:33 -06:00
|
|
|
#endif
|
2009-07-27 02:22:46 -06:00
|
|
|
|
2005-04-16 16:20:36 -06:00
|
|
|
static void __init xfrm4_policy_init(void)
|
|
|
|
{
|
|
|
|
xfrm_policy_register_afinfo(&xfrm4_policy_afinfo);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void __exit xfrm4_policy_fini(void)
|
|
|
|
{
|
2009-08-04 21:18:33 -06:00
|
|
|
#ifdef CONFIG_SYSCTL
|
2009-07-27 02:22:46 -06:00
|
|
|
if (sysctl_hdr)
|
|
|
|
unregister_net_sysctl_table(sysctl_hdr);
|
2009-08-04 21:18:33 -06:00
|
|
|
#endif
|
2005-04-16 16:20:36 -06:00
|
|
|
xfrm_policy_unregister_afinfo(&xfrm4_policy_afinfo);
|
|
|
|
}
|
|
|
|
|
2009-07-30 19:52:15 -06:00
|
|
|
void __init xfrm4_init(int rt_max_size)
|
2005-04-16 16:20:36 -06:00
|
|
|
{
|
2009-07-30 19:52:15 -06:00
|
|
|
/*
|
|
|
|
* Select a default value for the gc_thresh based on the main route
|
|
|
|
* table hash size. It seems to me the worst case scenario is when
|
|
|
|
* we have ipsec operating in transport mode, in which we create a
|
|
|
|
* dst_entry per socket. The xfrm gc algorithm starts trying to remove
|
|
|
|
* entries at gc_thresh, and prevents new allocations as 2*gc_thresh
|
|
|
|
* so lets set an initial xfrm gc_thresh value at the rt_max_size/2.
|
|
|
|
* That will let us store an ipsec connection per route table entry,
|
|
|
|
* and start cleaning when were 1/2 full
|
|
|
|
*/
|
|
|
|
xfrm4_dst_ops.gc_thresh = rt_max_size/2;
|
2010-10-08 00:37:34 -06:00
|
|
|
dst_entries_init(&xfrm4_dst_ops);
|
2010-01-24 23:47:53 -07:00
|
|
|
|
|
|
|
xfrm4_state_init();
|
|
|
|
xfrm4_policy_init();
|
2009-08-04 21:18:33 -06:00
|
|
|
#ifdef CONFIG_SYSCTL
|
2009-07-27 02:22:46 -06:00
|
|
|
sysctl_hdr = register_net_sysctl_table(&init_net, net_ipv4_ctl_path,
|
|
|
|
xfrm4_policy_table);
|
2009-08-04 21:18:33 -06:00
|
|
|
#endif
|
2005-04-16 16:20:36 -06:00
|
|
|
}
|
|
|
|
|