linux/net/ipv6/netfilter/ip6table_nat.c
<<
>>
Prefs
   1/*
   2 * Copyright (c) 2011 Patrick McHardy <kaber@trash.net>
   3 *
   4 * This program is free software; you can redistribute it and/or modify
   5 * it under the terms of the GNU General Public License version 2 as
   6 * published by the Free Software Foundation.
   7 *
   8 * Based on Rusty Russell's IPv4 NAT code. Development of IPv6 NAT
   9 * funded by Astaro.
  10 */
  11
  12#include <linux/module.h>
  13#include <linux/netfilter.h>
  14#include <linux/netfilter_ipv6.h>
  15#include <linux/netfilter_ipv6/ip6_tables.h>
  16#include <linux/ipv6.h>
  17#include <net/ipv6.h>
  18
  19#include <net/netfilter/nf_nat.h>
  20#include <net/netfilter/nf_nat_core.h>
  21#include <net/netfilter/nf_nat_l3proto.h>
  22
  23static const struct xt_table nf_nat_ipv6_table = {
  24        .name           = "nat",
  25        .valid_hooks    = (1 << NF_INET_PRE_ROUTING) |
  26                          (1 << NF_INET_POST_ROUTING) |
  27                          (1 << NF_INET_LOCAL_OUT) |
  28                          (1 << NF_INET_LOCAL_IN),
  29        .me             = THIS_MODULE,
  30        .af             = NFPROTO_IPV6,
  31};
  32
  33static unsigned int alloc_null_binding(struct nf_conn *ct, unsigned int hooknum)
  34{
  35        /* Force range to this IP; let proto decide mapping for
  36         * per-proto parts (hence not IP_NAT_RANGE_PROTO_SPECIFIED).
  37         */
  38        struct nf_nat_range range;
  39
  40        range.flags = 0;
  41        pr_debug("Allocating NULL binding for %p (%pI6)\n", ct,
  42                 HOOK2MANIP(hooknum) == NF_NAT_MANIP_SRC ?
  43                 &ct->tuplehash[IP_CT_DIR_REPLY].tuple.dst.u3.ip6 :
  44                 &ct->tuplehash[IP_CT_DIR_REPLY].tuple.src.u3.ip6);
  45
  46        return nf_nat_setup_info(ct, &range, HOOK2MANIP(hooknum));
  47}
  48
  49static unsigned int nf_nat_rule_find(struct sk_buff *skb, unsigned int hooknum,
  50                                     const struct net_device *in,
  51                                     const struct net_device *out,
  52                                     struct nf_conn *ct)
  53{
  54        struct net *net = nf_ct_net(ct);
  55        unsigned int ret;
  56
  57        ret = ip6t_do_table(skb, hooknum, in, out, net->ipv6.ip6table_nat);
  58        if (ret == NF_ACCEPT) {
  59                if (!nf_nat_initialized(ct, HOOK2MANIP(hooknum)))
  60                        ret = alloc_null_binding(ct, hooknum);
  61        }
  62        return ret;
  63}
  64
  65static unsigned int
  66nf_nat_ipv6_fn(unsigned int hooknum,
  67               struct sk_buff *skb,
  68               const struct net_device *in,
  69               const struct net_device *out,
  70               int (*okfn)(struct sk_buff *))
  71{
  72        struct nf_conn *ct;
  73        enum ip_conntrack_info ctinfo;
  74        struct nf_conn_nat *nat;
  75        enum nf_nat_manip_type maniptype = HOOK2MANIP(hooknum);
  76        __be16 frag_off;
  77        int hdrlen;
  78        u8 nexthdr;
  79
  80        ct = nf_ct_get(skb, &ctinfo);
  81        /* Can't track?  It's not due to stress, or conntrack would
  82         * have dropped it.  Hence it's the user's responsibilty to
  83         * packet filter it out, or implement conntrack/NAT for that
  84         * protocol. 8) --RR
  85         */
  86        if (!ct)
  87                return NF_ACCEPT;
  88
  89        /* Don't try to NAT if this packet is not conntracked */
  90        if (nf_ct_is_untracked(ct))
  91                return NF_ACCEPT;
  92
  93        nat = nfct_nat(ct);
  94        if (!nat) {
  95                /* NAT module was loaded late. */
  96                if (nf_ct_is_confirmed(ct))
  97                        return NF_ACCEPT;
  98                nat = nf_ct_ext_add(ct, NF_CT_EXT_NAT, GFP_ATOMIC);
  99                if (nat == NULL) {
 100                        pr_debug("failed to add NAT extension\n");
 101                        return NF_ACCEPT;
 102                }
 103        }
 104
 105        switch (ctinfo) {
 106        case IP_CT_RELATED:
 107        case IP_CT_RELATED_REPLY:
 108                nexthdr = ipv6_hdr(skb)->nexthdr;
 109                hdrlen = ipv6_skip_exthdr(skb, sizeof(struct ipv6hdr),
 110                                          &nexthdr, &frag_off);
 111
 112                if (hdrlen >= 0 && nexthdr == IPPROTO_ICMPV6) {
 113                        if (!nf_nat_icmpv6_reply_translation(skb, ct, ctinfo,
 114                                                             hooknum, hdrlen))
 115                                return NF_DROP;
 116                        else
 117                                return NF_ACCEPT;
 118                }
 119                /* Fall thru... (Only ICMPs can be IP_CT_IS_REPLY) */
 120        case IP_CT_NEW:
 121                /* Seen it before?  This can happen for loopback, retrans,
 122                 * or local packets.
 123                 */
 124                if (!nf_nat_initialized(ct, maniptype)) {
 125                        unsigned int ret;
 126
 127                        ret = nf_nat_rule_find(skb, hooknum, in, out, ct);
 128                        if (ret != NF_ACCEPT)
 129                                return ret;
 130                } else {
 131                        pr_debug("Already setup manip %s for ct %p\n",
 132                                 maniptype == NF_NAT_MANIP_SRC ? "SRC" : "DST",
 133                                 ct);
 134                        if (nf_nat_oif_changed(hooknum, ctinfo, nat, out))
 135                                goto oif_changed;
 136                }
 137                break;
 138
 139        default:
 140                /* ESTABLISHED */
 141                NF_CT_ASSERT(ctinfo == IP_CT_ESTABLISHED ||
 142                             ctinfo == IP_CT_ESTABLISHED_REPLY);
 143                if (nf_nat_oif_changed(hooknum, ctinfo, nat, out))
 144                        goto oif_changed;
 145        }
 146
 147        return nf_nat_packet(ct, ctinfo, hooknum, skb);
 148
 149oif_changed:
 150        nf_ct_kill_acct(ct, ctinfo, skb);
 151        return NF_DROP;
 152}
 153
 154static unsigned int
 155nf_nat_ipv6_in(unsigned int hooknum,
 156               struct sk_buff *skb,
 157               const struct net_device *in,
 158               const struct net_device *out,
 159               int (*okfn)(struct sk_buff *))
 160{
 161        unsigned int ret;
 162        struct in6_addr daddr = ipv6_hdr(skb)->daddr;
 163
 164        ret = nf_nat_ipv6_fn(hooknum, skb, in, out, okfn);
 165        if (ret != NF_DROP && ret != NF_STOLEN &&
 166            ipv6_addr_cmp(&daddr, &ipv6_hdr(skb)->daddr))
 167                skb_dst_drop(skb);
 168
 169        return ret;
 170}
 171
 172static unsigned int
 173nf_nat_ipv6_out(unsigned int hooknum,
 174                struct sk_buff *skb,
 175                const struct net_device *in,
 176                const struct net_device *out,
 177                int (*okfn)(struct sk_buff *))
 178{
 179#ifdef CONFIG_XFRM
 180        const struct nf_conn *ct;
 181        enum ip_conntrack_info ctinfo;
 182        int err;
 183#endif
 184        unsigned int ret;
 185
 186        /* root is playing with raw sockets. */
 187        if (skb->len < sizeof(struct ipv6hdr))
 188                return NF_ACCEPT;
 189
 190        ret = nf_nat_ipv6_fn(hooknum, skb, in, out, okfn);
 191#ifdef CONFIG_XFRM
 192        if (ret != NF_DROP && ret != NF_STOLEN &&
 193            !(IP6CB(skb)->flags & IP6SKB_XFRM_TRANSFORMED) &&
 194            (ct = nf_ct_get(skb, &ctinfo)) != NULL) {
 195                enum ip_conntrack_dir dir = CTINFO2DIR(ctinfo);
 196
 197                if (!nf_inet_addr_cmp(&ct->tuplehash[dir].tuple.src.u3,
 198                                      &ct->tuplehash[!dir].tuple.dst.u3) ||
 199                    (ct->tuplehash[dir].tuple.dst.protonum != IPPROTO_ICMPV6 &&
 200                     ct->tuplehash[dir].tuple.src.u.all !=
 201                     ct->tuplehash[!dir].tuple.dst.u.all)) {
 202                        err = nf_xfrm_me_harder(skb, AF_INET6);
 203                        if (err < 0)
 204                                ret = NF_DROP_ERR(err);
 205                }
 206        }
 207#endif
 208        return ret;
 209}
 210
 211static unsigned int
 212nf_nat_ipv6_local_fn(unsigned int hooknum,
 213                     struct sk_buff *skb,
 214                     const struct net_device *in,
 215                     const struct net_device *out,
 216                     int (*okfn)(struct sk_buff *))
 217{
 218        const struct nf_conn *ct;
 219        enum ip_conntrack_info ctinfo;
 220        unsigned int ret;
 221        int err;
 222
 223        /* root is playing with raw sockets. */
 224        if (skb->len < sizeof(struct ipv6hdr))
 225                return NF_ACCEPT;
 226
 227        ret = nf_nat_ipv6_fn(hooknum, skb, in, out, okfn);
 228        if (ret != NF_DROP && ret != NF_STOLEN &&
 229            (ct = nf_ct_get(skb, &ctinfo)) != NULL) {
 230                enum ip_conntrack_dir dir = CTINFO2DIR(ctinfo);
 231
 232                if (!nf_inet_addr_cmp(&ct->tuplehash[dir].tuple.dst.u3,
 233                                      &ct->tuplehash[!dir].tuple.src.u3)) {
 234                        err = ip6_route_me_harder(skb);
 235                        if (err < 0)
 236                                ret = NF_DROP_ERR(err);
 237                }
 238#ifdef CONFIG_XFRM
 239                else if (!(IP6CB(skb)->flags & IP6SKB_XFRM_TRANSFORMED) &&
 240                         ct->tuplehash[dir].tuple.dst.protonum != IPPROTO_ICMPV6 &&
 241                         ct->tuplehash[dir].tuple.dst.u.all !=
 242                         ct->tuplehash[!dir].tuple.src.u.all) {
 243                        err = nf_xfrm_me_harder(skb, AF_INET6);
 244                        if (err < 0)
 245                                ret = NF_DROP_ERR(err);
 246                }
 247#endif
 248        }
 249        return ret;
 250}
 251
 252static struct nf_hook_ops nf_nat_ipv6_ops[] __read_mostly = {
 253        /* Before packet filtering, change destination */
 254        {
 255                .hook           = nf_nat_ipv6_in,
 256                .owner          = THIS_MODULE,
 257                .pf             = NFPROTO_IPV6,
 258                .hooknum        = NF_INET_PRE_ROUTING,
 259                .priority       = NF_IP6_PRI_NAT_DST,
 260        },
 261        /* After packet filtering, change source */
 262        {
 263                .hook           = nf_nat_ipv6_out,
 264                .owner          = THIS_MODULE,
 265                .pf             = NFPROTO_IPV6,
 266                .hooknum        = NF_INET_POST_ROUTING,
 267                .priority       = NF_IP6_PRI_NAT_SRC,
 268        },
 269        /* Before packet filtering, change destination */
 270        {
 271                .hook           = nf_nat_ipv6_local_fn,
 272                .owner          = THIS_MODULE,
 273                .pf             = NFPROTO_IPV6,
 274                .hooknum        = NF_INET_LOCAL_OUT,
 275                .priority       = NF_IP6_PRI_NAT_DST,
 276        },
 277        /* After packet filtering, change source */
 278        {
 279                .hook           = nf_nat_ipv6_fn,
 280                .owner          = THIS_MODULE,
 281                .pf             = NFPROTO_IPV6,
 282                .hooknum        = NF_INET_LOCAL_IN,
 283                .priority       = NF_IP6_PRI_NAT_SRC,
 284        },
 285};
 286
 287static int __net_init ip6table_nat_net_init(struct net *net)
 288{
 289        struct ip6t_replace *repl;
 290
 291        repl = ip6t_alloc_initial_table(&nf_nat_ipv6_table);
 292        if (repl == NULL)
 293                return -ENOMEM;
 294        net->ipv6.ip6table_nat = ip6t_register_table(net, &nf_nat_ipv6_table, repl);
 295        kfree(repl);
 296        return PTR_RET(net->ipv6.ip6table_nat);
 297}
 298
 299static void __net_exit ip6table_nat_net_exit(struct net *net)
 300{
 301        ip6t_unregister_table(net, net->ipv6.ip6table_nat);
 302}
 303
 304static struct pernet_operations ip6table_nat_net_ops = {
 305        .init   = ip6table_nat_net_init,
 306        .exit   = ip6table_nat_net_exit,
 307};
 308
 309static int __init ip6table_nat_init(void)
 310{
 311        int err;
 312
 313        err = register_pernet_subsys(&ip6table_nat_net_ops);
 314        if (err < 0)
 315                goto err1;
 316
 317        err = nf_register_hooks(nf_nat_ipv6_ops, ARRAY_SIZE(nf_nat_ipv6_ops));
 318        if (err < 0)
 319                goto err2;
 320        return 0;
 321
 322err2:
 323        unregister_pernet_subsys(&ip6table_nat_net_ops);
 324err1:
 325        return err;
 326}
 327
 328static void __exit ip6table_nat_exit(void)
 329{
 330        nf_unregister_hooks(nf_nat_ipv6_ops, ARRAY_SIZE(nf_nat_ipv6_ops));
 331        unregister_pernet_subsys(&ip6table_nat_net_ops);
 332}
 333
 334module_init(ip6table_nat_init);
 335module_exit(ip6table_nat_exit);
 336
 337MODULE_LICENSE("GPL");
 338