linux/net/ipv6/netfilter/nf_flow_table_ipv6.c
<<
>>
Prefs
   1#include <linux/kernel.h>
   2#include <linux/init.h>
   3#include <linux/module.h>
   4#include <linux/netfilter.h>
   5#include <linux/rhashtable.h>
   6#include <linux/ipv6.h>
   7#include <linux/netdevice.h>
   8#include <net/ipv6.h>
   9#include <net/ip6_route.h>
  10#include <net/neighbour.h>
  11#include <net/netfilter/nf_flow_table.h>
  12#include <net/netfilter/nf_tables.h>
  13/* For layer 4 checksum field offset. */
  14#include <linux/tcp.h>
  15#include <linux/udp.h>
  16
  17static int nf_flow_nat_ipv6_tcp(struct sk_buff *skb, unsigned int thoff,
  18                                struct in6_addr *addr,
  19                                struct in6_addr *new_addr)
  20{
  21        struct tcphdr *tcph;
  22
  23        if (!pskb_may_pull(skb, thoff + sizeof(*tcph)) ||
  24            skb_try_make_writable(skb, thoff + sizeof(*tcph)))
  25                return -1;
  26
  27        tcph = (void *)(skb_network_header(skb) + thoff);
  28        inet_proto_csum_replace16(&tcph->check, skb, addr->s6_addr32,
  29                                  new_addr->s6_addr32, true);
  30
  31        return 0;
  32}
  33
  34static int nf_flow_nat_ipv6_udp(struct sk_buff *skb, unsigned int thoff,
  35                                struct in6_addr *addr,
  36                                struct in6_addr *new_addr)
  37{
  38        struct udphdr *udph;
  39
  40        if (!pskb_may_pull(skb, thoff + sizeof(*udph)) ||
  41            skb_try_make_writable(skb, thoff + sizeof(*udph)))
  42                return -1;
  43
  44        udph = (void *)(skb_network_header(skb) + thoff);
  45        if (udph->check || skb->ip_summed == CHECKSUM_PARTIAL) {
  46                inet_proto_csum_replace16(&udph->check, skb, addr->s6_addr32,
  47                                          new_addr->s6_addr32, true);
  48                if (!udph->check)
  49                        udph->check = CSUM_MANGLED_0;
  50        }
  51
  52        return 0;
  53}
  54
  55static int nf_flow_nat_ipv6_l4proto(struct sk_buff *skb, struct ipv6hdr *ip6h,
  56                                    unsigned int thoff, struct in6_addr *addr,
  57                                    struct in6_addr *new_addr)
  58{
  59        switch (ip6h->nexthdr) {
  60        case IPPROTO_TCP:
  61                if (nf_flow_nat_ipv6_tcp(skb, thoff, addr, new_addr) < 0)
  62                        return NF_DROP;
  63                break;
  64        case IPPROTO_UDP:
  65                if (nf_flow_nat_ipv6_udp(skb, thoff, addr, new_addr) < 0)
  66                        return NF_DROP;
  67                break;
  68        }
  69
  70        return 0;
  71}
  72
  73static int nf_flow_snat_ipv6(const struct flow_offload *flow,
  74                             struct sk_buff *skb, struct ipv6hdr *ip6h,
  75                             unsigned int thoff,
  76                             enum flow_offload_tuple_dir dir)
  77{
  78        struct in6_addr addr, new_addr;
  79
  80        switch (dir) {
  81        case FLOW_OFFLOAD_DIR_ORIGINAL:
  82                addr = ip6h->saddr;
  83                new_addr = flow->tuplehash[FLOW_OFFLOAD_DIR_REPLY].tuple.dst_v6;
  84                ip6h->saddr = new_addr;
  85                break;
  86        case FLOW_OFFLOAD_DIR_REPLY:
  87                addr = ip6h->daddr;
  88                new_addr = flow->tuplehash[FLOW_OFFLOAD_DIR_ORIGINAL].tuple.src_v6;
  89                ip6h->daddr = new_addr;
  90                break;
  91        default:
  92                return -1;
  93        }
  94
  95        return nf_flow_nat_ipv6_l4proto(skb, ip6h, thoff, &addr, &new_addr);
  96}
  97
  98static int nf_flow_dnat_ipv6(const struct flow_offload *flow,
  99                             struct sk_buff *skb, struct ipv6hdr *ip6h,
 100                             unsigned int thoff,
 101                             enum flow_offload_tuple_dir dir)
 102{
 103        struct in6_addr addr, new_addr;
 104
 105        switch (dir) {
 106        case FLOW_OFFLOAD_DIR_ORIGINAL:
 107                addr = ip6h->daddr;
 108                new_addr = flow->tuplehash[FLOW_OFFLOAD_DIR_REPLY].tuple.src_v6;
 109                ip6h->daddr = new_addr;
 110                break;
 111        case FLOW_OFFLOAD_DIR_REPLY:
 112                addr = ip6h->saddr;
 113                new_addr = flow->tuplehash[FLOW_OFFLOAD_DIR_ORIGINAL].tuple.dst_v6;
 114                ip6h->saddr = new_addr;
 115                break;
 116        default:
 117                return -1;
 118        }
 119
 120        return nf_flow_nat_ipv6_l4proto(skb, ip6h, thoff, &addr, &new_addr);
 121}
 122
 123static int nf_flow_nat_ipv6(const struct flow_offload *flow,
 124                            struct sk_buff *skb,
 125                            enum flow_offload_tuple_dir dir)
 126{
 127        struct ipv6hdr *ip6h = ipv6_hdr(skb);
 128        unsigned int thoff = sizeof(*ip6h);
 129
 130        if (flow->flags & FLOW_OFFLOAD_SNAT &&
 131            (nf_flow_snat_port(flow, skb, thoff, ip6h->nexthdr, dir) < 0 ||
 132             nf_flow_snat_ipv6(flow, skb, ip6h, thoff, dir) < 0))
 133                return -1;
 134        if (flow->flags & FLOW_OFFLOAD_DNAT &&
 135            (nf_flow_dnat_port(flow, skb, thoff, ip6h->nexthdr, dir) < 0 ||
 136             nf_flow_dnat_ipv6(flow, skb, ip6h, thoff, dir) < 0))
 137                return -1;
 138
 139        return 0;
 140}
 141
 142static int nf_flow_tuple_ipv6(struct sk_buff *skb, const struct net_device *dev,
 143                              struct flow_offload_tuple *tuple)
 144{
 145        struct flow_ports *ports;
 146        struct ipv6hdr *ip6h;
 147        unsigned int thoff;
 148
 149        if (!pskb_may_pull(skb, sizeof(*ip6h)))
 150                return -1;
 151
 152        ip6h = ipv6_hdr(skb);
 153
 154        if (ip6h->nexthdr != IPPROTO_TCP &&
 155            ip6h->nexthdr != IPPROTO_UDP)
 156                return -1;
 157
 158        thoff = sizeof(*ip6h);
 159        if (!pskb_may_pull(skb, thoff + sizeof(*ports)))
 160                return -1;
 161
 162        ports = (struct flow_ports *)(skb_network_header(skb) + thoff);
 163
 164        tuple->src_v6           = ip6h->saddr;
 165        tuple->dst_v6           = ip6h->daddr;
 166        tuple->src_port         = ports->source;
 167        tuple->dst_port         = ports->dest;
 168        tuple->l3proto          = AF_INET6;
 169        tuple->l4proto          = ip6h->nexthdr;
 170        tuple->iifidx           = dev->ifindex;
 171
 172        return 0;
 173}
 174
 175/* Based on ip_exceeds_mtu(). */
 176static bool __nf_flow_exceeds_mtu(const struct sk_buff *skb, unsigned int mtu)
 177{
 178        if (skb->len <= mtu)
 179                return false;
 180
 181        if (skb_is_gso(skb) && skb_gso_validate_network_len(skb, mtu))
 182                return false;
 183
 184        return true;
 185}
 186
 187static bool nf_flow_exceeds_mtu(struct sk_buff *skb, const struct rt6_info *rt)
 188{
 189        u32 mtu;
 190
 191        mtu = ip6_dst_mtu_forward(&rt->dst);
 192        if (__nf_flow_exceeds_mtu(skb, mtu))
 193                return true;
 194
 195        return false;
 196}
 197
 198unsigned int
 199nf_flow_offload_ipv6_hook(void *priv, struct sk_buff *skb,
 200                          const struct nf_hook_state *state)
 201{
 202        struct flow_offload_tuple_rhash *tuplehash;
 203        struct nf_flowtable *flow_table = priv;
 204        struct flow_offload_tuple tuple = {};
 205        enum flow_offload_tuple_dir dir;
 206        struct flow_offload *flow;
 207        struct net_device *outdev;
 208        struct in6_addr *nexthop;
 209        struct ipv6hdr *ip6h;
 210        struct rt6_info *rt;
 211
 212        if (skb->protocol != htons(ETH_P_IPV6))
 213                return NF_ACCEPT;
 214
 215        if (nf_flow_tuple_ipv6(skb, state->in, &tuple) < 0)
 216                return NF_ACCEPT;
 217
 218        tuplehash = flow_offload_lookup(flow_table, &tuple);
 219        if (tuplehash == NULL)
 220                return NF_ACCEPT;
 221
 222        outdev = dev_get_by_index_rcu(state->net, tuplehash->tuple.oifidx);
 223        if (!outdev)
 224                return NF_ACCEPT;
 225
 226        dir = tuplehash->tuple.dir;
 227        flow = container_of(tuplehash, struct flow_offload, tuplehash[dir]);
 228
 229        rt = (struct rt6_info *)flow->tuplehash[dir].tuple.dst_cache;
 230        if (unlikely(nf_flow_exceeds_mtu(skb, rt)))
 231                return NF_ACCEPT;
 232
 233        if (skb_try_make_writable(skb, sizeof(*ip6h)))
 234                return NF_DROP;
 235
 236        if (flow->flags & (FLOW_OFFLOAD_SNAT | FLOW_OFFLOAD_DNAT) &&
 237            nf_flow_nat_ipv6(flow, skb, dir) < 0)
 238                return NF_DROP;
 239
 240        flow->timeout = (u32)jiffies + NF_FLOW_TIMEOUT;
 241        ip6h = ipv6_hdr(skb);
 242        ip6h->hop_limit--;
 243
 244        skb->dev = outdev;
 245        nexthop = rt6_nexthop(rt, &flow->tuplehash[!dir].tuple.src_v6);
 246        neigh_xmit(NEIGH_ND_TABLE, outdev, nexthop, skb);
 247
 248        return NF_STOLEN;
 249}
 250EXPORT_SYMBOL_GPL(nf_flow_offload_ipv6_hook);
 251
 252static struct nf_flowtable_type flowtable_ipv6 = {
 253        .family         = NFPROTO_IPV6,
 254        .params         = &nf_flow_offload_rhash_params,
 255        .gc             = nf_flow_offload_work_gc,
 256        .free           = nf_flow_table_free,
 257        .hook           = nf_flow_offload_ipv6_hook,
 258        .owner          = THIS_MODULE,
 259};
 260
 261static int __init nf_flow_ipv6_module_init(void)
 262{
 263        nft_register_flowtable_type(&flowtable_ipv6);
 264
 265        return 0;
 266}
 267
 268static void __exit nf_flow_ipv6_module_exit(void)
 269{
 270        nft_unregister_flowtable_type(&flowtable_ipv6);
 271}
 272
 273module_init(nf_flow_ipv6_module_init);
 274module_exit(nf_flow_ipv6_module_exit);
 275
 276MODULE_LICENSE("GPL");
 277MODULE_AUTHOR("Pablo Neira Ayuso <pablo@netfilter.org>");
 278MODULE_ALIAS_NF_FLOWTABLE(AF_INET6);
 279