xt_GTPUAH.c 18.1 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
/*
 * GTPu klm for Linux/iptables
 *
 * Copyright (c) 2010-2011 Polaris Networks
 * Author: Pradip Biswas <pradip_biswas@polarisnetworks.net>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 *
 */

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/skbuff.h>
#include <linux/ip.h>
18
#include <linux/if_ether.h>
19
#include <linux/route.h>
20
#include <linux/time.h>
21
#include <net/checksum.h>
22
#include <net/ip.h>
23 24
#include <net/udp.h>
#include <net/inet_sock.h>
25
#include <net/route.h>
26 27 28 29
#include <net/addrconf.h>
#include <net/ip6_checksum.h>
#include <net/ip6_route.h>
#include <net/ipv6.h>
30 31
#include <linux/netfilter/x_tables.h>
#include <linux/netfilter_ipv4/ip_tables.h>
32 33 34 35 36
#ifdef CONFIG_BRIDGE_NETFILTER
#    include <linux/netfilter_bridge.h>
#endif
#if defined(CONFIG_IP6_NF_IPTABLES) || defined(CONFIG_IP6_NF_IPTABLES_MODULE)
#    define WITH_IPV6 1
37 38 39
#endif
#include "xt_GTPUAH.h"
#if !(defined KVERSION)
40
#    error "Kernel version is not defined!!!! Exiting."
41
#endif
42

gauthier's avatar
logs  
gauthier committed
43
//#define TRACE_IN_KERN_LOG 1
44 45 46 47 48 49

#if defined(TRACE_IN_KERN_LOG)
#define PR_INFO(fORMAT, aRGS...) pr_info(fORMAT, ##aRGS)
#else
#define PR_INFO(fORMAT, aRGS...)
#endif
50
#define INT_MODULE_PARM(n, v) static int n = v; module_param(n, int, 0444)
51
//-----------------------------------------------------------------------------
52 53 54
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Pradip Biswas <pradip_biswas@polarisnetworks.net>");
MODULE_DESCRIPTION("GTPu Data Path extension on netfilter");
55 56
//-----------------------------------------------------------------------------
static char*        _gtpuah_nf_inet_hook_2_string(int nf_inet_hookP);
57
static void         _gtpuah_print_hex_octets(unsigned char* data_pP, unsigned short sizeP);
58
static void         _gtpuah_tg4_add(struct sk_buff *old_skb_pP, const struct xt_action_param *par_pP);
59 60
#ifdef WITH_IPV6
static void         _gtpuah_tg6_add(struct sk_buff *old_skb_pP, const struct xt_action_param *par_pP);
61
static unsigned int gtpuah_tg6(struct sk_buff *skb_pP, const struct xt_action_param *par_pP);
62 63
#endif
static unsigned int gtpuah_tg4(struct sk_buff *skb_pP, const struct xt_action_param *par_pP);
64 65 66 67
static int          __init gtpuah_tg_init(void);
static void         __exit gtpuah_tg_exit(void);
//-----------------------------------------------------------------------------
static struct xt_target gtpuah_tg_reg[] __read_mostly = {
68 69 70 71 72 73 74 75 76 77 78
  {
    .name           = "GTPUAH",
    .revision       = 0,
    .family         = NFPROTO_IPV4,
    .hooks          = (1 << NF_INET_POST_ROUTING) |
    (1 << NF_INET_LOCAL_IN),
    .table          = "mangle",
    .target         = gtpuah_tg4,
    .targetsize     = sizeof(struct xt_gtpuah_target_info),
    .me             = THIS_MODULE,
  },
79
#ifdef WITH_IPV6
80 81 82 83 84 85 86 87 88 89 90
  {
    .name           = "GTPUAH",
    .revision       = 0,
    .family         = NFPROTO_IPV6,
    .hooks          = (1 << NF_INET_POST_ROUTING) |
    (1 << NF_INET_LOCAL_IN),
    .table          = "mangle",
    .target         = gtpuah_tg6,
    .targetsize     = sizeof(struct xt_gtpuah_target_info),
    .me             = THIS_MODULE,
  },
91 92
#endif
};
93

94
struct gtpuhdr {
95 96 97 98
  char      flags;
  char      msgtype;
  u_int16_t length;
  u_int32_t tunid;
99
};
100

101
//-----------------------------------------------------------------------------
102 103 104 105 106 107 108
#define GTPU_HDR_PNBIT 1
#define GTPU_HDR_SBIT 1 << 1
#define GTPU_HDR_EBIT 1 << 2
#define GTPU_ANY_EXT_HDR_BIT (GTPU_HDR_PNBIT | GTPU_HDR_SBIT | GTPU_HDR_EBIT)

#define GTPU_FAILURE 1
#define GTPU_SUCCESS !GTPU_FAILURE
109
#define GTPUAH_2_PRINT_BUFFER_LEN 8192
110

111
#define IP_MORE_FRAGMENTS 0x2000
112 113 114 115 116 117
#define NIPADDR(addr) \
        (uint8_t)(addr & 0x000000FF), \
        (uint8_t)((addr & 0x0000FF00) >> 8), \
        (uint8_t)((addr & 0x00FF0000) >> 16), \
        (uint8_t)((addr & 0xFF000000) >> 24)
//-----------------------------------------------------------------------------
118 119 120 121 122 123 124 125
static char _gtpuah_print_buffer[GTPUAH_2_PRINT_BUFFER_LEN];
INT_MODULE_PARM(tunnel_local, 0);
MODULE_PARM_DESC(tunnel_local, "Act as a boolean, tels if the S1U tunnel(s) are both start/end local");
INT_MODULE_PARM(gtpu_port, 2152);
MODULE_PARM_DESC(gtpu_port, "UDP port number for S1U interface (eNB and S-GW sides)");
INT_MODULE_PARM(mtu, 1564);
MODULE_PARM_DESC(mtu, "MTU of the S1U IP interface");
//-----------------------------------------------------------------------------
126
static char*
127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153
_gtpuah_nf_inet_hook_2_string(int nf_inet_hookP)
{
  //-----------------------------------------------------------------------------
  switch (nf_inet_hookP) {
  case NF_INET_PRE_ROUTING:
    return "NF_INET_PRE_ROUTING";
    break;

  case NF_INET_LOCAL_IN:
    return "NF_INET_LOCAL_IN";
    break;

  case NF_INET_FORWARD:
    return "NF_INET_FORWARD";
    break;

  case NF_INET_LOCAL_OUT:
    return "NF_INET_LOCAL_OUT";
    break;

  case NF_INET_POST_ROUTING:
    return "NF_INET_POST_ROUTING";
    break;

  default:
    return "NF_INET_UNKNOWN";
  }
154
}
155 156
//-----------------------------------------------------------------------------
void
157 158 159
_gtpuah_print_hex_octets(unsigned char* data_pP, unsigned short sizeP)
{
  //-----------------------------------------------------------------------------
160 161 162 163 164 165 166 167 168 169 170

  unsigned long octet_index = 0;
  unsigned long buffer_marker = 0;
  unsigned char aindex;
  struct timeval tv;
  char timeofday[64];
  unsigned int h,m,s;

  if (data_pP == NULL) {
    return;
  }
171

172
  if (sizeP > 2000) {
173
    return;
174 175 176 177 178 179 180 181 182 183 184
  }

  do_gettimeofday(&tv);
  h = (tv.tv_sec/3600) % 24;
  m = (tv.tv_sec / 60) % 60;
  s = tv.tv_sec % 60;
  snprintf(timeofday, 64, "%02d:%02d:%02d.%06ld", h,m,s,tv.tv_usec);

  buffer_marker+=snprintf(&_gtpuah_print_buffer[buffer_marker], GTPUAH_2_PRINT_BUFFER_LEN - buffer_marker,"%s------+-------------------------------------------------+\n",timeofday);
  buffer_marker+=snprintf(&_gtpuah_print_buffer[buffer_marker], GTPUAH_2_PRINT_BUFFER_LEN - buffer_marker,"%s      |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |\n",timeofday);
  buffer_marker+=snprintf(&_gtpuah_print_buffer[buffer_marker], GTPUAH_2_PRINT_BUFFER_LEN - buffer_marker,"%s------+-------------------------------------------------+\n",timeofday);
185 186 187
  pr_info("%s",_gtpuah_print_buffer);
  buffer_marker = 0;

188
  for (octet_index = 0; octet_index < sizeP; octet_index++) {
189
    if ((octet_index % 16) == 0) {
190
      if (octet_index != 0) {
191 192 193
        buffer_marker+=snprintf(&_gtpuah_print_buffer[buffer_marker], GTPUAH_2_PRINT_BUFFER_LEN - buffer_marker, " |\n");
        pr_info("%s",_gtpuah_print_buffer);
        buffer_marker = 0;
194
      }
195

196 197
      buffer_marker+=snprintf(&_gtpuah_print_buffer[buffer_marker], GTPUAH_2_PRINT_BUFFER_LEN - buffer_marker, "%s %04ld |",timeofday, octet_index);
    }
198

199 200 201 202 203 204 205 206 207 208 209 210 211 212
    /*
     * Print every single octet in hexadecimal form
     */
    buffer_marker+=snprintf(&_gtpuah_print_buffer[buffer_marker], GTPUAH_2_PRINT_BUFFER_LEN - buffer_marker, " %02x", data_pP[octet_index]);
    /*
     * Align newline and pipes according to the octets in groups of 2
     */
  }

  /*
   * Append enough spaces and put final pipe
   */
  for (aindex = octet_index; aindex < 16; ++aindex)
    buffer_marker+=snprintf(&_gtpuah_print_buffer[buffer_marker], GTPUAH_2_PRINT_BUFFER_LEN - buffer_marker, "   ");
213 214

  //SGI_IF_DEBUG("   ");
215 216 217 218
  buffer_marker+=snprintf(&_gtpuah_print_buffer[buffer_marker], GTPUAH_2_PRINT_BUFFER_LEN - buffer_marker, " |\n");
  pr_info("%s",_gtpuah_print_buffer);
}

219 220 221
#ifdef WITH_IPV6
//-----------------------------------------------------------------------------
static void
222 223 224
_gtpuah_tg6_add(struct sk_buff *old_skb_pP, const struct xt_action_param *par_pP)
{
  //-----------------------------------------------------------------------------
225 226 227
}
#endif

228 229
//-----------------------------------------------------------------------------
static void
230 231 232 233 234 235 236 237 238 239 240
_gtpuah_tg4_add(struct sk_buff *old_skb_pP, const struct xt_action_param *par_pP)
{
  //-----------------------------------------------------------------------------
  struct rtable  *rt              = NULL;
  struct ethhdr  *ethhdr_p        = NULL;
  struct iphdr   *old_iph_p       = ip_hdr(old_skb_pP);
  struct iphdr   *new_iph_p       = NULL;
  struct iphdr   *tmp_iph_p       = NULL;
  struct udphdr  *udph_p          = NULL;
  struct gtpuhdr *gtpuh_p         = NULL;
  struct sk_buff *new_skb_p       = NULL;
241
  uint16_t  headroom_reqd         = LL_MAX_HEADER + sizeof(struct iphdr) + sizeof(struct udphdr) + sizeof(struct gtpuhdr);
242 243 244 245 246 247 248 249 250 251 252
  uint16_t        orig_iplen = 0, udp_len = 0, ip_len = 0;
  int             flags = 0, offset = 0;
  unsigned int    addr_type       = RTN_UNSPEC;


  if (ip_is_fragment(ip_hdr(old_skb_pP))) {
    pr_info("GTPUAH: IP fragment, dropped\n");
    return;
  }

  if (skb_linearize(old_skb_pP) < 0) {
253
	PR_INFO("GTPUAH: skb no linearize\n");
254 255 256 257
    return;
  }

  if (old_skb_pP->mark == 0) {
258
	PR_INFO("GTPUAH: _gtpuah_target_add force info_pP mark %u to skb_pP mark %u\n",
259 260 261 262 263 264 265 266 267 268 269 270
            old_skb_pP->mark,
            ((const struct xt_gtpuah_target_info *)(par_pP->targinfo))->rtun);
    old_skb_pP->mark = ((const struct xt_gtpuah_target_info *)(par_pP->targinfo))->rtun;
  }

  /* Keep the length of the source IP packet */
  orig_iplen = ntohs(old_iph_p->tot_len);
  offset = ntohs(old_iph_p->frag_off);
  flags  = offset & ~IP_OFFSET;


  /* Create a new copy of the original skb...can't avoid :-( */
gauthier's avatar
gauthier committed
271
  if ((orig_iplen + sizeof(struct iphdr) + sizeof(struct udphdr) + sizeof(struct gtpuhdr)) <= mtu) {
272 273 274 275

    new_skb_p = alloc_skb(headroom_reqd + orig_iplen, GFP_ATOMIC);

    if (new_skb_p == NULL) {
276
      PR_INFO("GTPUAH: alloc_skb returned NULL\n");
277
      return;
278
    }
279 280

    if (skb_linearize(new_skb_p) < 0) {
281
      PR_INFO("GTPUAH: skb no linearize\n");
282
      goto free_new_skb;
283 284
    }

285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307
    skb_reserve(new_skb_p, headroom_reqd + orig_iplen);
    tmp_iph_p = (void *)skb_push(new_skb_p, orig_iplen);
    memcpy(tmp_iph_p, old_iph_p, orig_iplen);

    /* Add GTPu header */
    gtpuh_p          = (struct gtpuhdr*)skb_push(new_skb_p, sizeof(struct gtpuhdr));
    gtpuh_p->flags   = 0x30; /* v1 and Protocol-type=GTP */
    gtpuh_p->msgtype = 0xff; /* T-PDU */
    gtpuh_p->length  = htons(orig_iplen);
    gtpuh_p->tunid   = htonl(old_skb_pP->mark);

    /* Add UDP header */
    udp_len        = sizeof(struct udphdr) + sizeof(struct gtpuhdr) + orig_iplen;
    udph_p         = (struct udphdr*)skb_push(new_skb_p, sizeof(struct udphdr));
    udph_p->source = htons(gtpu_port);
    udph_p->dest   = htons(gtpu_port);
    udph_p->len    = htons(udp_len);
    udph_p->check  = 0;
    udph_p->check  = csum_tcpudp_magic(((const struct xt_gtpuah_target_info *)(par_pP->targinfo))->laddr,
                                       ((const struct xt_gtpuah_target_info *)(par_pP->targinfo))->raddr,
                                       udp_len,
                                       IPPROTO_UDP,
                                       csum_partial((char*)udph_p, udp_len, 0));
308
    skb_reset_transport_header(new_skb_p);
309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326

    /* Add IP header */
    ip_len = sizeof(struct iphdr) + sizeof(struct udphdr) + sizeof(struct gtpuhdr) + orig_iplen;
    new_iph_p = (struct iphdr*)skb_push(new_skb_p, sizeof(struct iphdr));
    new_iph_p->ihl      = 5;
    new_iph_p->version  = 4;
    new_iph_p->tos      = 0;
    new_iph_p->tot_len  = htons(ip_len);
    new_iph_p->id       = (uint16_t)(((uint64_t)new_skb_p) >> 8);
    new_iph_p->frag_off = 0;
    new_iph_p->ttl      = 64;
    new_iph_p->protocol = IPPROTO_UDP;
    new_iph_p->saddr    = ((const struct xt_gtpuah_target_info *)(par_pP->targinfo))->laddr;
    // !!!!!!!! LG TEST !!!!!!!!!!
    //new_iph_p->saddr    = old_iph_p->saddr;
    new_iph_p->daddr    = ((const struct xt_gtpuah_target_info *)(par_pP->targinfo))->raddr;
    new_iph_p->check    = 0;
    new_iph_p->check    = ip_fast_csum((unsigned char *)new_iph_p, new_iph_p->ihl);
327
    skb_reset_network_header(new_skb_p);
328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354

    // CHECKSUM_NONE, CHECKSUM_UNNECESSARY, CHECKSUM_COMPLETE, CHECKSUM_PARTIAL
    new_skb_p->ip_summed = CHECKSUM_NONE;
    new_skb_p->mark      = old_skb_pP->mark;

    switch (par_pP->hooknum) {
    case NF_INET_POST_ROUTING: {
      new_skb_p->pkt_type = PACKET_OTHERHOST; // PACKET_OUTGOING
#ifdef CONFIG_BRIDGE_NETFILTER

      if (new_skb_p->nf_bridge != NULL && new_skb_p->nf_bridge->mask & BRNF_BRIDGED) {
        addr_type = RTN_LOCAL;
        new_skb_p->pkt_type =PACKET_HOST;
      }

#endif

      if (tunnel_local == 0) {
        struct flowi   fl    = {
          .u = {
            .ip4 = {
              .daddr        = new_iph_p->daddr,
              .flowi4_tos   = RT_TOS(new_iph_p->tos),
              .flowi4_scope = RT_SCOPE_UNIVERSE,
            }
          }
        };
355 356 357 358
        /*pr_info("GTPUAH: PACKET -> NF_HOOK NF_INET_POST_ROUTING/%s encapsulated src: %u.%u.%u.%u dst: %u.%u.%u.%u\n",
                gtpuah_tg_reg[0].table,
                NIPADDR(old_iph_p->saddr),
                NIPADDR(old_iph_p->daddr));*/
359

360

361 362 363
        rt = ip_route_output_key(&init_net, &fl.u.ip4);

        if (rt == NULL) {
364
          PR_INFO("GTPURH: Failed to route packet to dst 0x%x.\n", fl.u.ip4.daddr);
365
          goto free_new_skb;
366
        }
367 368 369 370 371

        new_skb_p->priority = rt_tos2priority(new_iph_p->tos);
        skb_dst_drop(new_skb_p);

        if (rt->dst.dev) {
372
          PR_INFO("GTPUAH: dst dev name %s\n", rt->dst.dev->name);
373 374 375 376
          skb_dst_set(new_skb_p, dst_clone(&rt->dst));
          new_skb_p->dev      = skb_dst(new_skb_p)->dev;

          if (new_skb_p->len > dst_mtu(skb_dst(new_skb_p))) {
377
            goto free_new_skb;
378 379
          }

380
          //LG TESTnf_ct_attach(new_skb_p, old_skb_pP);
381

382
          PR_INFO("GTPUAH: PACKET -> NF_HOOK NF_INET_POST_ROUTING/%s mark %u encap src: %u.%u.%u.%u dst: %u.%u.%u.%u in src: %u.%u.%u.%u dst: %u.%u.%u.%u\n",
383 384 385 386 387
                  gtpuah_tg_reg[0].table,
                  new_skb_p->mark,
                  NIPADDR(old_iph_p->saddr),
                  NIPADDR(old_iph_p->daddr),
                  NIPADDR(new_iph_p->saddr),
gauthier's avatar
gauthier committed
388 389
                  NIPADDR(new_iph_p->daddr),
                  new_skb_p->len);
390

391
#if defined(TRACE_IN_KERN_LOG)
392 393 394
          _gtpuah_print_hex_octets(
            ip_hdr(new_skb_p),
            headroom_reqd);
395
#endif
396 397 398 399

          ip_local_out(new_skb_p);
          return;
        } else {
400
          PR_INFO("GTPURH: rt->dst.dev == NULL\n");
401
          goto free_new_skb;
402
        }
403
      } else { // (tunnel_local)
404

405 406 407 408 409 410 411 412 413
        new_skb_p->pkt_type = PACKET_HOST;
        new_skb_p->priority = rt_tos2priority(new_iph_p->tos);
        new_skb_p->protocol = htons(ETH_P_IP);

        // fake mac header
        ethhdr_p = (struct ethhdr*)skb_push(new_skb_p, ETH_HLEN);
        skb_set_mac_header(new_skb_p, 0);
        memset(ethhdr_p, 0, ETH_HLEN);
        ethhdr_p->h_proto = ntohs(ETH_P_IP);
414

415 416 417 418 419 420 421 422 423 424 425 426
        //_gtpuah_print_hex_octets(new_iph_p, ip_len);
        //ip_local_deliver_fn_ptr(new_skb_p);

        new_skb_p->ip_summed = CHECKSUM_NONE;
        skb_dst_drop(new_skb_p);
        nf_reset(new_skb_p);

        /*pr_info("GTPUAH(tun): PACKET -> NF_HOOK NF_INET_POST_ROUTING/%s encapsulated src: %u.%u.%u.%u dst: %u.%u.%u.%u\n",
                gtpuah_tg_reg[0].table,
                NIPADDR(old_iph_p->saddr),
                NIPADDR(old_iph_p->daddr));*/
        if ( dev_forward_skb(old_skb_pP->dev, new_skb_p) != NET_RX_SUCCESS) {
427
          PR_INFO("GTPUAH(tun): dev_forward_skb failed!!!\n");
428
        }
429

430
        return;
431
      }
432
    }
433 434 435
    break;

    default:
436
      PR_INFO("GTPUAH: NF_HOOK %u not processed\n", par_pP->hooknum);
437 438 439 440 441
      goto free_new_skb;
    }

    return;
  } else {
442
	PR_INFO("GTPUAH: PACKET DROPPED because of mtu %u < (%u + %u)\n",
443 444 445
            mtu, orig_iplen, headroom_reqd);
  }

446
free_new_skb:
447 448 449
  pr_info("GTPUAH: PACKET DROPPED\n");
  kfree_skb(new_skb_p);
  return ;
450 451
}

452 453 454
#ifdef WITH_IPV6
//-----------------------------------------------------------------------------
static unsigned int
455 456 457
gtpuah_tg6(struct sk_buff *skb_pP, const struct xt_action_param *par_pP)
{
  //-----------------------------------------------------------------------------
458

459
  const struct xt_gtpuah_target_info *tgi_p = par_pP->targinfo;
460

461
  if (tgi_p == NULL) {
462
    return NF_ACCEPT;
463 464 465 466 467 468 469 470
  }

  if (tgi_p->action == PARAM_GTPUAH_ACTION_ADD) {
    _gtpuah_tg6_add(skb_pP, par_pP);
    return NF_DROP; // TODO
  }

  return NF_ACCEPT;
471 472
}
#endif
473

474
//-----------------------------------------------------------------------------
475
static unsigned int
476 477 478 479 480
gtpuah_tg4(struct sk_buff *skb_pP, const struct xt_action_param *par_pP)
{
  //-----------------------------------------------------------------------------
  const struct iphdr                 *iph_p = ip_hdr(skb_pP);
  const struct xt_gtpuah_target_info *tgi_p = par_pP->targinfo;
481

482
  if (tgi_p == NULL) {
483
    return NF_ACCEPT;
484 485 486 487 488 489 490 491
  }

  if (tgi_p->action == PARAM_GTPUAH_ACTION_ADD) {
    _gtpuah_tg4_add(skb_pP, par_pP);
    return NF_DROP;
  }

  return NF_ACCEPT;
492 493
}

494 495
//-----------------------------------------------------------------------------
static int
496 497 498 499 500 501 502 503 504
__init gtpuah_tg_init(void)
{
  //-----------------------------------------------------------------------------
  pr_info("GTPUAH: Initializing module (KVersion: %d)\n", KVERSION);
  pr_info("GTPUAH: Copyright Polaris Networks 2010-2011\n");
  pr_info("GTPUAH: Modified by EURECOM Lionel GAUTHIER 2014\n");
#ifndef CMAKER
  pr_info("GTPUAH: Compiled %s at time %s\n",__DATE__,__TIME__);
#endif
505
#if defined(WITH_IPV6)
506
  pr_info("GTPURH: IPv4/IPv6 enabled\n");
507
#else
508
  pr_info("GTPURH: IPv4 only enabled\n");
509
#endif
510
  return xt_register_targets(gtpuah_tg_reg, ARRAY_SIZE(gtpuah_tg_reg));
511 512
}

513 514
//-----------------------------------------------------------------------------
static void
515 516 517 518 519
__exit gtpuah_tg_exit(void)
{
  //-----------------------------------------------------------------------------
  xt_unregister_targets(gtpuah_tg_reg, ARRAY_SIZE(gtpuah_tg_reg));
  pr_info("GTPUAH: Unloading module\n");
520 521
}

522 523 524 525 526

module_init(gtpuah_tg_init);
module_exit(gtpuah_tg_exit);
MODULE_ALIAS("ipt6_GTPUAH");
MODULE_ALIAS("ipt_GTPUAH");
527