[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-Id: <20201211122612.869225-11-jonas@norrbonn.se>
Date: Fri, 11 Dec 2020 13:26:10 +0100
From: Jonas Bonn <jonas@...rbonn.se>
To: netdev@...r.kernel.org
Cc: pablo@...filter.org, laforge@...monks.org,
Jonas Bonn <jonas@...rbonn.se>
Subject: [PATCH net-next v2 10/12] gtp: add IPv6 support
This patch adds support for handling IPv6. Both the GTP tunnel and the
tunneled packets may be IPv6; as they constitute independent streams,
both v4-over-v6 and v6-over-v4 are supported, as well.
This patch includes only the driver functionality for IPv6 support. A
follow-on patch will add support for configuring the tunnels with IPv6
addresses.
Signed-off-by: Jonas Bonn <jonas@...rbonn.se>
---
drivers/net/gtp.c | 330 +++++++++++++++++++++++++++++++++++++---------
1 file changed, 269 insertions(+), 61 deletions(-)
diff --git a/drivers/net/gtp.c b/drivers/net/gtp.c
index 86639fae8d45..4c902bffefa3 100644
--- a/drivers/net/gtp.c
+++ b/drivers/net/gtp.c
@@ -3,6 +3,7 @@
*
* (C) 2012-2014 by sysmocom - s.f.m.c. GmbH
* (C) 2016 by Pablo Neira Ayuso <pablo@...filter.org>
+ * (C) 2020 by Jonas Bonn <jonas@...rbonn.se>
*
* Author: Harald Welte <hwelte@...mocom.de>
* Pablo Neira Ayuso <pablo@...filter.org>
@@ -20,6 +21,7 @@
#include <linux/net.h>
#include <linux/file.h>
#include <linux/gtp.h>
+#include <linux/ipv6.h>
#include <net/net_namespace.h>
#include <net/protocol.h>
@@ -33,6 +35,11 @@
#include <net/netns/generic.h>
#include <net/gtp.h>
+#define PDP_F_PEER_V6 (1 << 0)
+#define PDP_F_MS_V6 (1 << 1)
+
+#define ipv4(in6addr) ((in6addr)->s6_addr32[3])
+
/* An active session for the subscriber. */
struct pdp_ctx {
struct hlist_node hlist_tid;
@@ -49,10 +56,10 @@ struct pdp_ctx {
} v1;
} u;
u8 gtp_version;
- u16 af;
+ u16 flags;
- struct in_addr ms_addr_ip4;
- struct in_addr peer_addr_ip4;
+ struct in6_addr ms_addr;
+ struct in6_addr peer_addr;
struct sock *sk;
struct net_device *dev;
@@ -97,9 +104,23 @@ static inline u32 gtp1u_hashfn(u32 tid)
return jhash_1word(tid, gtp_h_initval);
}
+static inline u32 ip_hashfn(struct in6_addr *ip)
+{
+ return __ipv6_addr_jhash(ip, gtp_h_initval);
+}
+
+static inline u32 ipv6_hashfn(struct in6_addr *ip)
+{
+ return ip_hashfn(ip);
+}
+
static inline u32 ipv4_hashfn(__be32 ip)
{
- return jhash_1word((__force u32)ip, gtp_h_initval);
+ struct in6_addr addr;
+
+ ipv6_addr_set_v4mapped(ip, &addr);
+
+ return ipv6_hashfn(&addr);
}
/* Resolve a PDP context structure based on the 64bit TID. */
@@ -134,23 +155,42 @@ static struct pdp_ctx *gtp1_pdp_find(struct gtp_dev *gtp, u32 tid)
return NULL;
}
-/* Resolve a PDP context based on IPv4 address of MS. */
-static struct pdp_ctx *ipv4_pdp_find(struct gtp_dev *gtp, __be32 ms_addr)
+static struct pdp_ctx *ip_pdp_find(struct gtp_dev *gtp,
+ struct in6_addr *ms_addr)
{
struct hlist_head *head;
struct pdp_ctx *pdp;
- head = >p->addr_hash[ipv4_hashfn(ms_addr) % gtp->hash_size];
+ head = >p->addr_hash[ipv6_hashfn(ms_addr) % gtp->hash_size];
hlist_for_each_entry_rcu(pdp, head, hlist_addr) {
- if (pdp->af == AF_INET &&
- pdp->ms_addr_ip4.s_addr == ms_addr)
+ if (ipv6_addr_equal(&pdp->ms_addr, ms_addr))
return pdp;
}
return NULL;
}
+/* Resolve a PDP context based on IPv6 address of MS. */
+static struct pdp_ctx *ipv6_pdp_find(struct gtp_dev *gtp,
+ struct in6_addr *ms_addr)
+{
+ return ip_pdp_find(gtp, ms_addr);
+}
+
+/* Resolve a PDP context based on IPv4 address of MS. */
+static struct pdp_ctx *ipv4_pdp_find(struct gtp_dev *gtp, __be32 ms_addr)
+{
+ struct in6_addr addr;
+
+ ipv6_addr_set_v4mapped(ms_addr, &addr);
+
+ return ip_pdp_find(gtp, &addr);
+}
+
+/* Check if the inner IP address in this packet is assigned to any
+ * existing mobile subscriber.
+ */
static bool gtp_check_ms_ipv4(struct sk_buff *skb, struct pdp_ctx *pctx,
unsigned int hdrlen, unsigned int role)
{
@@ -162,28 +202,51 @@ static bool gtp_check_ms_ipv4(struct sk_buff *skb, struct pdp_ctx *pctx,
iph = (struct iphdr *)(skb->data + hdrlen);
if (role == GTP_ROLE_SGSN)
- return iph->daddr == pctx->ms_addr_ip4.s_addr;
+ return iph->daddr == ipv4(&pctx->ms_addr);
else
- return iph->saddr == pctx->ms_addr_ip4.s_addr;
+ return iph->saddr == ipv4(&pctx->ms_addr);
}
-/* Check if the inner IP address in this packet is assigned to any
- * existing mobile subscriber.
- */
-static bool gtp_check_ms(struct sk_buff *skb, struct pdp_ctx *pctx,
- unsigned int hdrlen, unsigned int role)
+static bool gtp_check_ms_ipv6(struct sk_buff *skb, struct pdp_ctx *pctx,
+ unsigned int hdrlen, unsigned int role)
{
- switch (ntohs(skb->protocol)) {
- case ETH_P_IP:
- return gtp_check_ms_ipv4(skb, pctx, hdrlen, role);
- }
- return false;
+ struct ipv6hdr *iph;
+
+ if (!pskb_may_pull(skb, hdrlen + sizeof(struct ipv6hdr)))
+ return false;
+
+ iph = (struct ipv6hdr *)(skb->data + hdrlen);
+
+ if (role == GTP_ROLE_SGSN)
+ return ipv6_addr_equal(&iph->daddr, &pctx->ms_addr);
+ else
+ return ipv6_addr_equal(&iph->saddr, &pctx->ms_addr);
}
static int gtp_rx(struct pdp_ctx *pctx, struct sk_buff *skb,
unsigned int hdrlen, unsigned int role)
{
- if (!gtp_check_ms(skb, pctx, hdrlen, role)) {
+ uint8_t ipver;
+ int r;
+
+ if (!pskb_may_pull(skb, hdrlen + 1))
+ return false;
+
+ /* Get IP version of _inner_ packet */
+ ipver = inner_ip_hdr(skb)->version;
+
+ switch (ipver) {
+ case 4:
+ skb_set_inner_protocol(skb, cpu_to_be16(ETH_P_IP));
+ r = gtp_check_ms_ipv4(skb, pctx, hdrlen, role);
+ break;
+ case 6:
+ skb_set_inner_protocol(skb, cpu_to_be16(ETH_P_IPV6));
+ r = gtp_check_ms_ipv6(skb, pctx, hdrlen, role);
+ break;
+ }
+
+ if (!r) {
netdev_dbg(pctx->dev, "No PDP ctx for this MS\n");
return 1;
}
@@ -193,6 +256,8 @@ static int gtp_rx(struct pdp_ctx *pctx, struct sk_buff *skb,
!net_eq(sock_net(pctx->sk), dev_net(pctx->dev))))
return -1;
+ skb->protocol = skb->inner_protocol;
+
netdev_dbg(pctx->dev, "forwarding packet from GGSN to uplink\n");
/* Now that the UDP and the GTP header have been removed, set up the
@@ -201,7 +266,7 @@ static int gtp_rx(struct pdp_ctx *pctx, struct sk_buff *skb,
*/
skb_reset_network_header(skb);
- skb->dev = pctx->dev;
+ __skb_tunnel_rx(skb, pctx->dev, sock_net(pctx->sk));
dev_sw_netstats_rx_add(pctx->dev, skb->len);
@@ -220,7 +285,9 @@ static int gtp0_udp_encap_recv(struct gtp_dev *gtp, struct sk_buff *skb)
if (!pskb_may_pull(skb, hdrlen))
return -1;
- gtp0 = (struct gtp0_header *)(skb->data + sizeof(struct udphdr));
+ skb_set_inner_network_header(skb, skb_transport_offset(skb) + hdrlen);
+
+ gtp0 = (struct gtp0_header *)&udp_hdr(skb)[1];
if ((gtp0->flags >> 5) != GTP_V0)
return 1;
@@ -247,7 +314,9 @@ static int gtp1u_udp_encap_recv(struct gtp_dev *gtp, struct sk_buff *skb)
if (!pskb_may_pull(skb, hdrlen))
return -1;
- gtp1 = (struct gtp1_header *)(skb->data + sizeof(struct udphdr));
+ skb_set_inner_network_header(skb, skb_transport_offset(skb) + hdrlen);
+
+ gtp1 = (struct gtp1_header *)&udp_hdr(skb)[1];
if ((gtp1->flags >> 5) != GTP_V1)
return 1;
@@ -264,12 +333,10 @@ static int gtp1u_udp_encap_recv(struct gtp_dev *gtp, struct sk_buff *skb)
if (gtp1->flags & GTP1_F_MASK)
hdrlen += 4;
- /* Make sure the header is larger enough, including extensions. */
+ /* Make sure the header is large enough, including extensions. */
if (!pskb_may_pull(skb, hdrlen))
return -1;
- gtp1 = (struct gtp1_header *)(skb->data + sizeof(struct udphdr));
-
pctx = gtp1_pdp_find(gtp, ntohl(gtp1->tid));
if (!pctx) {
netdev_dbg(gtp->dev, "No PDP ctx to decap skb=%p\n", skb);
@@ -515,7 +582,7 @@ static struct rtable *gtp_get_v4_rt(struct sk_buff *skb,
memset(&fl4, 0, sizeof(fl4));
fl4.flowi4_oif = sk->sk_bound_dev_if;
- fl4.daddr = pctx->peer_addr_ip4.s_addr;
+ fl4.daddr = ipv4(&pctx->peer_addr);
fl4.saddr = inet_sk(sk)->inet_saddr;
fl4.flowi4_tos = RT_CONN_FLAGS(sk);
fl4.flowi4_proto = sk->sk_protocol;
@@ -536,6 +603,36 @@ static struct rtable *gtp_get_v4_rt(struct sk_buff *skb,
return rt;
}
+static struct dst_entry *gtp_get_v6_dst(struct sk_buff *skb,
+ struct net_device *dev,
+ struct pdp_ctx *pctx,
+ struct in6_addr *saddr)
+{
+ const struct sock *sk = pctx->sk;
+ struct dst_entry *dst = NULL;
+ struct flowi6 fl6;
+
+ memset(&fl6, 0, sizeof(fl6));
+ fl6.flowi6_mark = skb->mark;
+ fl6.flowi6_proto = IPPROTO_UDP;
+ fl6.daddr = pctx->peer_addr;
+
+ dst = ipv6_stub->ipv6_dst_lookup_flow(sock_net(sk), sk, &fl6, NULL);
+ if (IS_ERR(dst)) {
+ netdev_dbg(pctx->dev, "no route to %pI6\n", &fl6.daddr);
+ return ERR_PTR(-ENETUNREACH);
+ }
+ if (dst->dev == pctx->dev) {
+ netdev_dbg(pctx->dev, "circular route to %pI6\n", &fl6.daddr);
+ dst_release(dst);
+ return ERR_PTR(-ELOOP);
+ }
+
+ *saddr = fl6.saddr;
+
+ return dst;
+}
+
static inline void gtp0_push_header(struct sk_buff *skb, struct pdp_ctx *pctx)
{
int payload_len = skb->len;
@@ -591,10 +688,9 @@ static void gtp_push_header(struct sk_buff *skb, struct pdp_ctx *pctx,
}
}
-static int gtp_xmit_ip4(struct sk_buff *skb, struct net_device *dev)
+static int gtp_xmit_ip4(struct sk_buff *skb, struct net_device *dev,
+ struct pdp_ctx* pctx)
{
- struct gtp_dev *gtp = netdev_priv(dev);
- struct pdp_ctx *pctx;
struct rtable *rt;
__be32 saddr;
struct iphdr *iph;
@@ -602,22 +698,6 @@ static int gtp_xmit_ip4(struct sk_buff *skb, struct net_device *dev)
__be16 sport, port;
int r;
- /* Read the IP destination address and resolve the PDP context.
- * Prepend PDP header with TEI/TID from PDP ctx.
- */
- iph = ip_hdr(skb);
- if (gtp->role == GTP_ROLE_SGSN)
- pctx = ipv4_pdp_find(gtp, iph->saddr);
- else
- pctx = ipv4_pdp_find(gtp, iph->daddr);
-
- if (!pctx) {
- netdev_dbg(dev, "no PDP ctx found for %pI4, skip\n",
- &iph->daddr);
- return -ENOENT;
- }
- netdev_dbg(dev, "found PDP context %p\n", pctx);
-
rt = gtp_get_v4_rt(skb, dev, pctx, &saddr);
if (IS_ERR(rt)) {
if (PTR_ERR(rt) == -ENETUNREACH)
@@ -671,7 +751,7 @@ static int gtp_xmit_ip4(struct sk_buff *skb, struct net_device *dev)
&iph->saddr, &iph->daddr);
udp_tunnel_xmit_skb(rt, pctx->sk, skb,
- saddr, pctx->peer_addr_ip4.s_addr,
+ saddr, ipv4(&pctx->peer_addr),
iph->tos,
ip4_dst_hoplimit(&rt->dst),
0,
@@ -686,9 +766,130 @@ static int gtp_xmit_ip4(struct sk_buff *skb, struct net_device *dev)
return -EBADMSG;
}
+static int gtp_xmit_ip6(struct sk_buff *skb, struct net_device *dev,
+ struct pdp_ctx* pctx)
+{
+ struct dst_entry *dst;
+ struct in6_addr saddr;
+ struct ipv6hdr *iph;
+ int headroom;
+ __be16 sport, port;
+ int r;
+
+ dst = gtp_get_v6_dst(skb, dev, pctx, &saddr);
+ if (IS_ERR(dst)) {
+ if (PTR_ERR(dst) == -ENETUNREACH)
+ dev->stats.tx_carrier_errors++;
+ else if (PTR_ERR(dst) == -ELOOP)
+ dev->stats.collisions++;
+ return PTR_ERR(dst);
+ }
+
+ headroom = sizeof(struct ipv6hdr) + sizeof(struct udphdr);
+
+ switch (pctx->gtp_version) {
+ case GTP_V0:
+ headroom += sizeof(struct gtp0_header);
+ break;
+ case GTP_V1:
+ headroom += sizeof(struct gtp1_header);
+ break;
+ }
+
+ sport = udp_flow_src_port(sock_net(pctx->sk), skb,
+ 0, USHRT_MAX,
+ true);
+
+ r = skb_tunnel_check_pmtu(skb, dst, headroom,
+ netif_is_any_bridge_port(dev));
+ if (r < 0) {
+ dst_release(dst);
+ return r;
+ } else if (r) {
+ netif_rx(skb);
+ dst_release(dst);
+ return -EMSGSIZE;
+ }
+
+ skb_scrub_packet(skb, !net_eq(sock_net(pctx->sk), dev_net(pctx->dev)));
+
+ /* Ensure there is sufficient headroom. */
+ r = skb_cow_head(skb, dev->needed_headroom);
+ if (unlikely(r))
+ goto free_dst;
+
+ r = udp_tunnel_handle_offloads(skb, true);
+ if (unlikely(r))
+ goto free_dst;
+
+ skb_set_inner_protocol(skb, skb->protocol);
+
+ gtp_push_header(skb, pctx, &port);
+
+ iph = ipv6_hdr(skb);
+ netdev_dbg(dev, "gtp -> IP src: %pI6 dst: %pI6\n",
+ &iph->saddr, &iph->daddr);
+
+ udp_tunnel6_xmit_skb(dst, pctx->sk, skb,
+ skb->dev,
+ &saddr, &pctx->peer_addr,
+ 0,
+ ip6_dst_hoplimit(dst),
+ 0,
+ sport, port,
+ false);
+
+ return 0;
+
+free_dst:
+ dst_release(dst);
+ return -EBADMSG;
+}
+
+static struct pdp_ctx *pdp_find(struct sk_buff *skb, struct net_device *dev)
+{
+ struct gtp_dev *gtp = netdev_priv(dev);
+ unsigned int proto = ntohs(skb->protocol);
+ struct pdp_ctx* pctx = NULL;
+
+ switch (proto) {
+ case ETH_P_IP: {
+ __be32 addr;
+ struct iphdr *iph = ip_hdr(skb);
+ addr = (gtp->role == GTP_ROLE_SGSN) ? iph->saddr : iph->daddr;
+ pctx = ipv4_pdp_find(gtp, addr);
+
+ if (!pctx) {
+ netdev_dbg(dev, "no PDP ctx found for %pI4, skip\n",
+ &addr);
+ }
+
+ break;
+ }
+ case ETH_P_IPV6: {
+ struct in6_addr* addr;
+ struct ipv6hdr *iph = ipv6_hdr(skb);
+ addr = (gtp->role == GTP_ROLE_SGSN) ? &iph->saddr : &iph->daddr;
+ pctx = ipv6_pdp_find(gtp, addr);
+
+ if (!pctx) {
+ netdev_dbg(dev, "no PDP ctx found for %pI6, skip\n",
+ addr);
+ }
+
+ break;
+ }
+ default:
+ break;
+ }
+
+ return pctx;
+}
+
static netdev_tx_t gtp_dev_xmit(struct sk_buff *skb, struct net_device *dev)
{
unsigned int proto = ntohs(skb->protocol);
+ struct pdp_ctx *pctx;
int err;
if (proto != ETH_P_IP && proto != ETH_P_IPV6) {
@@ -699,7 +900,17 @@ static netdev_tx_t gtp_dev_xmit(struct sk_buff *skb, struct net_device *dev)
/* PDP context lookups in gtp_build_skb_*() need rcu read-side lock. */
rcu_read_lock();
- err = gtp_xmit_ip4(skb, dev);
+ pctx = pdp_find(skb, dev);
+ if (!pctx) {
+ err = -ENOENT;
+ rcu_read_unlock();
+ goto tx_err;
+ }
+
+ if (pctx->flags & PDP_F_PEER_V6)
+ err = gtp_xmit_ip6(skb, dev, pctx);
+ else
+ err = gtp_xmit_ip4(skb, dev, pctx);
rcu_read_unlock();
@@ -726,7 +937,7 @@ static const struct device_type gtp_type = {
static void gtp_link_setup(struct net_device *dev)
{
- unsigned int max_gtp_header_len = sizeof(struct iphdr) +
+ unsigned int max_gtp_header_len = sizeof(struct ipv6hdr) +
sizeof(struct udphdr) +
sizeof(struct gtp0_header);
@@ -1023,11 +1234,8 @@ static struct gtp_dev *gtp_find_dev(struct net *src_net, struct nlattr *nla[])
static void ipv4_pdp_fill(struct pdp_ctx *pctx, struct genl_info *info)
{
pctx->gtp_version = nla_get_u32(info->attrs[GTPA_VERSION]);
- pctx->af = AF_INET;
- pctx->peer_addr_ip4.s_addr =
- nla_get_be32(info->attrs[GTPA_PEER_ADDRESS]);
- pctx->ms_addr_ip4.s_addr =
- nla_get_be32(info->attrs[GTPA_MS_ADDRESS]);
+ ipv4(&pctx->peer_addr) = nla_get_be32(info->attrs[GTPA_PEER_ADDRESS]);
+ ipv4(&pctx->ms_addr) = nla_get_be32(info->attrs[GTPA_MS_ADDRESS]);
switch (pctx->gtp_version) {
case GTP_V0:
@@ -1127,13 +1335,13 @@ static struct pdp_ctx *gtp_pdp_add(struct gtp_dev *gtp, struct sock *sk,
switch (pctx->gtp_version) {
case GTP_V0:
netdev_dbg(dev, "GTPv0-U: new PDP ctx id=%llx ssgn=%pI4 ms=%pI4 (pdp=%p)\n",
- pctx->u.v0.tid, &pctx->peer_addr_ip4,
- &pctx->ms_addr_ip4, pctx);
+ pctx->u.v0.tid, &ipv4(&pctx->peer_addr),
+ &ipv4(&pctx->ms_addr), pctx);
break;
case GTP_V1:
netdev_dbg(dev, "GTPv1-U: new PDP ctx id=%x/%x ssgn=%pI4 ms=%pI4 (pdp=%p)\n",
pctx->u.v1.i_tei, pctx->u.v1.o_tei,
- &pctx->peer_addr_ip4, &pctx->ms_addr_ip4, pctx);
+ &ipv4(&pctx->peer_addr), &ipv4(&pctx->ms_addr), pctx);
break;
}
@@ -1315,8 +1523,8 @@ static int gtp_genl_fill_info(struct sk_buff *skb, u32 snd_portid, u32 snd_seq,
if (nla_put_u32(skb, GTPA_VERSION, pctx->gtp_version) ||
nla_put_u32(skb, GTPA_LINK, pctx->dev->ifindex) ||
- nla_put_be32(skb, GTPA_PEER_ADDRESS, pctx->peer_addr_ip4.s_addr) ||
- nla_put_be32(skb, GTPA_MS_ADDRESS, pctx->ms_addr_ip4.s_addr))
+ nla_put_be32(skb, GTPA_PEER_ADDRESS, ipv4(&pctx->peer_addr)) ||
+ nla_put_be32(skb, GTPA_MS_ADDRESS, ipv4(&pctx->ms_addr)))
goto nla_put_failure;
switch (pctx->gtp_version) {
--
2.27.0
Powered by blists - more mailing lists