[ovs-dev,v1] Basic GTP-U tunnel implementation in ovs
diff mbox

Message ID 1454669263-12898-1-git-send-email-niti.rohilla@tcs.com
State Awaiting Upstream
Headers show

Commit Message

niti1489@gmail.com Feb. 5, 2016, 10:47 a.m. UTC
GPRS Tunneling Protocol (GTP) is a group of IP-based communications protocols
used to carry general packet radio service (GPRS) within GSM, UMTS
and LTE networks. GTP-U is used for carrying user data within the GPRS core
network and between the radio access network and the core network.
In case of GTP, the tunneling code attaches a header with harcoded source and
destination MAC address 06:00:00:00:00:00, where the locally administered bit
is set to 1. This patch adds the implementation of mandatory part of GTP-U
protocol and supports only G-PDU messages. In G-PDU message, GTP-U header
is followed by a T-PDU.

Signed-off-by:Niti Rohilla <niti.rohilla@tcs.com>
Co-authored-by:Saloni Jain <saloni.jain@tcs.com>
---
 datapath/Modules.mk                               |   2 +
 datapath/linux/Modules.mk                         |   2 +
 datapath/linux/compat/gtp.c                       | 714 ++++++++++++++++++++++
 datapath/linux/compat/include/linux/if_link.h     |   7 +
 datapath/linux/compat/include/linux/openvswitch.h |   1 +
 datapath/linux/compat/include/net/gtp.h           |  24 +
 datapath/vport-gtp.c                              | 150 +++++
 datapath/vport.c                                  |   7 +
 lib/dpif-netlink.c                                |   5 +
 lib/netdev-vport.c                                |  12 +-
 ofproto/ofproto-dpif-ipfix.c                      |   6 +
 ofproto/ofproto-dpif-sflow.c                      |   6 +-
 tests/ovs-vsctl.at                                |   6 +-
 tests/system-kmod-macros.at                       |   2 +-
 tests/tunnel.at                                   |  12 +
 vswitchd/vswitch.xml                              |  16 +
 16 files changed, 968 insertions(+), 4 deletions(-)
 create mode 100644 datapath/linux/compat/gtp.c
 create mode 100644 datapath/linux/compat/include/net/gtp.h
 create mode 100644 datapath/vport-gtp.c

Comments

Ben Pfaff Feb. 5, 2016, 6:50 p.m. UTC | #1
On Fri, Feb 05, 2016 at 04:17:43PM +0530, Niti Rohilla wrote:
> GPRS Tunneling Protocol (GTP) is a group of IP-based communications protocols
> used to carry general packet radio service (GPRS) within GSM, UMTS
> and LTE networks. GTP-U is used for carrying user data within the GPRS core
> network and between the radio access network and the core network.
> In case of GTP, the tunneling code attaches a header with harcoded source and
> destination MAC address 06:00:00:00:00:00, where the locally administered bit
> is set to 1. This patch adds the implementation of mandatory part of GTP-U
> protocol and supports only G-PDU messages. In G-PDU message, GTP-U header
> is followed by a T-PDU.
> 
> Signed-off-by:Niti Rohilla <niti.rohilla@tcs.com>
> Co-authored-by:Saloni Jain <saloni.jain@tcs.com>

It doesn't look like upstream Linux has a GTP implementation.  Because
our usual workflow is to get code upstream first, you should start by
submitting the kernel patches against net-next when that tree is open (I
don't follow netdev, so I have no idea when that is).  Then, once the
GTP code is upstream, we can get it into OVS here.
Mark D. Gray Feb. 9, 2016, 3:15 p.m. UTC | #2
> From: dev [mailto:dev-bounces@openvswitch.org] On Behalf Of Niti Rohilla

> Sent: Friday, February 5, 2016 10:48 AM

> To: dev@openvswitch.org

> Cc: deepankar.gupta@tcs.com; partha.datta@tcs.com

> Subject: [ovs-dev] [PATCH v1] Basic GTP-U tunnel implementation in ovs

> 

> GPRS Tunneling Protocol (GTP) is a group of IP-based communications

> protocols used to carry general packet radio service (GPRS) within GSM,

> UMTS and LTE networks. GTP-U is used for carrying user data within the

> GPRS core network and between the radio access network and the core

> network.

> In case of GTP, the tunneling code attaches a header with harcoded source

> and destination MAC address 06:00:00:00:00:00, where the locally

> administered bit is set to 1. This patch adds the implementation of

> mandatory part of GTP-U protocol and supports only G-PDU messages. In G-

> PDU message, GTP-U header is followed by a T-PDU.

> 

Any plans for a userspace implementation?
Joe Stringer Feb. 11, 2016, 2:32 p.m. UTC | #3
On 5 February 2016 at 10:50, Ben Pfaff <blp@ovn.org> wrote:
> On Fri, Feb 05, 2016 at 04:17:43PM +0530, Niti Rohilla wrote:
>> GPRS Tunneling Protocol (GTP) is a group of IP-based communications protocols
>> used to carry general packet radio service (GPRS) within GSM, UMTS
>> and LTE networks. GTP-U is used for carrying user data within the GPRS core
>> network and between the radio access network and the core network.
>> In case of GTP, the tunneling code attaches a header with harcoded source and
>> destination MAC address 06:00:00:00:00:00, where the locally administered bit
>> is set to 1. This patch adds the implementation of mandatory part of GTP-U
>> protocol and supports only G-PDU messages. In G-PDU message, GTP-U header
>> is followed by a T-PDU.
>>
>> Signed-off-by:Niti Rohilla <niti.rohilla@tcs.com>
>> Co-authored-by:Saloni Jain <saloni.jain@tcs.com>
>
> It doesn't look like upstream Linux has a GTP implementation.  Because
> our usual workflow is to get code upstream first, you should start by
> submitting the kernel patches against net-next when that tree is open (I
> don't follow netdev, so I have no idea when that is).  Then, once the
> GTP code is upstream, we can get it into OVS here.

As a timely update from netdev conf, Andreas Schutlz and Harald Welte
spoke about upstream Linux GTP at netdev conference, so it may be
worth co-ordinating with them:

http://www.netdevconf.org/1.1/talk-kernel-level-gtp-generic-tunneling-protocol-implementation-harald-welte-andreas-schultz.html

Patch
diff mbox

diff --git a/datapath/Modules.mk b/datapath/Modules.mk
index 3ffeee2..77c2ea6 100644
--- a/datapath/Modules.mk
+++ b/datapath/Modules.mk
@@ -9,6 +9,7 @@  both_modules = \
 	vport_geneve \
 	vport_gre \
 	vport_lisp \
+	vport_gtp \
 	vport_stt \
 	vport_vxlan
 # When changing the name of 'build_modules', please also update the
@@ -32,6 +33,7 @@  vport_geneve_sources = vport-geneve.c
 vport_vxlan_sources = vport-vxlan.c
 vport_gre_sources = vport-gre.c
 vport_lisp_sources = vport-lisp.c
+vport_gtp_sources = vport-gtp.c
 vport_stt_sources = vport-stt.c
 
 openvswitch_headers = \
diff --git a/datapath/linux/Modules.mk b/datapath/linux/Modules.mk
index 6ab52a7..7c1b19d 100644
--- a/datapath/linux/Modules.mk
+++ b/datapath/linux/Modules.mk
@@ -14,6 +14,7 @@  openvswitch_sources += \
 	linux/compat/ip_tunnels_core.c \
 	linux/compat/ip6_output.c \
 	linux/compat/lisp.c \
+	linux/compat/gtp.c \
 	linux/compat/netdevice.c \
 	linux/compat/net_namespace.c \
 	linux/compat/nf_conntrack_core.c \
@@ -51,6 +52,7 @@  openvswitch_headers += \
 	linux/compat/include/linux/kconfig.h \
 	linux/compat/include/linux/kernel.h \
 	linux/compat/include/net/lisp.h \
+	linux/compat/include/net/gtp.h \
 	linux/compat/include/linux/list.h \
 	linux/compat/include/linux/mpls.h \
 	linux/compat/include/linux/net.h \
diff --git a/datapath/linux/compat/gtp.c b/datapath/linux/compat/gtp.c
new file mode 100644
index 0000000..4671ec2
--- /dev/null
+++ b/datapath/linux/compat/gtp.c
@@ -0,0 +1,714 @@ 
+/*
+ * Copyright (c) 2015 Nicira, Inc.
+ * Copyright (c) 2013 Cisco Systems, Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/version.h>
+
+#include <linux/in.h>
+#include <linux/ip.h>
+#include <linux/net.h>
+#include <linux/module.h>
+#include <linux/rculist.h>
+#include <linux/udp.h>
+
+#include <net/icmp.h>
+#include <net/ip.h>
+#include <net/gtp.h>
+#include <net/net_namespace.h>
+#include <net/netns/generic.h>
+#include <net/route.h>
+#include <net/udp.h>
+#include <net/udp_tunnel.h>
+#include <net/xfrm.h>
+
+#include "datapath.h"
+#include "gso.h"
+#include "vport.h"
+#include "gso.h"
+#include "vport-netdev.h"
+
+#define GTP_UDP_PORT		2152
+#define GTP_NETDEV_VER		"0.1"
+static int gtp_net_id;
+
+/* Pseudo network device */
+struct gtp_dev {
+    struct net         *net;        /* netns for packet i/o */
+    struct net_device  *dev;        /* netdev for gtp tunnel */
+    struct socket      *sock;
+    __be16             dst_port;
+    struct list_head   next;
+};
+
+/* per-network namespace private data for this module */
+struct gtp_net {
+    struct list_head gtp_list;
+};
+
+/*
+ *  GTP encapsulation header:
+ *
+ *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ *  |  V  |P|R|E|S|N|  Message Type |        Total Length           |
+ *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ *  |                           TEID                                |
+ *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ *  |      Sequence Number          | N-PDU Number  | Next Extension|
+ *  |                               |               |  Header type  |
+ *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ *
+ * Extension Header:
+ *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ *  |  Total length |                 Contents                      |
+ *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ *  |                          Contents                             |
+ *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ *  |                   Contents                  | Next Extntn hdr |
+ *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ */
+
+/**
+ * struct gtphdr - GTP header:
+ *
+ * @version(V): 3-bit field. For GTPv1, this has a value of 1.
+ * @protocol_type(P): a 1-bit value that differentiates GTP (value 1) from
+ *                    GTP' (value 0).
+ * @reserved(R): a 1-bit reserved field (must be 0).
+ * @extension_header_flag(E): a 1-bit value that states whether there is an
+ *                            extension header optional field.
+ * @sequence_number_flag(S): a 1-bit value that states whether there is a
+ *                           Sequence Number optional field.
+ * @n_pdu_number_flag(N): a 1-bit value that states whether there is a N-PDU
+ *                        number optional field.
+ * @message_type: an 8-bit field that indicates the type of GTP message.
+ * @total_length: a 16-bit field that indicates the length of the payload in
+ *                bytes (rest of the packet following the mandatory 8-byte GTP
+ *                header). Includes the optional fields.
+ * @teid: A 32-bit(4-octet) field used to multiplex different connections in
+          the same GTP tunnel.
+ * @sequence_number: an (optional) 16-bit field. This field exists if any of the
+ *                   E, S, or PN bits are on. The field must be interpreted only
+ *                   if the S bit is on.
+ * @n_pdu_number: an (optional) 8-bit field. This field exists if any of the E,
+ *                S, or PN bits are on. The field must be interpreted only if
+ *                the PN bit is on.
+ * @next_extension_header_type: an (optional) 8-bit field. This field exists if
+ *                              any of the E, S, or PN bits are on. The field
+ *                              must be interpreted only if the E bit is on.
+ *
+ * Extenstion header:
+ * @length: an 8-bit field. This field states the length of this extension
+ *          header, including the length, the contents, and the next extension
+ *          header field, in 4-octet units, so the length of the extension must
+ *          always be a multiple of 4.
+ * @contents: extension header contents.
+ * @next_extension_header: an 8-bit field. It states the type of the next
+ *                         extension, or 0 if no next extension exists. This
+ *                         permits chaining several next extension headers.
+ */
+
+struct gtp_extension_hdr {
+    u8 length;
+    u8 next_extension_hdr_type;
+    u8 extension_data[];
+};
+
+struct gtphdr {
+#ifdef __LITTLE_ENDIAN_BITFIELD
+    __u8 n_pdu_number_flag:1;
+    __u8 sequence_hdr_flag:1;
+    __u8 extension_hdr_flag:1;
+    __u8 reserved:1;
+    __u8 protocol_type:1;
+    __u8 version:2;
+#else
+    __u8 version:2;
+    __u8 protocol_type:1;
+    __u8 reserved:1;
+    __u8 extension_hdr_flag:1;
+    __u8 sequence_hdr_flag:1;
+    __u8 n_pdu_number_flag:1;
+#endif
+    __u8 message_type;
+    __be16 total_length;
+    __be32 teid;
+    struct gtp_extension_hdr extensions[];
+};
+
+#define GTP_HLEN (sizeof(struct udphdr) + sizeof(struct gtphdr))
+
+static inline struct gtphdr *gtp_hdr(const struct sk_buff *skb)
+{
+    return (struct gtphdr *)(udp_hdr(skb) + 1);
+}
+
+/* Compute source UDP port for outgoing packet.
+ * Currently we use the flow hash.
+ */
+static u16 get_src_port(struct net *net, struct sk_buff *skb)
+{
+    u32 hash = skb_get_hash(skb);
+    unsigned int range;
+    int high;
+    int low;
+
+    if (!hash) {
+        if (skb->protocol == htons(ETH_P_IP)) {
+            struct iphdr *iph;
+            int size = (sizeof(iph->saddr) * 2) / sizeof(u32);
+
+            iph = (struct iphdr *) skb_network_header(skb);
+            hash = jhash2((const u32 *)&iph->saddr, size, 0);
+        } else if (skb->protocol == htons(ETH_P_IPV6)) {
+            struct ipv6hdr *ipv6hdr;
+
+            ipv6hdr = (struct ipv6hdr *) skb_network_header(skb);
+            hash = jhash2((const u32 *)&ipv6hdr->saddr,
+                          (sizeof(struct in6_addr) * 2) / sizeof(u32), 0);
+        } else {
+            pr_warn_once("GTP inner protocol is not IP when "
+                         "calculating hash.\n");
+        }
+    }
+
+    inet_get_local_port_range(net, &low, &high);
+    range = (high - low) + 1;
+    return (((u64) hash * range) >> 32) + low;
+}
+
+static void gtp_build_header(struct sk_buff *skb,
+                             const struct ip_tunnel_key *tun_key)
+{
+    struct gtphdr *gtph;
+
+    gtph = (struct gtphdr *)__skb_push(skb, sizeof(struct gtphdr));
+    gtph->version = 1;       /* GTP-U version 1 */
+    gtph->protocol_type = 1; /* GTP Protocol */
+    gtph->reserved = 0;    /* Reserved flags, set to 0  */
+    gtph->extension_hdr_flag = 0; /* No extension header present */
+    gtph->sequence_hdr_flag = 0; /* No Sequence No. present  */
+    gtph->n_pdu_number_flag = 0;      /* No N PDU present */
+    gtph->message_type = 255; /* GPDU Packets */
+    /* mandatory part of GTP header first 8 octets */
+    gtph->total_length = htons(skb->len) - htons(sizeof(struct gtphdr));
+    gtph->teid = htonl(be64_to_cpu(tun_key->tun_id));
+}
+
+/* Called with rcu_read_lock and BH disabled. */
+static int gtp_rcv(struct sock *sk, struct sk_buff *skb)
+{
+    struct net_device *dev;
+    struct gtphdr *gtph;
+    struct iphdr *inner_iph;
+    struct metadata_dst *tun_dst;
+#ifndef HAVE_METADATA_DST
+    struct metadata_dst temp;
+#endif
+    __be64 key;
+    struct ethhdr *ethh;
+    __be16 protocol;
+
+    dev = rcu_dereference_sk_user_data(sk);
+    if (unlikely(!dev))
+        goto error;
+
+    if (iptunnel_pull_header(skb, GTP_HLEN, 0))
+        goto error;
+
+    gtph = gtp_hdr(skb);
+
+    key = cpu_to_be64(ntohl(gtph->teid));
+
+    /* Save outer tunnel values */
+#ifndef HAVE_METADATA_DST
+    tun_dst = &temp;
+    ovs_udp_tun_rx_dst(&tun_dst->u.tun_info, skb, AF_INET, TUNNEL_KEY, key, 0);
+#else
+    tun_dst = udp_tun_rx_dst(skb, AF_INET, TUNNEL_KEY, key, 0);
+#endif
+    /* Drop non-IP inner packets */
+    inner_iph = (struct iphdr *)(gtph + 1);
+    switch (inner_iph->version) {
+    case 4:
+        protocol = htons(ETH_P_IP);
+        break;
+    case 6:
+        protocol = htons(ETH_P_IPV6);
+        break;
+    default:
+        goto error;
+    }
+    skb->protocol = protocol;
+
+    /* Add Ethernet header */
+    ethh = (struct ethhdr *)skb_push(skb, ETH_HLEN);
+    memset(ethh, 0, ETH_HLEN);
+    ethh->h_dest[0] = 0x06;
+    ethh->h_source[0] = 0x06;
+    ethh->h_proto = protocol;
+
+    ovs_ip_tunnel_rcv(dev, skb, tun_dst);
+    goto out;
+
+error:
+    kfree_skb(skb);
+out:
+    return 0;
+}
+
+netdev_tx_t rpl_gtp_xmit(struct sk_buff *skb)
+{
+    struct net_device *dev = skb->dev;
+    struct gtp_dev *gtp_dev = netdev_priv(dev);
+    struct net *net = gtp_dev->net;
+    int network_offset = skb_network_offset(skb);
+    struct ip_tunnel_info *info;
+    struct ip_tunnel_key *tun_key;
+    struct rtable *rt;
+    int min_headroom;
+    __be16 src_port, dst_port;
+    struct flowi4 fl;
+    __be16 df;
+    int err;
+
+    info = skb_tunnel_info(skb);
+    if (unlikely(!info)) {
+        err = -EINVAL;
+        goto error;
+    }
+
+    if (skb->protocol != htons(ETH_P_IP) &&
+        skb->protocol != htons(ETH_P_IPV6)) {
+        err = 0;
+        goto error;
+    }
+
+    tun_key = &info->key;
+
+    /* Route lookup */
+    memset(&fl, 0, sizeof(fl));
+    fl.daddr = tun_key->u.ipv4.dst;
+    fl.saddr = tun_key->u.ipv4.src;
+    fl.flowi4_tos = RT_TOS(tun_key->tos);
+    fl.flowi4_mark = skb->mark;
+    fl.flowi4_proto = IPPROTO_UDP;
+    rt = ip_route_output_key(net, &fl);
+    if (IS_ERR(rt)) {
+        err = PTR_ERR(rt);
+        goto error;
+    }
+
+    min_headroom = LL_RESERVED_SPACE(rt_dst(rt).dev) + rt_dst(rt).header_len
+                   + sizeof(struct iphdr) + GTP_HLEN;
+
+    if (skb_headroom(skb) < min_headroom || skb_header_cloned(skb)) {
+        int head_delta = SKB_DATA_ALIGN(min_headroom -
+                                        skb_headroom(skb) + 16);
+
+        err = pskb_expand_head(skb, max_t(int, head_delta, 0),
+                               0, GFP_ATOMIC);
+        if (unlikely(err))
+            goto err_free_rt;
+    }
+
+    /* Reset l2 headers. */
+    skb_pull(skb, network_offset);
+    skb_reset_mac_header(skb);
+    vlan_set_tci(skb, 0);
+
+    skb = udp_tunnel_handle_offloads(skb, false, 0, false);
+    if (IS_ERR(skb)) {
+        err = PTR_ERR(skb);
+        skb = NULL;
+        goto err_free_rt;
+    }
+
+    src_port = htons(get_src_port(net, skb));
+    dst_port = gtp_dev->dst_port;
+
+    gtp_build_header(skb, tun_key);
+
+    skb->ignore_df = 1;
+
+    ovs_skb_set_inner_protocol(skb, skb->protocol);
+
+    df = tun_key->tun_flags & TUNNEL_DONT_FRAGMENT ? htons(IP_DF) : 0;
+    err = udp_tunnel_xmit_skb(rt, gtp_dev->sock->sk, skb,
+                              fl.saddr, tun_key->u.ipv4.dst,
+                              tun_key->tos, tun_key->ttl,
+                              df, src_port, dst_port, false, true);
+
+    iptunnel_xmit_stats(err, &dev->stats,
+                       (struct pcpu_sw_netstats __percpu *)dev->tstats);
+    return NETDEV_TX_OK;
+
+err_free_rt:
+    ip_rt_put(rt);
+error:
+    kfree_skb(skb);
+    return NETDEV_TX_OK;
+}
+EXPORT_SYMBOL(rpl_gtp_xmit);
+
+#ifdef HAVE_DEV_TSTATS
+/* Setup stats when device is created */
+static int gtp_init(struct net_device *dev)
+{
+    dev->tstats = (typeof(dev->tstats))
+                  netdev_alloc_pcpu_stats(struct pcpu_sw_netstats);
+    if (!dev->tstats)
+        return -ENOMEM;
+
+    return 0;
+}
+
+static void gtp_uninit(struct net_device *dev)
+{
+    free_percpu(dev->tstats);
+}
+#endif
+
+static struct socket *create_sock(struct net *net, bool ipv6,
+                                  __be16 port)
+{
+    struct socket *sock;
+    struct udp_port_cfg udp_conf;
+    int err;
+
+    memset(&udp_conf, 0, sizeof(udp_conf));
+
+    if (ipv6) {
+        udp_conf.family = AF_INET6;
+    } else {
+        udp_conf.family = AF_INET;
+        udp_conf.local_ip.s_addr = htonl(INADDR_ANY);
+    }
+
+    udp_conf.local_udp_port = port;
+
+    /* Open UDP socket */
+    err = udp_sock_create(net, &udp_conf, &sock);
+    if (err < 0)
+        return ERR_PTR(err);
+
+    return sock;
+}
+
+static int gtp_open(struct net_device *dev)
+{
+    struct gtp_dev *gtp = netdev_priv(dev);
+    struct udp_tunnel_sock_cfg tunnel_cfg;
+    struct net *net = gtp->net;
+
+    gtp->sock = create_sock(net, false, gtp->dst_port);
+    if (IS_ERR(gtp->sock))
+        return PTR_ERR(gtp->sock);
+
+    /* Mark socket as an encapsulation socket */
+    tunnel_cfg.sk_user_data = dev;
+    tunnel_cfg.encap_type = 1;
+    tunnel_cfg.encap_rcv = gtp_rcv;
+    tunnel_cfg.encap_destroy = NULL;
+    setup_udp_tunnel_sock(net, gtp->sock, &tunnel_cfg);
+    return 0;
+}
+
+static int gtp_stop(struct net_device *dev)
+{
+    struct gtp_dev *gtp = netdev_priv(dev);
+
+    udp_tunnel_sock_release(gtp->sock);
+    gtp->sock = NULL;
+    return 0;
+}
+
+static netdev_tx_t gtp_dev_xmit(struct sk_buff *skb, struct net_device *dev)
+{
+#ifdef HAVE_METADATA_DST
+    return rpl_gtp_xmit(skb);
+#else
+    /* Drop All packets coming from networking stack. OVS-CB is
+     * not initialized for these packets.
+     */
+
+    dev_kfree_skb(skb);
+    dev->stats.tx_dropped++;
+    return NETDEV_TX_OK;
+#endif
+}
+
+static const struct net_device_ops gtp_netdev_ops = {
+#ifdef HAVE_DEV_TSTATS
+    .ndo_init               = gtp_init,
+    .ndo_uninit             = gtp_uninit,
+    .ndo_get_stats64        = ip_tunnel_get_stats64,
+#endif
+    .ndo_open               = gtp_open,
+    .ndo_stop               = gtp_stop,
+    .ndo_start_xmit         = gtp_dev_xmit,
+    .ndo_change_mtu         = eth_change_mtu,
+    .ndo_validate_addr      = eth_validate_addr,
+    .ndo_set_mac_address    = eth_mac_addr,
+};
+
+static void gtp_get_drvinfo(struct net_device *dev,
+                            struct ethtool_drvinfo *drvinfo)
+{
+    strlcpy(drvinfo->version, GTP_NETDEV_VER, sizeof(drvinfo->version));
+    strlcpy(drvinfo->driver, "gtp", sizeof(drvinfo->driver));
+}
+
+static const struct ethtool_ops gtp_ethtool_ops = {
+    .get_drvinfo    = gtp_get_drvinfo,
+    .get_link       = ethtool_op_get_link,
+};
+
+/* Info for udev, that this is a virtual tunnel endpoint */
+static struct device_type gtp_type = {
+    .name = "gtp",
+};
+
+/* Initialize the device structure. */
+static void gtp_setup(struct net_device *dev)
+{
+    ether_setup(dev);
+
+    dev->netdev_ops = &gtp_netdev_ops;
+    dev->ethtool_ops = &gtp_ethtool_ops;
+    dev->destructor = free_netdev;
+
+    SET_NETDEV_DEVTYPE(dev, &gtp_type);
+
+    dev->features    |= NETIF_F_LLTX | NETIF_F_NETNS_LOCAL;
+    dev->features    |= NETIF_F_SG | NETIF_F_HW_CSUM;
+    dev->features    |= NETIF_F_RXCSUM;
+    dev->features    |= NETIF_F_GSO_SOFTWARE;
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,39)
+    dev->hw_features |= NETIF_F_SG | NETIF_F_HW_CSUM | NETIF_F_RXCSUM;
+    dev->hw_features |= NETIF_F_GSO_SOFTWARE;
+#endif
+#ifdef HAVE_METADATA_DST
+    netif_keep_dst(dev);
+#endif
+    dev->priv_flags |= IFF_LIVE_ADDR_CHANGE | IFF_NO_QUEUE;
+    eth_hw_addr_random(dev);
+}
+
+static const struct nla_policy gtp_policy[IFLA_GTP_MAX + 1] = {
+    [IFLA_GTP_PORT] = { .type = NLA_U16 },
+};
+
+static int gtp_validate(struct nlattr *tb[], struct nlattr *data[])
+{
+    if (tb[IFLA_ADDRESS]) {
+        if (nla_len(tb[IFLA_ADDRESS]) != ETH_ALEN)
+            return -EINVAL;
+
+        if (!is_valid_ether_addr(nla_data(tb[IFLA_ADDRESS])))
+            return -EADDRNOTAVAIL;
+    }
+
+    return 0;
+}
+
+static struct gtp_dev *find_dev(struct net *net, __be16 dst_port)
+{
+    struct gtp_net *ln = net_generic(net, gtp_net_id);
+    struct gtp_dev *dev;
+
+    list_for_each_entry(dev, &ln->gtp_list, next) {
+        if (dev->dst_port == dst_port)
+            return dev;
+    }
+    return NULL;
+}
+
+static int gtp_configure(struct net *net, struct net_device *dev,
+			  __be16 dst_port)
+{
+    struct gtp_net *ln = net_generic(net, gtp_net_id);
+    struct gtp_dev *gtp = netdev_priv(dev);
+    int err;
+
+    gtp->net = net;
+    gtp->dev = dev;
+
+    gtp->dst_port = dst_port;
+
+    if (find_dev(net, dst_port))
+        return -EBUSY;
+
+    err = register_netdevice(dev);
+    if (err)
+        return err;
+
+    list_add(&gtp->next, &ln->gtp_list);
+    return 0;
+}
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,39)
+static int gtp_newlink(struct net *net, struct net_device *dev,
+                       struct nlattr *tb[], struct nlattr *data[])
+{
+#else
+static int gtp_newlink(struct net_device *dev,
+                       struct nlattr *tb[], struct nlattr *data[])
+
+{
+    struct net *net = &init_net;
+#endif
+    __be16 dst_port = htons(GTP_UDP_PORT);
+
+    if (data[IFLA_GTP_PORT])
+        dst_port = nla_get_be16(data[IFLA_GTP_PORT]);
+
+    return gtp_configure(net, dev, dst_port);
+}
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,39)
+static void gtp_dellink(struct net_device *dev, struct list_head *head)
+#else
+static void gtp_dellink(struct net_device *dev)
+#endif
+{
+    struct gtp_dev *gtp = netdev_priv(dev);
+
+    list_del(&gtp->next);
+    unregister_netdevice_queue(dev, head);
+}
+
+static size_t gtp_get_size(const struct net_device *dev)
+{
+    return nla_total_size(sizeof(__be32));  /* IFLA_GTP_PORT */
+}
+
+static int gtp_fill_info(struct sk_buff *skb, const struct net_device *dev)
+{
+    struct gtp_dev *gtp = netdev_priv(dev);
+
+    if (nla_put_be16(skb, IFLA_GTP_PORT, gtp->dst_port))
+        goto nla_put_failure;
+
+    return 0;
+
+nla_put_failure:
+    return -EMSGSIZE;
+}
+
+static struct rtnl_link_ops gtp_link_ops __read_mostly = {
+    .kind           = "gtp",
+    .maxtype        = IFLA_GTP_MAX,
+    .policy         = gtp_policy,
+    .priv_size      = sizeof(struct gtp_dev),
+    .setup          = gtp_setup,
+    .validate       = gtp_validate,
+    .newlink        = gtp_newlink,
+    .dellink        = gtp_dellink,
+    .get_size       = gtp_get_size,
+    .fill_info      = gtp_fill_info,
+};
+
+struct net_device *rpl_gtp_dev_create_fb(struct net *net, const char *name,
+                                         u8 name_assign_type, u16 dst_port)
+{
+    struct nlattr *tb[IFLA_MAX + 1];
+    struct net_device *dev;
+    int err;
+
+    memset(tb, 0, sizeof(tb));
+    dev = rtnl_create_link(net, (char *) name, name_assign_type,
+                           &gtp_link_ops, tb);
+    if (IS_ERR(dev))
+        return dev;
+
+    err = gtp_configure(net, dev, htons(dst_port));
+    if (err) {
+        free_netdev(dev);
+        return ERR_PTR(err);
+    }
+    return dev;
+}
+EXPORT_SYMBOL_GPL(rpl_gtp_dev_create_fb);
+
+static int gtp_init_net(struct net *net)
+{
+    struct gtp_net *ln = net_generic(net, gtp_net_id);
+
+    INIT_LIST_HEAD(&ln->gtp_list);
+    return 0;
+}
+
+static void gtp_exit_net(struct net *net)
+{
+    struct gtp_net *ln = net_generic(net, gtp_net_id);
+    struct gtp_dev *gtp, *next;
+    struct net_device *dev, *aux;
+    LIST_HEAD(list);
+
+    rtnl_lock();
+
+    /* gather any gtp devices that were moved into this ns */
+    for_each_netdev_safe(net, dev, aux)
+    if (dev->rtnl_link_ops == &gtp_link_ops)
+        unregister_netdevice_queue(dev, &list);
+
+    list_for_each_entry_safe(gtp, next, &ln->gtp_list, next) {
+        /* If gtp->dev is in the same netns, it was already added
+         * to the gtp by the previous loop.
+         */
+        if (!net_eq(dev_net(gtp->dev), net))
+            unregister_netdevice_queue(gtp->dev, &list);
+    }
+
+    /* unregister the devices gathered above */
+    unregister_netdevice_many(&list);
+    rtnl_unlock();
+}
+
+static struct pernet_operations gtp_net_ops = {
+    .init = gtp_init_net,
+    .exit = gtp_exit_net,
+    .id   = &gtp_net_id,
+    .size = sizeof(struct gtp_net),
+};
+
+DEFINE_COMPAT_PNET_REG_FUNC(device)
+int rpl_gtp_init_module(void)
+{
+    int rc;
+
+    rc = register_pernet_subsys(&gtp_net_ops);
+    if (rc)
+        goto out1;
+
+    rc = rtnl_link_register(&gtp_link_ops);
+    if (rc)
+        goto out2;
+
+    pr_info("GTP tunneling driver\n");
+    return 0;
+out2:
+    unregister_pernet_subsys(&gtp_net_ops);
+out1:
+    return rc;
+}
+
+void rpl_gtp_cleanup_module(void)
+{
+    rtnl_link_unregister(&gtp_link_ops);
+    unregister_pernet_subsys(&gtp_net_ops);
+}
diff --git a/datapath/linux/compat/include/linux/if_link.h b/datapath/linux/compat/include/linux/if_link.h
index 6209dcb..82e6562 100644
--- a/datapath/linux/compat/include/linux/if_link.h
+++ b/datapath/linux/compat/include/linux/if_link.h
@@ -46,6 +46,13 @@  enum {
 };
 #define IFLA_LISP_MAX	(__IFLA_LISP_MAX - 1)
 
+/* GTP section */
+enum {
+	IFLA_GTP_PORT,	/* destination port */
+	__IFLA_GTP_MAX
+};
+#define IFLA_GTP_MAX	(__IFLA_GTP_MAX - 1)
+
 /* VXLAN section */
 enum {
 #define IFLA_VXLAN_UNSPEC rpl_IFLA_VXLAN_UNSPEC
diff --git a/datapath/linux/compat/include/linux/openvswitch.h b/datapath/linux/compat/include/linux/openvswitch.h
index 3b39ebb..49bdd51 100644
--- a/datapath/linux/compat/include/linux/openvswitch.h
+++ b/datapath/linux/compat/include/linux/openvswitch.h
@@ -235,6 +235,7 @@  enum ovs_vport_type {
 	OVS_VPORT_TYPE_GENEVE,	 /* Geneve tunnel. */
 	OVS_VPORT_TYPE_LISP = 105,  /* LISP tunnel */
 	OVS_VPORT_TYPE_STT = 106, /* STT tunnel */
+	OVS_VPORT_TYPE_GTP = 107,  /* GTP tunnel */
 	__OVS_VPORT_TYPE_MAX
 };
 
diff --git a/datapath/linux/compat/include/net/gtp.h b/datapath/linux/compat/include/net/gtp.h
new file mode 100644
index 0000000..05623bd
--- /dev/null
+++ b/datapath/linux/compat/include/net/gtp.h
@@ -0,0 +1,24 @@ 
+#ifndef __NET_GTP_WRAPPER_H
+#define __NET_GTP_WRAPPER_H  1
+
+#ifdef CONFIG_INET
+#include <net/udp_tunnel.h>
+#endif
+
+
+#ifdef CONFIG_INET
+#define gtp_dev_create_fb rpl_gtp_dev_create_fb
+struct net_device *rpl_gtp_dev_create_fb(struct net *net, const char *name,
+                                         u8 name_assign_type, u16 dst_port);
+#endif /*ifdef CONFIG_INET */
+
+#define gtp_init_module rpl_gtp_init_module
+int rpl_gtp_init_module(void);
+
+#define gtp_cleanup_module rpl_gtp_cleanup_module
+void rpl_gtp_cleanup_module(void);
+
+#define gtp_xmit rpl_gtp_xmit
+netdev_tx_t rpl_gtp_xmit(struct sk_buff *skb);
+
+#endif /*ifdef__NET_GTP_H */
diff --git a/datapath/vport-gtp.c b/datapath/vport-gtp.c
new file mode 100644
index 0000000..6892b2b
--- /dev/null
+++ b/datapath/vport-gtp.c
@@ -0,0 +1,150 @@ 
+/*
+ * Copyright (c) 2015 Nicira, Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/in.h>
+#include <linux/ip.h>
+#include <linux/net.h>
+#include <linux/rculist.h>
+#include <linux/udp.h>
+#include <linux/if_vlan.h>
+#include <linux/module.h>
+
+#include <net/gtp.h>
+#include <net/icmp.h>
+#include <net/ip.h>
+#include <net/route.h>
+#include <net/udp.h>
+#include <net/xfrm.h>
+
+#include "datapath.h"
+#include "vport.h"
+#include "vport-netdev.h"
+
+static struct vport_ops ovs_gtp_vport_ops;
+/**
+ * struct gtp_port - Keeps track of open UDP ports
+ * @dst_port: destination port.
+ */
+struct gtp_port {
+    u16 port_no;
+};
+
+static inline struct gtp_port *gtp_vport(const struct vport *vport)
+{
+    return vport_priv(vport);
+}
+
+static int gtp_get_options(const struct vport *vport,
+                           struct sk_buff *skb)
+{
+    struct gtp_port *gtp_port = gtp_vport(vport);
+
+    if (nla_put_u16(skb, OVS_TUNNEL_ATTR_DST_PORT, gtp_port->port_no))
+        return -EMSGSIZE;
+    return 0;
+}
+
+static int gtp_get_egress_tun_info(struct vport *vport, struct sk_buff *skb,
+                                   struct dp_upcall_info *upcall)
+{
+    struct gtp_port *gtp_port = gtp_vport(vport);
+    struct net *net = ovs_dp_get_net(vport->dp);
+    __be16 dport = htons(gtp_port->port_no);
+    __be16 sport = udp_flow_src_port(net, skb, 1, USHRT_MAX, true);
+
+    return ovs_tunnel_get_egress_info(upcall, ovs_dp_get_net(vport->dp),
+                                      skb, IPPROTO_UDP, sport, dport);
+}
+
+static struct vport *gtp_tnl_create(const struct vport_parms *parms)
+{
+    struct net *net = ovs_dp_get_net(parms->dp);
+    struct nlattr *options = parms->options;
+    struct gtp_port *gtp_port;
+    struct net_device *dev;
+    struct vport *vport;
+    struct nlattr *a;
+    u16 dst_port;
+    int err;
+
+    if (!options) {
+        err = -EINVAL;
+        goto error;
+    }
+
+    a = nla_find_nested(options, OVS_TUNNEL_ATTR_DST_PORT);
+    if (a && nla_len(a) == sizeof(u16)) {
+        dst_port = nla_get_u16(a);
+    } else {
+        /* Require destination port from userspace. */
+        err = -EINVAL;
+        goto error;
+    }
+
+    vport = ovs_vport_alloc(sizeof(struct gtp_port),
+                            &ovs_gtp_vport_ops, parms);
+    if (IS_ERR(vport))
+        return vport;
+
+    gtp_port = gtp_vport(vport);
+    gtp_port->port_no = dst_port;
+
+    rtnl_lock();
+    dev = gtp_dev_create_fb(net, parms->name, NET_NAME_USER, dst_port);
+    if (IS_ERR(dev)) {
+        rtnl_unlock();
+        ovs_vport_free(vport);
+        return ERR_CAST(dev);
+    }
+
+    dev_change_flags(dev, dev->flags | IFF_UP);
+    rtnl_unlock();
+    return vport;
+error:
+    return ERR_PTR(err);
+}
+
+static struct vport *gtp_create(const struct vport_parms *parms)
+{
+    struct vport *vport;
+
+    vport = gtp_tnl_create(parms);
+    if (IS_ERR(vport))
+        return vport;
+
+    return ovs_netdev_link(vport, parms->name);
+}
+
+static struct vport_ops ovs_gtp_vport_ops = {
+    .type                = OVS_VPORT_TYPE_GTP,
+    .create              = gtp_create,
+    .destroy             = ovs_netdev_tunnel_destroy,
+    .get_options         = gtp_get_options,
+    .send                = gtp_xmit,
+    .get_egress_tun_info = gtp_get_egress_tun_info,
+};
+
+static int __init ovs_gtp_tnl_init(void)
+{
+    return ovs_vport_ops_register(&ovs_gtp_vport_ops);
+}
+
+static void __exit ovs_gtp_tnl_exit(void)
+{
+    ovs_vport_ops_unregister(&ovs_gtp_vport_ops);
+}
+
+module_init(ovs_gtp_tnl_init);
+module_exit(ovs_gtp_tnl_exit);
+
+MODULE_DESCRIPTION("OVS: Gtp switching port");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("vport-type-107");
diff --git a/datapath/vport.c b/datapath/vport.c
index 7fd9858..24212ba 100644
--- a/datapath/vport.c
+++ b/datapath/vport.c
@@ -31,6 +31,7 @@ 
 #include <linux/if_link.h>
 #include <net/net_namespace.h>
 #include <net/lisp.h>
+#include <net/gtp.h>
 #include <net/gre.h>
 #include <net/geneve.h>
 #include <net/vxlan.h>
@@ -64,6 +65,9 @@  int ovs_vport_init(void)
 	err = lisp_init_module();
 	if (err)
 		goto err_lisp;
+	err = gtp_init_module();
+	if (err)
+		goto err_gtp;
 	err = ipgre_init();
 	if (err)
 		goto err_gre;
@@ -86,6 +90,8 @@  err_vxlan:
 err_geneve:
 	ipgre_fini();
 err_gre:
+	gtp_cleanup_module();
+err_gtp:
 	lisp_cleanup_module();
 err_lisp:
 	kfree(dev_table);
@@ -103,6 +109,7 @@  void ovs_vport_exit(void)
 	vxlan_cleanup_module();
 	geneve_cleanup_module();
 	ipgre_fini();
+	gtp_cleanup_module();
 	lisp_cleanup_module();
 	kfree(dev_table);
 }
diff --git a/lib/dpif-netlink.c b/lib/dpif-netlink.c
index 5e48393..8106c10 100644
--- a/lib/dpif-netlink.c
+++ b/lib/dpif-netlink.c
@@ -766,6 +766,9 @@  get_vport_type(const struct dpif_netlink_vport *vport)
     case OVS_VPORT_TYPE_LISP:
         return "lisp";
 
+    case OVS_VPORT_TYPE_GTP:
+        return "gtp";
+
     case OVS_VPORT_TYPE_STT:
         return "stt";
 
@@ -798,6 +801,8 @@  netdev_to_ovs_vport_type(const struct netdev *netdev)
         return OVS_VPORT_TYPE_VXLAN;
     } else if (!strcmp(type, "lisp")) {
         return OVS_VPORT_TYPE_LISP;
+    } else if (!strcmp(type, "gtp")) {
+        return OVS_VPORT_TYPE_GTP;
     } else {
         return OVS_VPORT_TYPE_UNSPEC;
     }
diff --git a/lib/netdev-vport.c b/lib/netdev-vport.c
index 88f5022..38ee727 100644
--- a/lib/netdev-vport.c
+++ b/lib/netdev-vport.c
@@ -56,6 +56,7 @@  static struct vlog_rate_limit err_rl = VLOG_RATE_LIMIT_INIT(60, 5);
 #define GENEVE_DST_PORT 6081
 #define VXLAN_DST_PORT 4789
 #define LISP_DST_PORT 4341
+#define GTP_DST_PORT 2152
 #define STT_DST_PORT 7471
 
 #define VXLAN_HLEN   (sizeof(struct udp_header) +         \
@@ -156,7 +157,8 @@  netdev_vport_needs_dst_port(const struct netdev *dev)
 
     return (class->get_config == get_tunnel_config &&
             (!strcmp("geneve", type) || !strcmp("vxlan", type) ||
-             !strcmp("lisp", type) || !strcmp("stt", type)) );
+             !strcmp("lisp", type) || !strcmp("gtp", type) ||
+             !strcmp("stt", type)) );
 }
 
 const char *
@@ -255,6 +257,8 @@  netdev_vport_construct(struct netdev *netdev_)
         dev->tnl_cfg.dst_port = htons(VXLAN_DST_PORT);
     } else if (!strcmp(type, "lisp")) {
         dev->tnl_cfg.dst_port = htons(LISP_DST_PORT);
+    } else if (!strcmp(type, "gtp")) {
+        dev->tnl_cfg.dst_port = htons(GTP_DST_PORT);
     } else if (!strcmp(type, "stt")) {
         dev->tnl_cfg.dst_port = htons(STT_DST_PORT);
     }
@@ -481,6 +485,10 @@  set_tunnel_config(struct netdev *dev_, const struct smap *args)
         tnl_cfg.dst_port = htons(LISP_DST_PORT);
     }
 
+    if (!strcmp(type, "gtp")) {
+        tnl_cfg.dst_port = htons(GTP_DST_PORT);
+    }
+
     if (!strcmp(type, "stt")) {
         tnl_cfg.dst_port = htons(STT_DST_PORT);
     }
@@ -728,6 +736,7 @@  get_tunnel_config(const struct netdev *dev, struct smap *args)
         if ((!strcmp("geneve", type) && dst_port != GENEVE_DST_PORT) ||
             (!strcmp("vxlan", type) && dst_port != VXLAN_DST_PORT) ||
             (!strcmp("lisp", type) && dst_port != LISP_DST_PORT) ||
+            (!strcmp("gtp", type) && dst_port != GTP_DST_PORT) ||
             (!strcmp("stt", type) && dst_port != STT_DST_PORT)) {
             smap_add_format(args, "dst_port", "%d", dst_port);
         }
@@ -1563,6 +1572,7 @@  netdev_vport_tunnel_register(void)
                                            push_udp_header,
                                            netdev_vxlan_pop_header),
         TUNNEL_CLASS("lisp", "lisp_sys", NULL, NULL, NULL),
+        TUNNEL_CLASS("gtp", "gtp_sys", NULL, NULL, NULL),
         TUNNEL_CLASS("stt", "stt_sys", NULL, NULL, NULL),
     };
     static struct ovsthread_once once = OVSTHREAD_ONCE_INITIALIZER;
diff --git a/ofproto/ofproto-dpif-ipfix.c b/ofproto/ofproto-dpif-ipfix.c
index a610c53..3d434bc 100644
--- a/ofproto/ofproto-dpif-ipfix.c
+++ b/ofproto/ofproto-dpif-ipfix.c
@@ -71,6 +71,7 @@  enum dpif_ipfix_tunnel_type {
     DPIF_IPFIX_TUNNEL_STT = 0x04,
     DPIF_IPFIX_TUNNEL_IPSEC_GRE = 0x05,
     DPIF_IPFIX_TUNNEL_GENEVE = 0x07,
+    DPIF_IPFIX_TUNNEL_GTP = 0x08,
     NUM_DPIF_IPFIX_TUNNEL
 };
 
@@ -308,6 +309,7 @@  static uint8_t tunnel_protocol[NUM_DPIF_IPFIX_TUNNEL] = {
     IPPROTO_GRE,    /* DPIF_IPFIX_TUNNEL_IPSEC_GRE */
     0          ,    /* reserved */
     IPPROTO_UDP,    /* DPIF_IPFIX_TUNNEL_GENEVE*/
+    IPPROTO_UDP,    /* DPIF_IPFIX_TUNNEL_GTP*/
 };
 
 OVS_PACKED(
@@ -358,6 +360,7 @@  BUILD_ASSERT_DECL(sizeof(struct ipfix_data_record_aggregated_ip) == 32);
  * VxLAN: 24-bit VIN,
  * GRE: 32-bit key,
  * LISP: 24-bit instance ID
+ * GTP: 32-bit key
  * STT: 64-bit key
  */
 #define MAX_TUNNEL_KEY_LEN 8
@@ -602,6 +605,9 @@  dpif_ipfix_add_tunnel_port(struct dpif_ipfix *di, struct ofport *ofport,
     } else if (strcmp(type, "lisp") == 0) {
         dip->tunnel_type = DPIF_IPFIX_TUNNEL_LISP;
         dip->tunnel_key_length = 3;
+    } else if (strcmp(type, "gtp") == 0) {
+        dip->tunnel_type = DPIF_IPFIX_TUNNEL_GTP;
+        dip->tunnel_key_length = 4;
     } else if (strcmp(type, "geneve") == 0) {
         dip->tunnel_type = DPIF_IPFIX_TUNNEL_GENEVE;
         dip->tunnel_key_length = 3;
diff --git a/ofproto/ofproto-dpif-sflow.c b/ofproto/ofproto-dpif-sflow.c
index f11699c..c4fe04f 100644
--- a/ofproto/ofproto-dpif-sflow.c
+++ b/ofproto/ofproto-dpif-sflow.c
@@ -62,7 +62,8 @@  enum dpif_sflow_tunnel_type {
     DPIF_SFLOW_TUNNEL_GRE,
     DPIF_SFLOW_TUNNEL_LISP,
     DPIF_SFLOW_TUNNEL_IPSEC_GRE,
-    DPIF_SFLOW_TUNNEL_GENEVE
+    DPIF_SFLOW_TUNNEL_GENEVE,
+    DPIF_SFLOW_TUNNEL_GTP
 };
 
 struct dpif_sflow_port {
@@ -592,6 +593,8 @@  dpif_sflow_tunnel_type(struct ofport *ofport) {
 	    return DPIF_SFLOW_TUNNEL_VXLAN;
 	} else if (strcmp(type, "lisp") == 0) {
 	    return DPIF_SFLOW_TUNNEL_LISP;
+	} else if (strcmp(type, "gtp") == 0) {
+	    return DPIF_SFLOW_TUNNEL_GTP;
 	} else if (strcmp(type, "geneve") == 0) {
 	    return DPIF_SFLOW_TUNNEL_GENEVE;
 	}
@@ -616,6 +619,7 @@  dpif_sflow_tunnel_proto(enum dpif_sflow_tunnel_type tunnel_type)
 
     case DPIF_SFLOW_TUNNEL_VXLAN:
     case DPIF_SFLOW_TUNNEL_LISP:
+    case DPIF_SFLOW_TUNNEL_GTP:
     case DPIF_SFLOW_TUNNEL_GENEVE:
         ipproto = IPPROTO_UDP;
 
diff --git a/tests/ovs-vsctl.at b/tests/ovs-vsctl.at
index fc59652..c14425b 100644
--- a/tests/ovs-vsctl.at
+++ b/tests/ovs-vsctl.at
@@ -1235,6 +1235,7 @@  m4_foreach(
 [genev_sys],
 [gre_sys],
 [lisp_sys],
+[gtp_sys],
 [vxlan_sys]],
 [
 # Try creating the port
@@ -1263,7 +1264,9 @@  OVS_VSWITCHD_START([add-port br0 p1 -- set Interface p1 type=gre \
                     -- add-port br0 p4 -- set Interface p4 type=vxlan \
                     options:remote_ip=2.2.2.2 ofport_request=4 \
                     -- add-port br0 p5 -- set Interface p5 type=geneve \
-                    options:remote_ip=2.2.2.2 ofport_request=5])
+                    options:remote_ip=2.2.2.2 ofport_request=5 \
+                    -- add-port br0 p6 -- set Interface p6 type=gtp \
+                    options:remote_ip=2.2.2.2 ofport_request=6])
 
 # Test creating all reserved tunnel port names
 m4_foreach(
@@ -1271,6 +1274,7 @@  m4_foreach(
 [[genev_sys],
 [gre_sys],
 [lisp_sys],
+[gtp_sys],
 [vxlan_sys]],
 [
 # Try creating the port
diff --git a/tests/system-kmod-macros.at b/tests/system-kmod-macros.at
index 20ee7bf..d01ade4 100644
--- a/tests/system-kmod-macros.at
+++ b/tests/system-kmod-macros.at
@@ -18,7 +18,7 @@  m4_define([_ADD_BR], [[add-br $1 -- set Bridge $1 protocols=OpenFlow10,OpenFlow1
 m4_define([OVS_TRAFFIC_VSWITCHD_START],
   [AT_CHECK([modprobe openvswitch])
    on_exit 'modprobe -r openvswitch'
-   m4_foreach([mod], [[vport_geneve], [vport_gre], [vport_lisp], [vport_stt], [vport_vxlan]],
+   m4_foreach([mod], [[vport_geneve], [vport_gre], [vport_lisp], [vport_stt], [vport_vxlan], [vport_gtp]],
               [modprobe -q mod || echo "Module mod not loaded."
                on_exit 'modprobe -q -r mod'])
    on_exit 'ovs-dpctl del-dp ovs-system'
diff --git a/tests/tunnel.at b/tests/tunnel.at
index 0c033da..a835d7f 100644
--- a/tests/tunnel.at
+++ b/tests/tunnel.at
@@ -352,6 +352,18 @@  AT_CHECK([ovs-appctl dpif/show | tail -n +3], [0], [dnl
 OVS_VSWITCHD_STOP
 AT_CLEANUP
 
+AT_SETUP([tunnel - GTP])
+OVS_VSWITCHD_START([add-port br0 p1 -- set Interface p1 type=gtp \
+                    options:remote_ip=1.1.1.1 ofport_request=1])
+
+AT_CHECK([ovs-appctl dpif/show | tail -n +3], [0], [dnl
+		br0 65534/100: (dummy)
+		p1 1/2152: (gtp: remote_ip=1.1.1.1)
+])
+
+OVS_VSWITCHD_STOP
+AT_CLEANUP
+
 AT_SETUP([tunnel - different VXLAN UDP port])
 OVS_VSWITCHD_START([add-port br0 p1 -- set Interface p1 type=vxlan \
                     options:remote_ip=1.1.1.1 ofport_request=1 options:dst_port=4341])
diff --git a/vswitchd/vswitch.xml b/vswitchd/vswitch.xml
index c2ec914..cc757bd 100644
--- a/vswitchd/vswitch.xml
+++ b/vswitchd/vswitch.xml
@@ -1897,6 +1897,22 @@ 
             </p>
           </dd>
 
+          <dt><code>gtp</code></dt>
+          <dd>
+              GPRS Tunneling Protocol (GTP) is a group of IP-based communications
+              protocols used to carry general packet radio service (GPRS) within GSM,
+              UMTS and LTE networks.GTP-U is used for carrying user data within the GPRS
+              core network and between the radio access network and the core network.
+              The user data transported can be packets in any of IPv4, IPv6, or PPP
+              formats.
+              The protocol is documented at
+              http://www.3gpp.org/DynaReport/29281.htm
+
+              Open vSwitch uses UDP destination port 2152.  The source port used for
+              GTP traffic varies on a per-flow basis and is in the ephemeral port
+              range.
+          </dd>
+
           <dt><code>stt</code></dt>
           <dd>
             The Stateless TCP Tunnel (STT) is particularly useful when tunnel