|
|
|
@ -1,6 +1,6 @@ |
|
|
|
|
/* GTP according to GSM TS 09.60 / 3GPP TS 29.060 */ |
|
|
|
|
|
|
|
|
|
/* (C) 2012 by sysmocom - s.f.m.c. GmbH
|
|
|
|
|
/* (C) 2012-2014 by sysmocom - s.f.m.c. GmbH
|
|
|
|
|
* Author: Harald Welte <hwelte@sysmocom.de> |
|
|
|
|
* |
|
|
|
|
* This program is free software; you can redistribute it and/or |
|
|
|
@ -14,15 +14,21 @@ |
|
|
|
|
#include <linux/udp.h> |
|
|
|
|
#include <linux/rculist.h> |
|
|
|
|
#include <linux/jhash.h> |
|
|
|
|
#include <linux/if_tunnel.h> |
|
|
|
|
|
|
|
|
|
#include <net/protocol.h> |
|
|
|
|
#include <net/ip.h> |
|
|
|
|
#include <net/udp.h> |
|
|
|
|
#include <net/icmp.h> |
|
|
|
|
#include <net/xfrm.h> |
|
|
|
|
|
|
|
|
|
/* general GTP protocol related definitions */ |
|
|
|
|
|
|
|
|
|
#define GTP0_PORT 3386 |
|
|
|
|
#define GTP1U_PORT 2152 |
|
|
|
|
|
|
|
|
|
#define GTP_TPDU 255 |
|
|
|
|
|
|
|
|
|
struct gtp0_header { /* According to GSM TS 09.60 */ |
|
|
|
|
uint8_t flags; |
|
|
|
|
uint8_t type; |
|
|
|
@ -43,6 +49,9 @@ struct gtp1_header_short { /* According to 3GPP TS 29.060 */ |
|
|
|
|
|
|
|
|
|
/* implementation-specific definitions */ |
|
|
|
|
|
|
|
|
|
/* FIXME: initialize this !! */ |
|
|
|
|
static uint32_t gtp_h_initval; |
|
|
|
|
|
|
|
|
|
struct gsn { |
|
|
|
|
struct list_head list; |
|
|
|
|
}; |
|
|
|
@ -91,17 +100,17 @@ static inline uint32_t gtp0_hashfn(uint64_t tid) |
|
|
|
|
|
|
|
|
|
static inline uint32_t gtp1u_hashfn(uint32_t tei) |
|
|
|
|
{ |
|
|
|
|
return jhash_1words(tei, gtp_h_initval); |
|
|
|
|
return jhash_1word(tei, gtp_h_initval); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
static inline uint32_t ipv4_hashfn(uint32_t ip) |
|
|
|
|
{ |
|
|
|
|
return jhash_1words(ip, gtp_h_initval); |
|
|
|
|
return jhash_1word(ip, gtp_h_initval); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
static inline uint32_t ipv6_hashfn(struct in6_addr *ip6) |
|
|
|
|
{ |
|
|
|
|
return jhash2(&ip6->s6_addr32, sizeof(*ip6)/4, gtp_h_initval); |
|
|
|
|
return jhash2((const u32 *) &ip6->s6_addr32, sizeof(*ip6)/4, gtp_h_initval); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
@ -109,13 +118,12 @@ static inline uint32_t ipv6_hashfn(struct in6_addr *ip6) |
|
|
|
|
static struct pdp_ctx *gtp0_pdp_find(struct gtp_instance *gti, uint64_t tid) |
|
|
|
|
{ |
|
|
|
|
struct hlist_head *head; |
|
|
|
|
struct hlist_node *pos; |
|
|
|
|
struct pdp_ctx *pdp; |
|
|
|
|
|
|
|
|
|
head = >i->tei_hash[gtp0_hashfn(tid) % gti->hash_size]; |
|
|
|
|
|
|
|
|
|
hlist_for_each_entry_rcu(pdp, pos, head, hlist) { |
|
|
|
|
if (pdp->gtp_version == 0 && gtp->tid == tid) |
|
|
|
|
hlist_for_each_entry_rcu(pdp, head, hlist) { |
|
|
|
|
if (pdp->gtp_version == 0 && pdp->tid == tid) |
|
|
|
|
return pdp; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
@ -126,13 +134,12 @@ static struct pdp_ctx *gtp0_pdp_find(struct gtp_instance *gti, uint64_t tid) |
|
|
|
|
static struct pdp_ctx *gtp1_pdp_find(struct gtp_instance *gti, uint32_t tei) |
|
|
|
|
{ |
|
|
|
|
struct hlist_head *head; |
|
|
|
|
struct hlist_node *pos; |
|
|
|
|
struct pdp_ctx *pdp; |
|
|
|
|
|
|
|
|
|
head = >i->tei_hash[gtp1u_hashfn(tei) % gti->hash_size]; |
|
|
|
|
|
|
|
|
|
hlist_for_each_entry_rcu(pdp, pos, head, hlist) { |
|
|
|
|
if (pdp->gtp_version == 1 && gtp->tid == tei) |
|
|
|
|
hlist_for_each_entry_rcu(pdp, head, hlist) { |
|
|
|
|
if (pdp->gtp_version == 1 && pdp->tid == tei) |
|
|
|
|
return pdp; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
@ -144,13 +151,12 @@ static struct pdp_ctx *ipv4_pdp_find(struct gtp_instance *gti, |
|
|
|
|
uint32_t ms_addr) |
|
|
|
|
{ |
|
|
|
|
struct hlist_head *head; |
|
|
|
|
struct hlist_node *pos; |
|
|
|
|
struct pdp_ctx *pdp; |
|
|
|
|
|
|
|
|
|
head = >i->addr_hash[ipv4_hashfn(ms_addr) % gti->hash_size]; |
|
|
|
|
|
|
|
|
|
hlist_for_each_entry_rcu(pdp, pos, head, hlist) { |
|
|
|
|
if (pdp->af == AF_INET && gtp->ms_addr.ip4 == ms_addr) |
|
|
|
|
hlist_for_each_entry_rcu(pdp, head, hlist) { |
|
|
|
|
if (pdp->af == AF_INET && pdp->ms_addr.ip4 == ms_addr) |
|
|
|
|
return pdp; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
@ -162,14 +168,13 @@ static struct pdp_ctx *ipv6_pdp_find(struct gtp_instance *gti, |
|
|
|
|
struct in6_addr *ms_addr) |
|
|
|
|
{ |
|
|
|
|
struct hlist_head *head; |
|
|
|
|
struct hlist_node *pos; |
|
|
|
|
struct pdp_ctx *pdp; |
|
|
|
|
|
|
|
|
|
head = >i->addr_hash[ipv6_hashfn(ms_addr) % gti->hash_size]; |
|
|
|
|
|
|
|
|
|
hlist_for_each_entry_rcu(pdp, pos, head, hlist) { |
|
|
|
|
hlist_for_each_entry_rcu(pdp, head, hlist) { |
|
|
|
|
if (pdp->af == AF_INET6 && |
|
|
|
|
!memcmp(>p->ms_addr.ip6, ms_addr, sizeof(*ms_addr))) |
|
|
|
|
!memcmp(&pdp->ms_addr.ip6, ms_addr, sizeof(*ms_addr))) |
|
|
|
|
return pdp; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
@ -199,10 +204,11 @@ static inline struct gtp_instance *sk_to_gti(struct sock *sk) |
|
|
|
|
* Return codes: 0: succes, <0: error, >0: passed up to userspace UDP */ |
|
|
|
|
static int gtp0_udp_encap_recv(struct sock *sk, struct sk_buff *skb) |
|
|
|
|
{ |
|
|
|
|
struct gtp0_header *gtp0 = skb_transport_header(skb); |
|
|
|
|
struct gtp0_header *gtp0 = (struct gtp0_header *) skb_transport_header(skb); |
|
|
|
|
struct gtp_instance *gti; |
|
|
|
|
struct pdp_ctx *pctx; |
|
|
|
|
uint64_t tid; |
|
|
|
|
int rc; |
|
|
|
|
|
|
|
|
|
/* resolve the GTP instance to which the socket belongs */ |
|
|
|
|
gti = sk_to_gti(sk); |
|
|
|
@ -218,7 +224,7 @@ static int gtp0_udp_encap_recv(struct sock *sk, struct sk_buff *skb) |
|
|
|
|
goto drop_put; |
|
|
|
|
|
|
|
|
|
/* check if it is T-PDU. if not -> userspace */ |
|
|
|
|
if (gtp0->type != GTP_GPDU) |
|
|
|
|
if (gtp0->type != GTP_TPDU) |
|
|
|
|
goto user_put; |
|
|
|
|
|
|
|
|
|
/* look-up the PDP context for the Tunnel ID */ |
|
|
|
@ -257,13 +263,16 @@ user: |
|
|
|
|
|
|
|
|
|
/* UDP encapsulation receive handler. See net/ipv4/udp.c.
|
|
|
|
|
* Return codes: 0: succes, <0: error, >0: passed up to userspace UDP */ |
|
|
|
|
static int gtp1_udp_encap_recv(struct sock *sk, struct sk_buff *skb) |
|
|
|
|
static int gtp1u_udp_encap_recv(struct sock *sk, struct sk_buff *skb) |
|
|
|
|
{ |
|
|
|
|
struct gtp1_header_short *gtp1 = skb_transport_header(skb); |
|
|
|
|
struct gtp1_header_short *gtp1 = |
|
|
|
|
(struct gtp1_header_short *) skb_transport_header(skb); |
|
|
|
|
struct gtp0_header *gtp0 = (struct gtp0_header *) gtp1; |
|
|
|
|
struct gtp_instance *gti; |
|
|
|
|
struct pdp_ctx *pctx; |
|
|
|
|
unsigned int min_len = sizeof(*gtp1); |
|
|
|
|
uint64_t tid; |
|
|
|
|
int rc; |
|
|
|
|
|
|
|
|
|
/* resolve the GTP instance to which the socket belongs */ |
|
|
|
|
gti = sk_to_gti(sk); |
|
|
|
@ -294,7 +303,7 @@ static int gtp1_udp_encap_recv(struct sock *sk, struct sk_buff *skb) |
|
|
|
|
min_len += 1; |
|
|
|
|
|
|
|
|
|
/* check if it is T-PDU. */ |
|
|
|
|
if (gtp0->type != GTP_GPDU) |
|
|
|
|
if (gtp0->type != GTP_TPDU) |
|
|
|
|
goto drop_put; |
|
|
|
|
|
|
|
|
|
/* check for sufficient header size */ |
|
|
|
@ -346,19 +355,35 @@ static int gtp_dev_init(struct net_device *dev) |
|
|
|
|
return 0; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
static void gtp_dev_uninit(strut net_device *dev) |
|
|
|
|
static void gtp_dev_uninit(struct net_device *dev) |
|
|
|
|
{ |
|
|
|
|
dev_put(dev); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
#define IP_UDP_LEN (sizeof(struct iphdr) + sizeof(struct udphdr)) |
|
|
|
|
|
|
|
|
|
static struct rtable * |
|
|
|
|
ip4_route_output_gtp(struct net *net, struct flowi4 *fl4, |
|
|
|
|
__be32 daddr, __be32 saddr, __u8 tos, int oif) |
|
|
|
|
{ |
|
|
|
|
memset(fl4, 0, sizeof(*fl4)); |
|
|
|
|
fl4->flowi4_oif = oif; |
|
|
|
|
fl4->daddr = daddr; |
|
|
|
|
fl4->saddr = saddr; |
|
|
|
|
fl4->flowi4_tos = tos; |
|
|
|
|
fl4->flowi4_proto = IPPROTO_UDP; |
|
|
|
|
return ip_route_output_key(net, fl4); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
static netdev_tx_t gtp_dev_xmit(struct sk_buff *skb, struct net_device *dev) |
|
|
|
|
{ |
|
|
|
|
struct gtp_instance gti = netdev_priv(dev); |
|
|
|
|
struct pdp_context *pctx; |
|
|
|
|
struct gtp_instance *gti = netdev_priv(dev); |
|
|
|
|
struct pdp_ctx *pctx; |
|
|
|
|
struct pcup_tstats *tstats; |
|
|
|
|
struct iphdr *old_iph, *iph; |
|
|
|
|
struct udphdr *uh; |
|
|
|
|
unsigned int payload_len; |
|
|
|
|
int df, mtu; |
|
|
|
|
|
|
|
|
|
/* read the IP desination address and resolve the PDP context.
|
|
|
|
|
* Prepend PDP header with TEI/TID from PDP ctx */ |
|
|
|
@ -384,7 +409,7 @@ static netdev_tx_t gtp_dev_xmit(struct sk_buff *skb, struct net_device *dev) |
|
|
|
|
gtp0 = (struct gtp0_header *) skb_push(skb, sizeof(*gtp0)); |
|
|
|
|
|
|
|
|
|
gtp0->flags = 0; |
|
|
|
|
gtp0->type = GTP_GPDU; |
|
|
|
|
gtp0->type = GTP_TPDU; |
|
|
|
|
gtp0->length = payload_len; |
|
|
|
|
gtp0->seq = atomic_inc_return(&pctx->tx_seq) % 0xffff; |
|
|
|
|
gtp0->flow = pctx->flow; |
|
|
|
@ -400,7 +425,7 @@ static netdev_tx_t gtp_dev_xmit(struct sk_buff *skb, struct net_device *dev) |
|
|
|
|
gtp1u = (struct gtp1u_header *) skb_push(skb, sizeof(*gtp1u)); |
|
|
|
|
|
|
|
|
|
gtp1u->flags = (1 << 5) | 0x10; /* V1, GTP-non-prime */ |
|
|
|
|
gtp1u->type = GTP_GPDU; |
|
|
|
|
gtp1u->type = GTP_TPDU; |
|
|
|
|
gtp1u->length = payload_len; |
|
|
|
|
gtp1u->tei = pctx->tid; |
|
|
|
|
|
|
|
|
@ -436,18 +461,18 @@ static netdev_tx_t gtp_dev_xmit(struct sk_buff *skb, struct net_device *dev) |
|
|
|
|
|
|
|
|
|
if (tdev == dev) { |
|
|
|
|
ip_rt_put(rt); |
|
|
|
|
dev->stats.collissions++; |
|
|
|
|
dev->stats.collisions++; |
|
|
|
|
goto tx_error; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
df = gti->frag_off; |
|
|
|
|
df = old_iph->frag_off; |
|
|
|
|
if (df) |
|
|
|
|
mtu = dst_mtu(&rt->dst) - dev->hard_header_len - tunnel->hlen; |
|
|
|
|
else |
|
|
|
|
mtu = skb_dst(skb) ? dst_mtu(skb_dst(skb)) : dev->mtu; |
|
|
|
|
|
|
|
|
|
if (skb_dst(skb)) |
|
|
|
|
skb_dst(skb)->ops->update_pmtu(skb_dst(skb), mtu); |
|
|
|
|
skb_dst(skb)->ops->update_pmtu(skb_dst(skb), NULL, skb, mtu); |
|
|
|
|
|
|
|
|
|
if (skb->protocol == htons(ETH_P_IP)) { |
|
|
|
|
df |= (old_iph->frag_off & htons(IP_DF)); |
|
|
|
@ -497,19 +522,6 @@ tx_error: |
|
|
|
|
return NETDEV_TX_OK; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
static struct rtable * |
|
|
|
|
ip4_route_output_gtp(struct net *net, struct flowi4 *fl4, |
|
|
|
|
__be32 daddr, __be32 saddr, __u8 tos, int oif) |
|
|
|
|
{ |
|
|
|
|
memset(fl4, 0, sizeof(*fl4)); |
|
|
|
|
fl4->flowi4_oif = oif; |
|
|
|
|
fl4->daddr = daddr; |
|
|
|
|
fl4->saddr = saddr; |
|
|
|
|
fl4->flowi4_tos = tos; |
|
|
|
|
fl4->flowi4_proto = IPPROTO_UDP; |
|
|
|
|
return ip_route_output_key(net, fl4); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
static const struct net_device_ops gtp_netdev_ops = { |
|
|
|
|
.ndo_init = gtp_dev_init, |
|
|
|
|
.ndo_uninit = gtp_dev_uninit, |
|
|
|
@ -523,7 +535,7 @@ static const struct nla_policy gtp_link_policy[IFLA_GTP_MAX + 1] = { |
|
|
|
|
static void gtp_link_setup(struct net_device *dev) |
|
|
|
|
{ |
|
|
|
|
dev->netdev_ops = >p_netdev_ops; |
|
|
|
|
dev->descructor = gtp_dev_free; |
|
|
|
|
dev->destructor = gtp_dev_free; |
|
|
|
|
|
|
|
|
|
dev->type = FIXME; |
|
|
|
|
dev->needed_headroom = FIXME; |
|
|
|
@ -538,11 +550,11 @@ static void gtp_link_setup(struct net_device *dev) |
|
|
|
|
static void gtp_link_validate(struct nlattr *tb[], struct nlattr *data[]) |
|
|
|
|
{ |
|
|
|
|
/* FIXME */ |
|
|
|
|
return 0; |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
static int gtp_newlink(struct net *src_net, struct net_device *dev, |
|
|
|
|
struct nlattr *tb[]) |
|
|
|
|
struct nlattr *tb[], struct nlattr *data[]) |
|
|
|
|
{ |
|
|
|
|
int rc; |
|
|
|
|
|
|
|
|
@ -563,7 +575,7 @@ static int gtp_changelink(struct net_device *dev, struct nlattr *tb[], |
|
|
|
|
return 0; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
static int gtp_link_get_size(const struct net_device *dev) |
|
|
|
|
static size_t gtp_link_get_size(const struct net_device *dev) |
|
|
|
|
{ |
|
|
|
|
return 0; /* FIXME */ |
|
|
|
|
} |
|
|
|
@ -594,7 +606,7 @@ static int gtp_create_bind_sock(struct gtp_instance *gti) |
|
|
|
|
{ |
|
|
|
|
int rc; |
|
|
|
|
struct sockaddr_in sin; |
|
|
|
|
struct sk *sk; |
|
|
|
|
struct sock *sk; |
|
|
|
|
|
|
|
|
|
/* Create and bind the socket for GTP0 */ |
|
|
|
|
rc = sock_create(AF_INET, SOCK_DGRAM, IPPROTO_UDP, >i->sock0); |
|
|
|
@ -608,9 +620,9 @@ static int gtp_create_bind_sock(struct gtp_instance *gti) |
|
|
|
|
if (rc < 0) |
|
|
|
|
goto out; |
|
|
|
|
|
|
|
|
|
sk = s->sock0->sk; |
|
|
|
|
sk = gti->sock0->sk; |
|
|
|
|
udp_sk(sk)->encap_type = UDP_ENCAP_GTP0; |
|
|
|
|
udp_sk(sk)->encap_rcv = gtp0_udp_encap_rcv; |
|
|
|
|
udp_sk(sk)->encap_rcv = gtp0_udp_encap_recv; |
|
|
|
|
udp_encap_enable(); |
|
|
|
|
|
|
|
|
|
/* Create and bind the socket for GTP1 user-plane */ |
|
|
|
@ -625,16 +637,16 @@ static int gtp_create_bind_sock(struct gtp_instance *gti) |
|
|
|
|
if (rc < 0) |
|
|
|
|
goto out_free1; |
|
|
|
|
|
|
|
|
|
sk = s->sock1u->sk; |
|
|
|
|
sk = gti->sock1u->sk; |
|
|
|
|
udp_sk(sk)->encap_type = UDP_ENCAP_GTP1U; |
|
|
|
|
udp_sk(sk)->encap_rcv = gtp1u_udp_encap_rcv; |
|
|
|
|
udp_sk(sk)->encap_rcv = gtp1u_udp_encap_recv; |
|
|
|
|
|
|
|
|
|
return 0; |
|
|
|
|
|
|
|
|
|
out_free1: |
|
|
|
|
sock_release(&s->sock1u); |
|
|
|
|
sock_release(gti->sock1u); |
|
|
|
|
out_free0: |
|
|
|
|
sock_release(&s->sock0); |
|
|
|
|
sock_release(gti->sock0); |
|
|
|
|
out: |
|
|
|
|
return rc; |
|
|
|
|
} |
|
|
|
@ -646,6 +658,8 @@ static int __init gtp_init(void) |
|
|
|
|
rc = rtnl_link_register(>p_link_ops); |
|
|
|
|
if (rc < 0) |
|
|
|
|
return rc; |
|
|
|
|
|
|
|
|
|
return 0; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
static void __exit gtp_fini(void) |
|
|
|
|