linux/net/ipv6/netfilter.c
Eric Leblond 9f40ac713c netfilter: nfmark IPV6 routing in OUTPUT, mangle, NFQUEUE
This patch let nfmark to be evaluated for routing decision for OUTPUT
packet, in mangle table, when process paquet in NFQUEUE. This patch is
an IPv6 port of Laurent Licour IPv4 one.

Signed-off-by: Eric Leblond <eric@inl.fr>
Signed-off-by: Patrick McHardy <kaber@trash.net>
2008-11-25 12:18:11 +01:00

180 lines
4.4 KiB
C

#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/ipv6.h>
#include <linux/netfilter.h>
#include <linux/netfilter_ipv6.h>
#include <net/dst.h>
#include <net/ipv6.h>
#include <net/ip6_route.h>
#include <net/xfrm.h>
#include <net/ip6_checksum.h>
#include <net/netfilter/nf_queue.h>
int ip6_route_me_harder(struct sk_buff *skb)
{
struct net *net = dev_net(skb->dst->dev);
struct ipv6hdr *iph = ipv6_hdr(skb);
struct dst_entry *dst;
struct flowi fl = {
.oif = skb->sk ? skb->sk->sk_bound_dev_if : 0,
.mark = skb->mark,
.nl_u =
{ .ip6_u =
{ .daddr = iph->daddr,
.saddr = iph->saddr, } },
};
dst = ip6_route_output(net, skb->sk, &fl);
#ifdef CONFIG_XFRM
if (!(IP6CB(skb)->flags & IP6SKB_XFRM_TRANSFORMED) &&
xfrm_decode_session(skb, &fl, AF_INET6) == 0)
if (xfrm_lookup(&skb->dst, &fl, skb->sk, 0))
return -1;
#endif
if (dst->error) {
IP6_INC_STATS(net, ip6_dst_idev(dst), IPSTATS_MIB_OUTNOROUTES);
LIMIT_NETDEBUG(KERN_DEBUG "ip6_route_me_harder: No more route.\n");
dst_release(dst);
return -EINVAL;
}
/* Drop old route. */
dst_release(skb->dst);
skb->dst = dst;
return 0;
}
EXPORT_SYMBOL(ip6_route_me_harder);
/*
* Extra routing may needed on local out, as the QUEUE target never
* returns control to the table.
*/
struct ip6_rt_info {
struct in6_addr daddr;
struct in6_addr saddr;
u_int32_t mark;
};
static void nf_ip6_saveroute(const struct sk_buff *skb,
struct nf_queue_entry *entry)
{
struct ip6_rt_info *rt_info = nf_queue_entry_reroute(entry);
if (entry->hook == NF_INET_LOCAL_OUT) {
struct ipv6hdr *iph = ipv6_hdr(skb);
rt_info->daddr = iph->daddr;
rt_info->saddr = iph->saddr;
rt_info->mark = skb->mark;
}
}
static int nf_ip6_reroute(struct sk_buff *skb,
const struct nf_queue_entry *entry)
{
struct ip6_rt_info *rt_info = nf_queue_entry_reroute(entry);
if (entry->hook == NF_INET_LOCAL_OUT) {
struct ipv6hdr *iph = ipv6_hdr(skb);
if (!ipv6_addr_equal(&iph->daddr, &rt_info->daddr) ||
!ipv6_addr_equal(&iph->saddr, &rt_info->saddr) ||
skb->mark != rt_info->mark)
return ip6_route_me_harder(skb);
}
return 0;
}
static int nf_ip6_route(struct dst_entry **dst, struct flowi *fl)
{
*dst = ip6_route_output(&init_net, NULL, fl);
return (*dst)->error;
}
__sum16 nf_ip6_checksum(struct sk_buff *skb, unsigned int hook,
unsigned int dataoff, u_int8_t protocol)
{
struct ipv6hdr *ip6h = ipv6_hdr(skb);
__sum16 csum = 0;
switch (skb->ip_summed) {
case CHECKSUM_COMPLETE:
if (hook != NF_INET_PRE_ROUTING && hook != NF_INET_LOCAL_IN)
break;
if (!csum_ipv6_magic(&ip6h->saddr, &ip6h->daddr,
skb->len - dataoff, protocol,
csum_sub(skb->csum,
skb_checksum(skb, 0,
dataoff, 0)))) {
skb->ip_summed = CHECKSUM_UNNECESSARY;
break;
}
/* fall through */
case CHECKSUM_NONE:
skb->csum = ~csum_unfold(
csum_ipv6_magic(&ip6h->saddr, &ip6h->daddr,
skb->len - dataoff,
protocol,
csum_sub(0,
skb_checksum(skb, 0,
dataoff, 0))));
csum = __skb_checksum_complete(skb);
}
return csum;
}
EXPORT_SYMBOL(nf_ip6_checksum);
static __sum16 nf_ip6_checksum_partial(struct sk_buff *skb, unsigned int hook,
unsigned int dataoff, unsigned int len,
u_int8_t protocol)
{
struct ipv6hdr *ip6h = ipv6_hdr(skb);
__wsum hsum;
__sum16 csum = 0;
switch (skb->ip_summed) {
case CHECKSUM_COMPLETE:
if (len == skb->len - dataoff)
return nf_ip6_checksum(skb, hook, dataoff, protocol);
/* fall through */
case CHECKSUM_NONE:
hsum = skb_checksum(skb, 0, dataoff, 0);
skb->csum = ~csum_unfold(csum_ipv6_magic(&ip6h->saddr,
&ip6h->daddr,
skb->len - dataoff,
protocol,
csum_sub(0, hsum)));
skb->ip_summed = CHECKSUM_NONE;
csum = __skb_checksum_complete_head(skb, dataoff + len);
if (!csum)
skb->ip_summed = CHECKSUM_UNNECESSARY;
}
return csum;
};
static const struct nf_afinfo nf_ip6_afinfo = {
.family = AF_INET6,
.checksum = nf_ip6_checksum,
.checksum_partial = nf_ip6_checksum_partial,
.route = nf_ip6_route,
.saveroute = nf_ip6_saveroute,
.reroute = nf_ip6_reroute,
.route_key_size = sizeof(struct ip6_rt_info),
};
int __init ipv6_netfilter_init(void)
{
return nf_register_afinfo(&nf_ip6_afinfo);
}
/* This can be called from inet6_init() on errors, so it cannot
* be marked __exit. -DaveM
*/
void ipv6_netfilter_fini(void)
{
nf_unregister_afinfo(&nf_ip6_afinfo);
}