| 1 | #include <linux/kernel.h> |
| 2 | #include <linux/init.h> |
| 3 | #include <linux/ipv6.h> |
| 4 | #include <linux/netfilter.h> |
| 5 | #include <linux/netfilter_ipv6.h> |
| 6 | #include <linux/export.h> |
| 7 | #include <net/dst.h> |
| 8 | #include <net/ipv6.h> |
| 9 | #include <net/ip6_route.h> |
| 10 | #include <net/xfrm.h> |
| 11 | #include <net/ip6_checksum.h> |
| 12 | #include <net/netfilter/nf_queue.h> |
| 13 | |
| 14 | int ip6_route_me_harder(struct sk_buff *skb) |
| 15 | { |
| 16 | struct net *net = dev_net(skb_dst(skb)->dev); |
| 17 | const struct ipv6hdr *iph = ipv6_hdr(skb); |
| 18 | struct dst_entry *dst; |
| 19 | struct flowi6 fl6 = { |
| 20 | .flowi6_oif = skb->sk ? skb->sk->sk_bound_dev_if : 0, |
| 21 | .flowi6_mark = skb->mark, |
| 22 | .daddr = iph->daddr, |
| 23 | .saddr = iph->saddr, |
| 24 | }; |
| 25 | |
| 26 | dst = ip6_route_output(net, skb->sk, &fl6); |
| 27 | if (dst->error) { |
| 28 | IP6_INC_STATS(net, ip6_dst_idev(dst), IPSTATS_MIB_OUTNOROUTES); |
| 29 | LIMIT_NETDEBUG(KERN_DEBUG "ip6_route_me_harder: No more route.\n"); |
| 30 | dst_release(dst); |
| 31 | return -EINVAL; |
| 32 | } |
| 33 | |
| 34 | /* Drop old route. */ |
| 35 | skb_dst_drop(skb); |
| 36 | |
| 37 | skb_dst_set(skb, dst); |
| 38 | |
| 39 | #ifdef CONFIG_XFRM |
| 40 | if (!(IP6CB(skb)->flags & IP6SKB_XFRM_TRANSFORMED) && |
| 41 | xfrm_decode_session(skb, flowi6_to_flowi(&fl6), AF_INET6) == 0) { |
| 42 | skb_dst_set(skb, NULL); |
| 43 | dst = xfrm_lookup(net, dst, flowi6_to_flowi(&fl6), skb->sk, 0); |
| 44 | if (IS_ERR(dst)) |
| 45 | return -1; |
| 46 | skb_dst_set(skb, dst); |
| 47 | } |
| 48 | #endif |
| 49 | |
| 50 | return 0; |
| 51 | } |
| 52 | EXPORT_SYMBOL(ip6_route_me_harder); |
| 53 | |
| 54 | /* |
| 55 | * Extra routing may needed on local out, as the QUEUE target never |
| 56 | * returns control to the table. |
| 57 | */ |
| 58 | |
| 59 | struct ip6_rt_info { |
| 60 | struct in6_addr daddr; |
| 61 | struct in6_addr saddr; |
| 62 | u_int32_t mark; |
| 63 | }; |
| 64 | |
| 65 | static void nf_ip6_saveroute(const struct sk_buff *skb, |
| 66 | struct nf_queue_entry *entry) |
| 67 | { |
| 68 | struct ip6_rt_info *rt_info = nf_queue_entry_reroute(entry); |
| 69 | |
| 70 | if (entry->hook == NF_INET_LOCAL_OUT) { |
| 71 | const struct ipv6hdr *iph = ipv6_hdr(skb); |
| 72 | |
| 73 | rt_info->daddr = iph->daddr; |
| 74 | rt_info->saddr = iph->saddr; |
| 75 | rt_info->mark = skb->mark; |
| 76 | } |
| 77 | } |
| 78 | |
| 79 | static int nf_ip6_reroute(struct sk_buff *skb, |
| 80 | const struct nf_queue_entry *entry) |
| 81 | { |
| 82 | struct ip6_rt_info *rt_info = nf_queue_entry_reroute(entry); |
| 83 | |
| 84 | if (entry->hook == NF_INET_LOCAL_OUT) { |
| 85 | const struct ipv6hdr *iph = ipv6_hdr(skb); |
| 86 | if (!ipv6_addr_equal(&iph->daddr, &rt_info->daddr) || |
| 87 | !ipv6_addr_equal(&iph->saddr, &rt_info->saddr) || |
| 88 | skb->mark != rt_info->mark) |
| 89 | return ip6_route_me_harder(skb); |
| 90 | } |
| 91 | return 0; |
| 92 | } |
| 93 | |
| 94 | static int nf_ip6_route(struct net *net, struct dst_entry **dst, |
| 95 | struct flowi *fl, bool strict) |
| 96 | { |
| 97 | static const struct ipv6_pinfo fake_pinfo; |
| 98 | static const struct inet_sock fake_sk = { |
| 99 | /* makes ip6_route_output set RT6_LOOKUP_F_IFACE: */ |
| 100 | .sk.sk_bound_dev_if = 1, |
| 101 | .pinet6 = (struct ipv6_pinfo *) &fake_pinfo, |
| 102 | }; |
| 103 | const void *sk = strict ? &fake_sk : NULL; |
| 104 | |
| 105 | *dst = ip6_route_output(net, sk, &fl->u.ip6); |
| 106 | return (*dst)->error; |
| 107 | } |
| 108 | |
| 109 | __sum16 nf_ip6_checksum(struct sk_buff *skb, unsigned int hook, |
| 110 | unsigned int dataoff, u_int8_t protocol) |
| 111 | { |
| 112 | const struct ipv6hdr *ip6h = ipv6_hdr(skb); |
| 113 | __sum16 csum = 0; |
| 114 | |
| 115 | switch (skb->ip_summed) { |
| 116 | case CHECKSUM_COMPLETE: |
| 117 | if (hook != NF_INET_PRE_ROUTING && hook != NF_INET_LOCAL_IN) |
| 118 | break; |
| 119 | if (!csum_ipv6_magic(&ip6h->saddr, &ip6h->daddr, |
| 120 | skb->len - dataoff, protocol, |
| 121 | csum_sub(skb->csum, |
| 122 | skb_checksum(skb, 0, |
| 123 | dataoff, 0)))) { |
| 124 | skb->ip_summed = CHECKSUM_UNNECESSARY; |
| 125 | break; |
| 126 | } |
| 127 | /* fall through */ |
| 128 | case CHECKSUM_NONE: |
| 129 | skb->csum = ~csum_unfold( |
| 130 | csum_ipv6_magic(&ip6h->saddr, &ip6h->daddr, |
| 131 | skb->len - dataoff, |
| 132 | protocol, |
| 133 | csum_sub(0, |
| 134 | skb_checksum(skb, 0, |
| 135 | dataoff, 0)))); |
| 136 | csum = __skb_checksum_complete(skb); |
| 137 | } |
| 138 | return csum; |
| 139 | } |
| 140 | EXPORT_SYMBOL(nf_ip6_checksum); |
| 141 | |
| 142 | static __sum16 nf_ip6_checksum_partial(struct sk_buff *skb, unsigned int hook, |
| 143 | unsigned int dataoff, unsigned int len, |
| 144 | u_int8_t protocol) |
| 145 | { |
| 146 | const struct ipv6hdr *ip6h = ipv6_hdr(skb); |
| 147 | __wsum hsum; |
| 148 | __sum16 csum = 0; |
| 149 | |
| 150 | switch (skb->ip_summed) { |
| 151 | case CHECKSUM_COMPLETE: |
| 152 | if (len == skb->len - dataoff) |
| 153 | return nf_ip6_checksum(skb, hook, dataoff, protocol); |
| 154 | /* fall through */ |
| 155 | case CHECKSUM_NONE: |
| 156 | hsum = skb_checksum(skb, 0, dataoff, 0); |
| 157 | skb->csum = ~csum_unfold(csum_ipv6_magic(&ip6h->saddr, |
| 158 | &ip6h->daddr, |
| 159 | skb->len - dataoff, |
| 160 | protocol, |
| 161 | csum_sub(0, hsum))); |
| 162 | skb->ip_summed = CHECKSUM_NONE; |
| 163 | return __skb_checksum_complete_head(skb, dataoff + len); |
| 164 | } |
| 165 | return csum; |
| 166 | }; |
| 167 | |
| 168 | static const struct nf_afinfo nf_ip6_afinfo = { |
| 169 | .family = AF_INET6, |
| 170 | .checksum = nf_ip6_checksum, |
| 171 | .checksum_partial = nf_ip6_checksum_partial, |
| 172 | .route = nf_ip6_route, |
| 173 | .saveroute = nf_ip6_saveroute, |
| 174 | .reroute = nf_ip6_reroute, |
| 175 | .route_key_size = sizeof(struct ip6_rt_info), |
| 176 | }; |
| 177 | |
| 178 | int __init ipv6_netfilter_init(void) |
| 179 | { |
| 180 | return nf_register_afinfo(&nf_ip6_afinfo); |
| 181 | } |
| 182 | |
| 183 | /* This can be called from inet6_init() on errors, so it cannot |
| 184 | * be marked __exit. -DaveM |
| 185 | */ |
| 186 | void ipv6_netfilter_fini(void) |
| 187 | { |
| 188 | nf_unregister_afinfo(&nf_ip6_afinfo); |
| 189 | } |