forked from Minki/linux
9f40ac713c
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>
180 lines
4.4 KiB
C
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);
|
|
}
|