Hi,
Here are the not yet merged pieces of the kernel GTP support. I tried to address all the comment from the previous rounds.
After this series has been applied, IPv4 GTP tunnels over IPv4 should be complete. Anything IPv6 still needs work.
Regrads Andreas --
Andreas Schultz (12): gtp: select netns based on NL attribute gtp-rtnl: and netns support gtp: add socket destroy handler gtp: replace udp encap setup with setup_udp_tunnel_sock gtp: switch to iptunnel framework and no longer depend on real_dev gtp: Split TID handling for GTPv0 and GTPv1 gtp-rtnl: sync GTPA_FLOW nl attribute name from kernel to userspace gtp-rtnl: real_ifname is not long needed, remove it gtp-rtnl: Split TID handling for GTPv0 and GTPv1 gtp: get started with IPv6 support list Andreas Schultz as author gtp: add support for replacing PDP contexts
gtp.c | 669 +++++++++++++++++++++++++-------------- gtp_nl.h | 3 + libgtnl/AUTHORS | 1 + libgtnl/include/libgtpnl/gtp.h | 6 + libgtnl/include/libgtpnl/gtpnl.h | 3 +- libgtnl/include/linux/gtp_nl.h | 5 +- libgtnl/src/gtp-genl.c | 36 ++- libgtnl/src/gtp-rtnl.c | 6 +- libgtnl/src/gtp.c | 44 ++- libgtnl/src/internal.h | 13 +- libgtnl/src/libgtpnl.map | 6 + libgtnl/tools/Makefile.am | 4 +- libgtnl/tools/gtp-link-add.c | 5 +- libgtnl/tools/gtp-tunnel.c | 134 +++++--- libgtnl/tools/gtpnl.c | 35 -- 15 files changed, 642 insertions(+), 328 deletions(-) delete mode 100644 libgtnl/tools/gtpnl.c
This permits a split namespace setup where the GTP transport sockets are in one namespace the gtp tunnel interface is in another namespace.
The target namespece is selected by the new GTPA_NET_NS_FD NL attributes. It fall back to the netns of the GTP-U sockets if the NL attr is not present.
Signed-off-by: Andreas Schultz aschultz@tpip.net --- gtp.c | 35 +++++++++++++++++++++++++++++++---- gtp_nl.h | 1 + 2 files changed, 32 insertions(+), 4 deletions(-)
diff --git a/gtp.c b/gtp.c index cbe2da1..cd41aea 100644 --- a/gtp.c +++ b/gtp.c @@ -740,7 +740,7 @@ static int gtp_encap_enable(struct net_device *dev, struct gtp_instance *gti, static int gtp_newlink(struct net *src_net, struct net_device *dev, struct nlattr *tb[], struct nlattr *data[]) { - struct gtp_net *gn = net_generic(src_net, gtp_net_id); + struct gtp_net *gn; struct net_device *real_dev; struct gtp_instance *gti; int hashsize, err, fd0, fd1; @@ -780,6 +780,7 @@ static int gtp_newlink(struct net *src_net, struct net_device *dev, if (err < 0) goto err1;
+ gn = net_generic(dev_net(dev), gtp_net_id); list_add_rcu(>i->list, &gn->gtp_instance_list);
netdev_dbg(dev, "registered new interface\n"); @@ -847,6 +848,19 @@ static struct rtnl_link_ops gtp_link_ops __read_mostly = { .fill_info = gtp_fill_info, };
+static struct net *gtp_genl_get_net(struct net *src_net, struct nlattr *tb[]) +{ + struct net *net; + /* Examine the link attributes and figure out which + * network namespace we are talking about. + */ + if (tb[GTPA_NET_NS_FD]) + net = get_net_ns_by_fd(nla_get_u32(tb[GTPA_NET_NS_FD])); + else + net = get_net(src_net); + return net; +} + static int gtp_hashtable_new(struct gtp_instance *gti, int hsize) { int i; @@ -1065,7 +1079,7 @@ static int ipv4_pdp_add(struct net_device *dev, struct genl_info *info)
static int gtp_genl_tunnel_new(struct sk_buff *skb, struct genl_info *info) { - struct net *net = sock_net(skb->sk); + struct net *net; struct net_device *dev;
if (!info->attrs[GTPA_VERSION] || @@ -1075,6 +1089,10 @@ static int gtp_genl_tunnel_new(struct sk_buff *skb, struct genl_info *info) !info->attrs[GTPA_TID]) return -EINVAL;
+ net = gtp_genl_get_net(sock_net(skb->sk), info->attrs); + if (IS_ERR(net)) + return PTR_ERR(net); + /* Check if there's an existing gtpX device to configure */ dev = gtp_find_dev(net, nla_get_u32(info->attrs[GTPA_LINK])); if (dev == NULL) @@ -1085,7 +1103,7 @@ static int gtp_genl_tunnel_new(struct sk_buff *skb, struct genl_info *info)
static int gtp_genl_tunnel_delete(struct sk_buff *skb, struct genl_info *info) { - struct net *net = sock_net(skb->sk); + struct net *net; struct gtp_instance *gti; struct net_device *dev; struct pdp_ctx *pctx; @@ -1113,6 +1131,10 @@ static int gtp_genl_tunnel_delete(struct sk_buff *skb, struct genl_info *info) if (gtp_version == GTP_V1 && tid > UINT_MAX) return -EINVAL;
+ net = gtp_genl_get_net(sock_net(skb->sk), info->attrs); + if (IS_ERR(net)) + return PTR_ERR(net); + /* Check if there's an existing gtpX device to configure */ dev = gtp_find_dev(net, nla_get_u32(info->attrs[GTPA_LINK])); if (dev == NULL) @@ -1181,7 +1203,7 @@ nla_put_failure:
static int gtp_genl_tunnel_get(struct sk_buff *skb, struct genl_info *info) { - struct net *net = sock_net(skb->sk); + struct net *net; struct net_device *dev; struct gtp_instance *gti; struct pdp_ctx *pctx = NULL; @@ -1202,6 +1224,10 @@ static int gtp_genl_tunnel_get(struct sk_buff *skb, struct genl_info *info) return -EINVAL; }
+ net = gtp_genl_get_net(sock_net(skb->sk), info->attrs); + if (IS_ERR(net)) + return PTR_ERR(net); + /* Check if there's an existing gtpX device to configure */ dev = gtp_find_dev(net, nla_get_u32(info->attrs[GTPA_LINK])); if (dev == NULL) @@ -1310,6 +1336,7 @@ static struct nla_policy gtp_genl_policy[GTPA_MAX + 1] = { [GTPA_SGSN_ADDRESS] = { .type = NLA_NESTED, }, [GTPA_MS_ADDRESS] = { .type = NLA_NESTED, }, [GTPA_FLOW] = { .type = NLA_U16, }, + [GTPA_NET_NS_FD] = { .type = NLA_U32, }, };
static const struct genl_ops gtp_genl_ops[] = { diff --git a/gtp_nl.h b/gtp_nl.h index 7bdd2f5..c20666d 100644 --- a/gtp_nl.h +++ b/gtp_nl.h @@ -40,6 +40,7 @@ enum gtp_attrs { GTPA_SGSN_ADDRESS, GTPA_MS_ADDRESS, GTPA_FLOW, + GTPA_NET_NS_FD, __GTPA_MAX, }; #define GTPA_MAX (__GTPA_MAX + 1)
Signed-off-by: Andreas Schultz aschultz@tpip.net --- libgtnl/include/libgtpnl/gtp.h | 2 ++ libgtnl/include/libgtpnl/gtpnl.h | 2 +- libgtnl/include/linux/gtp_nl.h | 1 + libgtnl/src/gtp-genl.c | 2 ++ libgtnl/src/gtp-rtnl.c | 4 +++- libgtnl/src/gtp.c | 12 ++++++++++++ libgtnl/src/internal.h | 1 + libgtnl/src/libgtpnl.map | 2 ++ 8 files changed, 24 insertions(+), 2 deletions(-)
diff --git a/libgtnl/include/libgtpnl/gtp.h b/libgtnl/include/libgtpnl/gtp.h index 10b34d5..fa09d2a 100644 --- a/libgtnl/include/libgtpnl/gtp.h +++ b/libgtnl/include/libgtpnl/gtp.h @@ -8,6 +8,7 @@ struct gtp_tunnel; struct gtp_tunnel *gtp_tunnel_alloc(void); void gtp_tunnel_free(struct gtp_tunnel *t);
+void gtp_tunnel_set_ifns(struct gtp_tunnel *t, int ifns); void gtp_tunnel_set_ifidx(struct gtp_tunnel *t, uint32_t ifidx); void gtp_tunnel_set_ms_ip4(struct gtp_tunnel *t, struct in_addr *ms_addr); void gtp_tunnel_set_sgsn_ip4(struct gtp_tunnel *t, struct in_addr *sgsn_addr); @@ -15,6 +16,7 @@ void gtp_tunnel_set_version(struct gtp_tunnel *t, uint32_t version); void gtp_tunnel_set_tid(struct gtp_tunnel *t, uint64_t tid); void gtp_tunnel_set_flowid(struct gtp_tunnel *t, uint16_t flowid);
+const int gtp_tunnel_get_ifns(struct gtp_tunnel *t); const uint32_t gtp_tunnel_get_ifidx(struct gtp_tunnel *t); const struct in_addr *gtp_tunnel_get_ms_ip4(struct gtp_tunnel *t); const struct in_addr *gtp_tunnel_get_sgsn_ip4(struct gtp_tunnel *t); diff --git a/libgtnl/include/libgtpnl/gtpnl.h b/libgtnl/include/libgtpnl/gtpnl.h index c4faf6c..3d3fd73 100644 --- a/libgtnl/include/libgtpnl/gtpnl.h +++ b/libgtnl/include/libgtpnl/gtpnl.h @@ -16,7 +16,7 @@ int genl_lookup_family(struct mnl_socket *nl, const char *family);
struct in_addr;
-int gtp_dev_create(const char *gtp_ifname, const char *real_ifname, +int gtp_dev_create(int dest_ns, const char *gtp_ifname, const char *real_ifname, int fd0, int fd1); int gtp_dev_config(const char *iface, struct in_addr *net, uint32_t prefix); int gtp_dev_destroy(const char *gtp_ifname); diff --git a/libgtnl/include/linux/gtp_nl.h b/libgtnl/include/linux/gtp_nl.h index 0a28046..a8fdf3a 100644 --- a/libgtnl/include/linux/gtp_nl.h +++ b/libgtnl/include/linux/gtp_nl.h @@ -40,6 +40,7 @@ enum gtp_attrs { GTPA_SGSN_ADDRESS, GTPA_MS_ADDRESS, GTPA_FLOWID, /* only for GTPv0 */ + GTPA_NET_NS_FD, __GTPA_MAX, }; #define GTPA_MAX (__GTPA_MAX + 1) diff --git a/libgtnl/src/gtp-genl.c b/libgtnl/src/gtp-genl.c index c1f60ab..bffa7a5 100644 --- a/libgtnl/src/gtp-genl.c +++ b/libgtnl/src/gtp-genl.c @@ -44,6 +44,8 @@ static void gtp_build_payload(struct nlmsghdr *nlh, struct gtp_tunnel *t) { mnl_attr_put_u32(nlh, GTPA_VERSION, t->gtp_version); + if (t->ifns >= 0) + mnl_attr_put_u32(nlh, GTPA_NET_NS_FD, t->ifns); mnl_attr_put_u32(nlh, GTPA_LINK, t->ifidx); mnl_attr_put_u32(nlh, GTPA_SGSN_ADDRESS, t->sgsn_addr.s_addr); mnl_attr_put_u32(nlh, GTPA_MS_ADDRESS, t->ms_addr.s_addr); diff --git a/libgtnl/src/gtp-rtnl.c b/libgtnl/src/gtp-rtnl.c index 22b9430..76e2021 100644 --- a/libgtnl/src/gtp-rtnl.c +++ b/libgtnl/src/gtp-rtnl.c @@ -104,7 +104,7 @@ static int gtp_dev_talk(struct nlmsghdr *nlh, uint32_t seq) return ret; }
-int gtp_dev_create(const char *gtp_ifname, const char *real_ifname, +int gtp_dev_create(int dest_ns, const char *gtp_ifname, const char *real_ifname, int fd0, int fd1) { char buf[MNL_SOCKET_BUFFER_SIZE]; @@ -120,6 +120,8 @@ int gtp_dev_create(const char *gtp_ifname, const char *real_ifname, ifm->ifi_change |= IFF_UP; ifm->ifi_flags |= IFF_UP;
+ if (dest_ns >= 0) + mnl_attr_put_u32(nlh, IFLA_NET_NS_FD, dest_ns); mnl_attr_put_u32(nlh, IFLA_LINK, if_nametoindex(real_ifname)); mnl_attr_put_str(nlh, IFLA_IFNAME, gtp_ifname); nest = mnl_attr_nest_start(nlh, IFLA_LINKINFO); diff --git a/libgtnl/src/gtp.c b/libgtnl/src/gtp.c index 4534091..6e3d473 100644 --- a/libgtnl/src/gtp.c +++ b/libgtnl/src/gtp.c @@ -39,6 +39,12 @@ void gtp_tunnel_free(struct gtp_tunnel *t) } EXPORT_SYMBOL(gtp_tunnel_free);
+void gtp_tunnel_set_ifns(struct gtp_tunnel *t, int ifns) +{ + t->ifns = ifns; +} +EXPORT_SYMBOL(gtp_tunnel_set_ifns); + void gtp_tunnel_set_ifidx(struct gtp_tunnel *t, uint32_t ifidx) { t->ifidx = ifidx; @@ -75,6 +81,12 @@ void gtp_tunnel_set_flowid(struct gtp_tunnel *t, uint16_t flowid) } EXPORT_SYMBOL(gtp_tunnel_set_flowid);
+const int gtp_tunnel_get_ifns(struct gtp_tunnel *t) +{ + return t->ifns; +} +EXPORT_SYMBOL(gtp_tunnel_get_ifns); + const uint32_t gtp_tunnel_get_ifidx(struct gtp_tunnel *t) { return t->ifidx; diff --git a/libgtnl/src/internal.h b/libgtnl/src/internal.h index 75b3954..68f0135 100644 --- a/libgtnl/src/internal.h +++ b/libgtnl/src/internal.h @@ -13,6 +13,7 @@ #include <netinet/in.h>
struct gtp_tunnel { + int ifns; uint32_t ifidx; struct in_addr ms_addr; struct in_addr sgsn_addr; diff --git a/libgtnl/src/libgtpnl.map b/libgtnl/src/libgtpnl.map index 6e69ef8..2368467 100644 --- a/libgtnl/src/libgtpnl.map +++ b/libgtnl/src/libgtpnl.map @@ -15,12 +15,14 @@ global:
gtp_tunnel_alloc; gtp_tunnel_free; + gtp_tunnel_set_ifns; gtp_tunnel_set_ifidx; gtp_tunnel_set_ms_ip4; gtp_tunnel_set_sgsn_ip4; gtp_tunnel_set_version; gtp_tunnel_set_tid; gtp_tunnel_set_flowid; + gtp_tunnel_get_ifns; gtp_tunnel_get_ifidx; gtp_tunnel_get_ms_ip4; gtp_tunnel_get_sgsn_ip4;
Add a socket destroy handler and use it to detach and release all resources from an UDP socket.
Do not longer pin the socket with a reference. This together with the release handler allows user space to close the socket from underneath us. The destroy handler will handle the rest.
Signed-off-by: Andreas Schultz aschultz@tpip.net --- gtp.c | 34 +++++++++++++++++++++++++++------- 1 file changed, 27 insertions(+), 7 deletions(-)
diff --git a/gtp.c b/gtp.c index cd41aea..280e93b 100644 --- a/gtp.c +++ b/gtp.c @@ -80,6 +80,8 @@ struct gtp_net { struct list_head gtp_instance_list; };
+static void gtp_encap_disable(struct gtp_instance *gti); + static inline u32 gtp0_hashfn(u64 tid) { u32 *tid32 = (u32 *) &tid; @@ -317,6 +319,16 @@ out_rcu: return ret; }
+static void gtp_udp_encap_destroy(struct sock *sk) +{ + struct gtp_instance *gti = sk_to_gti(sk); + + if (gti) { + gtp_encap_disable(gti); + sock_put(sk); + } +} + /* UDP encapsulation receive handler. See net/ipv4/udp.c. * Return codes: 0: success, <0: error, >0: passed up to userspace UDP. */ @@ -401,8 +413,6 @@ static int gtp_dev_init(struct net_device *dev) return 0; }
-static void gtp_encap_disable(struct gtp_instance *gti); - static void gtp_dev_uninit(struct net_device *dev) { struct gtp_instance *gti = netdev_priv(dev); @@ -944,15 +954,18 @@ static int gtp_encap_enable(struct net_device *dev, struct gtp_instance *gti, sk = gti->sock0->sk; udp_sk(sk)->encap_type = UDP_ENCAP_GTP0; udp_sk(sk)->encap_rcv = gtp_udp_encap_recv; + udp_sk(sk)->encap_destroy = gtp_udp_encap_destroy; sk->sk_user_data = gti; udp_encap_enable();
sk = gti->sock1u->sk; udp_sk(sk)->encap_type = UDP_ENCAP_GTP1U; udp_sk(sk)->encap_rcv = gtp_udp_encap_recv; + udp_sk(sk)->encap_destroy = gtp_udp_encap_destroy; sk->sk_user_data = gti;
- return 0; + err = 0; + err2: sockfd_put(sock1u); err1: @@ -962,10 +975,17 @@ err1:
static void gtp_encap_disable(struct gtp_instance *gti) { - if (gti->sock1u) - sockfd_put(gti->sock1u); - if (gti->sock0) - sockfd_put(gti->sock0); + if (gti->sock0 && gti->sock0->sk) { + udp_sk(gti->sock0->sk)->encap_type = 0; + rcu_assign_sk_user_data(gti->sock0->sk, NULL); + } + if (gti->sock1u && gti->sock1u->sk) { + udp_sk(gti->sock1u->sk)->encap_type = 0; + rcu_assign_sk_user_data(gti->sock1u->sk, NULL); + } + + gti->sock0 = NULL; + gti->sock1u = NULL; }
static struct net_device *gtp_find_dev(struct net *net, int ifindex)
setup_udp_tunnel_sock() is doing exactly the same as we did before. So instead of replicating all that, simply use it instead.
Signed-off-by: Andreas Schultz aschultz@tpip.net --- gtp.c | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-)
diff --git a/gtp.c b/gtp.c index 280e93b..4ab2b01 100644 --- a/gtp.c +++ b/gtp.c @@ -24,6 +24,7 @@ #include <net/protocol.h> #include <net/ip.h> #include <net/udp.h> +#include <net/udp_tunnel.h> #include <net/icmp.h> #include <net/xfrm.h> #include <net/genetlink.h> @@ -917,7 +918,7 @@ static int gtp_encap_enable(struct net_device *dev, struct gtp_instance *gti, { int err; struct socket *sock0, *sock1u; - struct sock *sk; + struct udp_tunnel_sock_cfg tuncfg = {NULL};
netdev_dbg(dev, "enable gtp on %d, %d\n", fd_gtp0, fd_gtp1);
@@ -951,18 +952,15 @@ static int gtp_encap_enable(struct net_device *dev, struct gtp_instance *gti, gti->sock0 = sock0; gti->sock1u = sock1u;
- sk = gti->sock0->sk; - udp_sk(sk)->encap_type = UDP_ENCAP_GTP0; - udp_sk(sk)->encap_rcv = gtp_udp_encap_recv; - udp_sk(sk)->encap_destroy = gtp_udp_encap_destroy; - sk->sk_user_data = gti; - udp_encap_enable(); - - sk = gti->sock1u->sk; - udp_sk(sk)->encap_type = UDP_ENCAP_GTP1U; - udp_sk(sk)->encap_rcv = gtp_udp_encap_recv; - udp_sk(sk)->encap_destroy = gtp_udp_encap_destroy; - sk->sk_user_data = gti; + tuncfg.sk_user_data = gti; + tuncfg.encap_rcv = gtp_udp_encap_recv; + tuncfg.encap_destroy = gtp_udp_encap_destroy; + + tuncfg.encap_type = UDP_ENCAP_GTP0; + setup_udp_tunnel_sock(sock_net(gti->sock0->sk), gti->sock0, &tuncfg); + + tuncfg.encap_type = UDP_ENCAP_GTP1U; + setup_udp_tunnel_sock(sock_net(gti->sock1u->sk), gti->sock1u, &tuncfg);
err = 0;
Send tunnel data on GTP-U socket insead of raw interface and use existing iptunnel helpers for that.
Signed-off-by: Andreas Schultz aschultz@tpip.net --- gtp.c | 241 ++++++++++++++++++++++++++++++++++++------------------------------ 1 file changed, 132 insertions(+), 109 deletions(-)
diff --git a/gtp.c b/gtp.c index 4ab2b01..bead51f 100644 --- a/gtp.c +++ b/gtp.c @@ -68,7 +68,6 @@ struct gtp_instance { struct socket *sock1u;
struct net_device *dev; - struct net_device *real_dev;
unsigned int hash_size; struct hlist_head *tid_hash; @@ -404,7 +403,6 @@ static int gtp_dev_init(struct net_device *dev) { struct gtp_instance *gti = netdev_priv(dev);
- dev->flags = IFF_NOARP; gti->dev = dev;
dev->tstats = alloc_percpu(struct pcpu_sw_netstats); @@ -424,26 +422,34 @@ static void gtp_dev_uninit(struct net_device *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) +static inline void init_gtp_flow(struct flowi4 *fl4, + const struct sock *sk, + __be32 daddr) { memset(fl4, 0, sizeof(*fl4)); - fl4->flowi4_oif = oif; + fl4->flowi4_oif = sk->sk_bound_dev_if; fl4->daddr = daddr; - fl4->saddr = saddr; - fl4->flowi4_tos = tos; - fl4->flowi4_proto = IPPROTO_UDP; + fl4->saddr = inet_sk(sk)->inet_saddr; + fl4->flowi4_tos = RT_CONN_FLAGS(sk); + fl4->flowi4_proto = sk->sk_protocol; +} + +static struct rtable * +ip4_route_output_gtp(struct net *net, struct flowi4 *fl4, + const struct sock *sk, + __be32 daddr) +{ + init_gtp_flow(fl4, sk, daddr); return ip_route_output_key(net, fl4); }
static inline void -gtp0_push_header(struct sk_buff *skb, struct pdp_ctx *pctx, int payload_len) +gtp0_push_header(struct sk_buff *skb, struct pdp_ctx *pctx) { struct gtp0_header *gtp0; + int payload_len = skb->len;
/* ensure there is sufficient headroom */ - skb_cow(skb, sizeof(*gtp0) + IP_UDP_LEN); gtp0 = (struct gtp0_header *) skb_push(skb, sizeof(*gtp0));
gtp0->flags = 0x1e; /* V0, GTP-non-prime */ @@ -457,12 +463,12 @@ gtp0_push_header(struct sk_buff *skb, struct pdp_ctx *pctx, int payload_len) }
static inline void -gtp1_push_header(struct sk_buff *skb, struct pdp_ctx *pctx, int payload_len) +gtp1_push_header(struct sk_buff *skb, struct pdp_ctx *pctx) { struct gtp1_header *gtp1; + int payload_len = skb->len;
/* ensure there is sufficient headroom */ - skb_cow(skb, sizeof(*gtp1) + IP_UDP_LEN); gtp1 = (struct gtp1_header *) skb_push(skb, sizeof(*gtp1));
/* Bits 8 7 6 5 4 3 2 1 @@ -502,6 +508,7 @@ gtp_iptunnel_xmit_stats(int err, struct net_device_stats *err_stats, }
struct gtp_pktinfo { + struct sock *sk; union { struct iphdr *iph; struct ipv6hdr *ip6h; @@ -515,10 +522,12 @@ struct gtp_pktinfo { };
static inline void -gtp_set_pktinfo_ipv4(struct gtp_pktinfo *pktinfo, struct iphdr *iph, +gtp_set_pktinfo_ipv4(struct gtp_pktinfo *pktinfo, struct sock *sk, + struct iphdr *iph, struct pdp_ctx *pctx, struct rtable *rt, struct flowi4 *fl4, struct net_device *dev) { + pktinfo->sk = sk; pktinfo->iph = iph; pktinfo->pctx = pctx; pktinfo->rt = rt; @@ -530,7 +539,7 @@ static int gtp_ip4_prepare_xmit(struct sk_buff *skb, struct net_device *dev, struct gtp_pktinfo *pktinfo) { struct gtp_instance *gti = netdev_priv(dev); - struct inet_sock *inet = inet_sk(gti->sock0->sk); + struct sock *sk; struct iphdr *iph; struct pdp_ctx *pctx; struct rtable *rt; @@ -549,14 +558,23 @@ static int gtp_ip4_prepare_xmit(struct sk_buff *skb, struct net_device *dev, netdev_dbg(dev, "found PDP context %p\n", pctx);
/* Obtain route for the new encapsulated GTP packet */ - rt = ip4_route_output_gtp(dev_net(dev), &fl4, - pctx->sgsn_addr.ip4.s_addr, - inet->inet_saddr, 0, - gti->real_dev->ifindex); + switch (pctx->gtp_version) { + case GTP_V0: + sk = gti->sock0->sk; + break; + case GTP_V1: + sk = gti->sock1u->sk; + break; + default: + return -ENOENT; + } + + rt = ip4_route_output_gtp(sock_net(sk), &fl4, + gti->sock0->sk, + pctx->sgsn_addr.ip4.s_addr); if (IS_ERR(rt)) { - netdev_dbg(dev, "no route to SSGN %pI4 from ifidx=%d\n", - &pctx->sgsn_addr.ip4.s_addr, - gti->real_dev->ifindex); + netdev_dbg(dev, "no route to SSGN %pI4\n", + &pctx->sgsn_addr.ip4.s_addr); dev->stats.tx_carrier_errors++; goto err; } @@ -570,12 +588,11 @@ static int gtp_ip4_prepare_xmit(struct sk_buff *skb, struct net_device *dev, }
skb_dst_drop(skb); - skb_dst_set(skb, &rt->dst);
/* This is similar to tnl_update_pmtu() */ df = iph->frag_off; if (df) { - mtu = dst_mtu(&rt->dst) - gti->real_dev->hard_header_len - + mtu = dst_mtu(&rt->dst) - dev->hard_header_len - sizeof(struct iphdr) - sizeof(struct udphdr); switch (pctx->gtp_version) { case GTP_V0: @@ -586,10 +603,9 @@ static int gtp_ip4_prepare_xmit(struct sk_buff *skb, struct net_device *dev, break; } } else - mtu = skb_dst(skb) ? dst_mtu(skb_dst(skb)) : dev->mtu; + mtu = dst_mtu(&rt->dst);
- if (skb_dst(skb)) - skb_dst(skb)->ops->update_pmtu(skb_dst(skb), NULL, skb, mtu); + rt->dst.ops->update_pmtu(&rt->dst, NULL, skb, mtu);
if (!skb_is_gso(skb) && (iph->frag_off & htons(IP_DF)) && mtu < ntohs(iph->tot_len)) { @@ -600,7 +616,7 @@ static int gtp_ip4_prepare_xmit(struct sk_buff *skb, struct net_device *dev, goto err_rt; }
- gtp_set_pktinfo_ipv4(pktinfo, iph, pctx, rt, &fl4, dev); + gtp_set_pktinfo_ipv4(pktinfo, sk, iph, pctx, rt, &fl4, dev);
return 0; err_rt: @@ -616,34 +632,23 @@ static int gtp_ip6_prepare_xmit(struct sk_buff *skb, struct net_device *dev, return 0; }
-static inline void -gtp_push_ip4hdr(struct sk_buff *skb, struct gtp_pktinfo *pktinfo) +static inline int +gtp_udp_tunnel_xmit(struct sk_buff *skb, __be16 port, + struct gtp_pktinfo *pktinfo) { - struct iphdr *iph; - - /* Push down and install the IP header. Similar to iptunnel_xmit() */ - skb_push(skb, sizeof(struct iphdr)); - skb_reset_network_header(skb); - - iph = ip_hdr(skb); - - iph->version = 4; - iph->ihl = sizeof(struct iphdr) >> 2; - iph->frag_off = htons(IP_DF); - iph->protocol = IPPROTO_UDP; - iph->tos = pktinfo->iph->tos; - iph->daddr = pktinfo->fl4.daddr; - iph->saddr = pktinfo->fl4.saddr; - iph->ttl = ip4_dst_hoplimit(&pktinfo->rt->dst); - __ip_select_ident(dev_net(pktinfo->rt->dst.dev), iph, - (skb_shinfo(skb)->gso_segs ?: 1) - 1); - netdev_dbg(pktinfo->dev, "gtp -> IP src: %pI4 dst: %pI4\n", - &iph->saddr, &iph->daddr); + &pktinfo->iph->saddr, &pktinfo->iph->daddr); + + return udp_tunnel_xmit_skb(pktinfo->rt, pktinfo->sk, skb, + pktinfo->fl4.saddr, + pktinfo->fl4.daddr, + pktinfo->iph->tos, + ip4_dst_hoplimit(&pktinfo->rt->dst), + htons(IP_DF), port, port, true, false); }
-static inline void -gtp_push_ip6hdr(struct sk_buff *skb, struct gtp_pktinfo *pktinfo) +static inline int +gtp_ip6tunnel_xmit(struct sk_buff *skb, struct gtp_pktinfo *pktinfo) { /* TODO IPV6 support */ } @@ -654,9 +659,17 @@ static netdev_tx_t gtp_dev_xmit(struct sk_buff *skb, struct net_device *dev) unsigned int payload_len; struct gtp_pktinfo pktinfo; unsigned int proto = ntohs(skb->protocol); - int gtph_len, err; + int gtph_len, err = -EINVAL; + __be16 gtph_port;
rcu_read_lock(); + + /* ensure there is sufficient headroom */ + if (skb_cow_head(skb, dev->needed_headroom)) + goto tx_error; + + skb_reset_inner_headers(skb); + switch (proto) { case ETH_P_IP: err = gtp_ip4_prepare_xmit(skb, dev, &pktinfo); @@ -669,56 +682,55 @@ static netdev_tx_t gtp_dev_xmit(struct sk_buff *skb, struct net_device *dev) if (err < 0) goto tx_error;
- /* Annotate length of the encapsulated packet */ - payload_len = skb->len; - /* Push down GTP header */ switch (pktinfo.pctx->gtp_version) { case GTP_V0: - gtp0_push_header(skb, pktinfo.pctx, payload_len); - break; - case GTP_V1: - gtp1_push_header(skb, pktinfo.pctx, payload_len); - break; - } - - /* Push down and install the UDP header. */ - skb_push(skb, sizeof(struct udphdr)); - skb_reset_transport_header(skb); - - uh = udp_hdr(skb); - switch (pktinfo.pctx->gtp_version) { - case GTP_V0: - uh->source = uh->dest = htons(GTP0_PORT); + gtph_port = htons(GTP0_PORT); gtph_len = sizeof(struct gtp0_header); + + gtp0_push_header(skb, pktinfo.pctx); break; case GTP_V1: - uh->source = uh->dest = htons(GTP1U_PORT); + gtph_port = htons(GTP1U_PORT); gtph_len = sizeof(struct gtp1_header); + + gtp1_push_header(skb, pktinfo.pctx); break; + default: + goto tx_error; }
- uh->len = htons(sizeof(struct udphdr) + payload_len + gtph_len); - uh->check = 0; - - netdev_dbg(dev, "gtp -> UDP src: %u dst: %u (len %u)\n", - ntohs(uh->source), ntohs(uh->dest), ntohs(uh->len)); - switch (proto) { case ETH_P_IP: - gtp_push_ip4hdr(skb, &pktinfo); + err = gtp_udp_tunnel_xmit(skb, gtph_port, &pktinfo); break; case ETH_P_IPV6: - gtp_push_ip6hdr(skb, &pktinfo); + /* Annotate length of the encapsulated packet */ + payload_len = skb->len; + + /* Push down and install the UDP header. */ + skb_push(skb, sizeof(struct udphdr)); + skb_reset_transport_header(skb); + + uh = udp_hdr(skb); + + uh->source = uh->dest = gtph_port; + uh->len = htons(sizeof(struct udphdr) + payload_len + gtph_len); + uh->check = 0; + + netdev_dbg(dev, "gtp -> UDP src: %u dst: %u (len %u)\n", + ntohs(uh->source), ntohs(uh->dest), ntohs(uh->len)); + + nf_reset(skb); + + netdev_dbg(dev, "Good, now packet leaving from GGSN to SGSN\n"); + + err = gtp_ip6tunnel_xmit(skb, &pktinfo); break; } - rcu_read_unlock();
- nf_reset(skb); - - netdev_dbg(dev, "Good, now packet leaving from GGSN to SGSN\n"); + rcu_read_unlock();
- err = ip_local_out(skb); gtp_iptunnel_xmit_stats(err, &dev->stats, dev->tstats);
return NETDEV_TX_OK; @@ -737,10 +749,26 @@ static const struct net_device_ops gtp_netdev_ops = {
static void gtp_link_setup(struct net_device *dev) { - dev->priv_flags |= IFF_NO_QUEUE; - dev->netdev_ops = >p_netdev_ops; dev->destructor = free_netdev; + + dev->hard_header_len = 0; + dev->addr_len = 0; + + /* Zero header length */ + dev->type = ARPHRD_NONE; + dev->flags = IFF_POINTOPOINT | IFF_NOARP | IFF_MULTICAST; + dev->tx_queue_len = 1000; + + dev->priv_flags |= IFF_NO_QUEUE; + dev->features |= NETIF_F_LLTX; + netif_keep_dst(dev); + + dev->needed_headroom = LL_MAX_HEADER + + sizeof(struct iphdr) + + sizeof(struct udphdr) + + sizeof(struct gtp0_header); + }
static int gtp_hashtable_new(struct gtp_instance *gti, int hsize); @@ -752,31 +780,20 @@ static int gtp_newlink(struct net *src_net, struct net_device *dev, struct nlattr *tb[], struct nlattr *data[]) { struct gtp_net *gn; - struct net_device *real_dev; struct gtp_instance *gti; int hashsize, err, fd0, fd1;
- if (!tb[IFLA_LINK]) - return -EINVAL; - - real_dev = __dev_get_by_index(src_net, nla_get_u32(tb[IFLA_LINK])); - if (!real_dev) - return -ENODEV; - - dev_hold(real_dev); - if (!tb[IFLA_MTU]) - dev->mtu = real_dev->mtu; - else if (dev->mtu > real_dev->mtu) - return -EINVAL; + dev->mtu = 1500;
gti = netdev_priv(dev); - gti->real_dev = real_dev;
fd0 = nla_get_u32(data[IFLA_GTP_FD0]); fd1 = nla_get_u32(data[IFLA_GTP_FD1]);
- gtp_encap_enable(dev, gti, fd0, fd1); + err = gtp_encap_enable(dev, gti, fd0, fd1); + if (err < 0) + goto out_err;
if (!data[IFLA_GTP_HASHSIZE]) hashsize = 1024; @@ -785,21 +802,28 @@ static int gtp_newlink(struct net *src_net, struct net_device *dev,
err = gtp_hashtable_new(gti, hashsize); if (err < 0) - return err; + goto out_encap;
err = register_netdevice(dev); - if (err < 0) - goto err1; + if (err < 0) { + netdev_dbg(dev, "failed to register new netdev %d\n", err); + goto out_hashtable; + }
gn = net_generic(dev_net(dev), gtp_net_id); list_add_rcu(>i->list, &gn->gtp_instance_list);
- netdev_dbg(dev, "registered new interface\n"); + netdev_dbg(dev, "registered new GTP interface\n");
return 0; -err1: - netdev_dbg(dev, "failed to register new netdev %d\n", err); + +out_hashtable: gtp_hashtable_free(gti); + +out_encap: + gtp_encap_disable(gti); + +out_err: return err; }
@@ -809,7 +833,6 @@ static void gtp_dellink(struct net_device *dev, struct list_head *head)
gtp_encap_disable(gti); gtp_hashtable_free(gti); - dev_put(gti->real_dev); list_del_rcu(>i->list); unregister_netdevice_queue(dev, head); }
GTPv1 tunnel use separate TEI for each direction. Add a union to hold TID for v0 and TEI for v1 separately.
Signed-off-by: Andreas Schultz aschultz@tpip.net --- gtp.c | 211 +++++++++++++++++++++++++++++++++++---------------------------- gtp_nl.h | 2 + 2 files changed, 118 insertions(+), 95 deletions(-)
diff --git a/gtp.c b/gtp.c index bead51f..457587e 100644 --- a/gtp.c +++ b/gtp.c @@ -39,7 +39,17 @@ struct pdp_ctx { struct hlist_node hlist_tid; struct hlist_node hlist_addr;
- u64 tid; + union { + uint64_t tid; + struct { + uint64_t tid; + u16 flow; + } v0; + struct { + uint32_t i_tei; + uint32_t o_tei; + } v1; + } u; u8 gtp_version; u16 af;
@@ -53,7 +63,6 @@ struct pdp_ctx { struct in_addr ip4; } sgsn_addr;
- u16 flow; atomic_t tx_seq;
struct rcu_head rcu_head; @@ -113,7 +122,7 @@ static struct pdp_ctx *gtp0_pdp_find(struct gtp_instance *gti, u64 tid) head = >i->tid_hash[gtp0_hashfn(tid) % gti->hash_size];
hlist_for_each_entry_rcu(pdp, head, hlist_tid) { - if (pdp->gtp_version == GTP_V0 && pdp->tid == tid) + if (pdp->gtp_version == GTP_V0 && pdp->u.v0.tid == tid) return pdp; }
@@ -129,7 +138,7 @@ static struct pdp_ctx *gtp1_pdp_find(struct gtp_instance *gti, u32 tid) head = >i->tid_hash[gtp1u_hashfn(tid) % gti->hash_size];
hlist_for_each_entry_rcu(pdp, head, hlist_tid) { - if (pdp->gtp_version == GTP_V1 && pdp->tid == tid) + if (pdp->gtp_version == GTP_V1 && pdp->u.v1.i_tei == tid) return pdp; }
@@ -456,10 +465,10 @@ gtp0_push_header(struct sk_buff *skb, struct pdp_ctx *pctx) gtp0->type = GTP_TPDU; gtp0->length = htons(payload_len); gtp0->seq = htons((atomic_inc_return(&pctx->tx_seq)-1) % 0xffff); - gtp0->flow = htons(pctx->flow); + gtp0->flow = htons(pctx->u.v0.flow); gtp0->number = 0xFF; gtp0->spare[0] = gtp0->spare[1] = gtp0->spare[2] = 0xFF; - gtp0->tid = cpu_to_be64(pctx->tid); + gtp0->tid = cpu_to_be64(pctx->u.v0.tid); }
static inline void @@ -480,7 +489,7 @@ gtp1_push_header(struct sk_buff *skb, struct pdp_ctx *pctx) gtp1->flags = 0x38; /* V1, GTP-non-prime */ gtp1->type = GTP_TPDU; gtp1->length = htons(payload_len); - gtp1->tid = htonl((u32)pctx->tid); + gtp1->tid = htonl(pctx->u.v1.o_tei);
/* TODO: Suppport for extension header, sequence number and N-PDU. * Update the length field if any of them is available. @@ -1021,43 +1030,45 @@ static struct net_device *gtp_find_dev(struct net *net, int ifindex) return NULL; }
-static int ipv4_pdp_add(struct net_device *dev, struct genl_info *info) +static void ipv4_pdp_fill(struct pdp_ctx *pctx, struct genl_info *info) { - struct gtp_instance *gti = netdev_priv(dev); - struct pdp_ctx *pctx; - u16 flow = 0; - u32 gtp_version, sgsn_addr, ms_addr, hash_ms, hash_tid; - u64 tid; - bool found = false; + pctx->gtp_version = nla_get_u32(info->attrs[GTPA_VERSION]); + pctx->af = AF_INET; + pctx->sgsn_addr.ip4.s_addr = + nla_get_u32(info->attrs[GTPA_SGSN_ADDRESS]); + pctx->ms_addr.ip4.s_addr = + nla_get_u32(info->attrs[GTPA_MS_ADDRESS]);
- gtp_version = nla_get_u32(info->attrs[GTPA_VERSION]); - switch (gtp_version) { + switch (pctx->gtp_version) { case GTP_V0: - case GTP_V1: + + /* According to TS 09.60, sections 7.5.1 and 7.5.2, the flow + * label needs to be the same for uplink and downlink packets, + * so let's annotate this. + */ + pctx->u.v0.tid = nla_get_u64(info->attrs[GTPA_TID]); + pctx->u.v0.flow = nla_get_u16(info->attrs[GTPA_FLOW]); break; - default: - return -EINVAL; - }
- tid = nla_get_u64(info->attrs[GTPA_TID]); - /* GTPv1 allows 32-bits tunnel IDs */ - if (gtp_version == GTP_V1 && tid > UINT_MAX) - return -EINVAL; + case GTP_V1: + pctx->u.v1.i_tei = nla_get_u32(info->attrs[GTPA_I_TEI]); + pctx->u.v1.o_tei = nla_get_u32(info->attrs[GTPA_O_TEI]);
- /* According to TS 09.60, sections 7.5.1 and 7.5.2, the flow label - * needs to be the same for uplink and downlink packets, so let's - * annotate this. - */ - if (gtp_version == GTP_V0) { - if (!info->attrs[GTPA_FLOW]) - return -EINVAL; + break;
- flow = nla_get_u16(info->attrs[GTPA_FLOW]); + default: + break; } +}
- sgsn_addr = nla_get_u32(info->attrs[GTPA_SGSN_ADDRESS]); - ms_addr = nla_get_u32(info->attrs[GTPA_MS_ADDRESS]); +static int ipv4_pdp_add(struct net_device *dev, struct genl_info *info) +{ + struct gtp_instance *gti = netdev_priv(dev); + struct pdp_ctx *pctx; + u32 ms_addr, hash_ms, hash_tid = 0; + bool found = false;
+ ms_addr = nla_get_u32(info->attrs[GTPA_MS_ADDRESS]); hash_ms = ipv4_hashfn(ms_addr) % gti->hash_size;
hlist_for_each_entry_rcu(pctx, >i->addr_hash[hash_ms], hlist_addr) { @@ -1073,47 +1084,50 @@ static int ipv4_pdp_add(struct net_device *dev, struct genl_info *info) if (info->nlhdr->nlmsg_flags & NLM_F_REPLACE) return -EOPNOTSUPP;
- pctx->af = AF_INET; - pctx->gtp_version = gtp_version; - pctx->tid = tid; - pctx->sgsn_addr.ip4.s_addr = sgsn_addr; - pctx->ms_addr.ip4.s_addr = ms_addr; + ipv4_pdp_fill(pctx, info);
- netdev_dbg(dev, "update tunnel id = %llx (pdp %p)\n", - tid, pctx); + if (pctx->gtp_version == GTP_V0) + netdev_dbg(dev, "GTPv0-U: update tunnel id = %llx (pdp %p)\n", + pctx->u.v0.tid, pctx); + else if (pctx->gtp_version == GTP_V1) + netdev_dbg(dev, "GTPv1-U: update tunnel id = %x/%x (pdp %p)\n", + pctx->u.v1.i_tei, pctx->u.v1.o_tei, pctx);
return 0; + }
pctx = kmalloc(sizeof(struct pdp_ctx), GFP_KERNEL); if (pctx == NULL) return -ENOMEM;
- pctx->af = AF_INET; - pctx->gtp_version = gtp_version; - pctx->tid = tid; - pctx->sgsn_addr.ip4.s_addr = sgsn_addr; - pctx->ms_addr.ip4.s_addr = ms_addr; - pctx->flow = flow; + ipv4_pdp_fill(pctx, info); atomic_set(&pctx->tx_seq, 0);
- switch (gtp_version) { + switch (pctx->gtp_version) { case GTP_V0: /* TS 09.60: "The flow label identifies unambiguously a GTP * flow.". We use the tid for this instead, I cannot find a * situation in which this doesn't unambiguosly identify the * PDP context. */ - hash_tid = gtp0_hashfn(tid) % gti->hash_size; + hash_tid = gtp0_hashfn(pctx->u.v0.tid) % gti->hash_size; break; + case GTP_V1: - hash_tid = gtp1u_hashfn(tid) % gti->hash_size; + hash_tid = gtp1u_hashfn(pctx->u.v1.i_tei) % gti->hash_size; break; } + hlist_add_head_rcu(&pctx->hlist_addr, >i->addr_hash[hash_ms]); hlist_add_head_rcu(&pctx->hlist_tid, >i->tid_hash[hash_tid]);
- netdev_dbg(dev, "adding tunnel id = %llx (pdp %p)\n", tid, pctx); + if (pctx->gtp_version == GTP_V0) + netdev_dbg(dev, "GTPv0-U: adding tunnel id = %llx (pdp %p)\n", + pctx->u.v0.tid, pctx); + else if (pctx->gtp_version == GTP_V1) + netdev_dbg(dev, "GTPv1-U: adding tunnel id = %x/%x (pdp %p)\n", + pctx->u.v1.i_tei, pctx->u.v1.o_tei, pctx);
return 0; } @@ -1126,9 +1140,25 @@ static int gtp_genl_tunnel_new(struct sk_buff *skb, struct genl_info *info) if (!info->attrs[GTPA_VERSION] || !info->attrs[GTPA_LINK] || !info->attrs[GTPA_SGSN_ADDRESS] || - !info->attrs[GTPA_MS_ADDRESS] || - !info->attrs[GTPA_TID]) + !info->attrs[GTPA_MS_ADDRESS]) + return -EINVAL; + + switch (nla_get_u32(info->attrs[GTPA_VERSION])) { + case GTP_V0: + if (!info->attrs[GTPA_TID] || + !info->attrs[GTPA_FLOW]) + return -EINVAL; + break; + + case GTP_V1: + if (!info->attrs[GTPA_I_TEI] || + !info->attrs[GTPA_O_TEI]) + return -EINVAL; + break; + + default: return -EINVAL; + }
net = gtp_genl_get_net(sock_net(skb->sk), info->attrs); if (IS_ERR(net)) @@ -1148,28 +1178,9 @@ static int gtp_genl_tunnel_delete(struct sk_buff *skb, struct genl_info *info) struct gtp_instance *gti; struct net_device *dev; struct pdp_ctx *pctx; - u32 gtp_version; - u64 tid;
if (!info->attrs[GTPA_VERSION] || - !info->attrs[GTPA_LINK] || - !info->attrs[GTPA_SGSN_ADDRESS] || - !info->attrs[GTPA_MS_ADDRESS] || - !info->attrs[GTPA_TID]) - return -EINVAL; - - gtp_version = nla_get_u32(info->attrs[GTPA_VERSION]); - switch (gtp_version) { - case GTP_V0: - case GTP_V1: - break; - default: - return -EINVAL; - } - - tid = nla_get_u64(info->attrs[GTPA_TID]); - /* GTPv1 allows 32-bits tunnel IDs */ - if (gtp_version == GTP_V1 && tid > UINT_MAX) + !info->attrs[GTPA_LINK]) return -EINVAL;
net = gtp_genl_get_net(sock_net(skb->sk), info->attrs); @@ -1183,20 +1194,32 @@ static int gtp_genl_tunnel_delete(struct sk_buff *skb, struct genl_info *info)
gti = netdev_priv(dev);
- switch (gtp_version) { + switch (nla_get_u32(info->attrs[GTPA_VERSION])) { case GTP_V0: + if (!info->attrs[GTPA_TID]) + return -EINVAL; pctx = gtp0_pdp_find(gti, nla_get_u64(info->attrs[GTPA_TID])); break; + case GTP_V1: - pctx = gtp1_pdp_find(gti, nla_get_u64(info->attrs[GTPA_TID])); + if (!info->attrs[GTPA_I_TEI]) + return -EINVAL; + pctx = gtp1_pdp_find(gti, nla_get_u64(info->attrs[GTPA_I_TEI])); break; + + default: + return -EINVAL; }
if (pctx == NULL) return -ENOENT;
- netdev_dbg(dev, "deleting tunnel with ID %lld\n", - (unsigned long long) nla_get_u64(info->attrs[GTPA_TID])); + if (pctx->gtp_version == GTP_V0) + netdev_dbg(dev, "GTPv0-U: deleting tunnel id = %llx (pdp %p)\n", + pctx->u.v0.tid, pctx); + else if (pctx->gtp_version == GTP_V1) + netdev_dbg(dev, "GTPv1-U: deleting tunnel id = %x/%x (pdp %p)\n", + pctx->u.v1.i_tei, pctx->u.v1.o_tei, pctx);
hlist_del_rcu(&pctx->hlist_tid); hlist_del_rcu(&pctx->hlist_addr); @@ -1228,9 +1251,12 @@ 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_SGSN_ADDRESS, pctx->sgsn_addr.ip4.s_addr) || nla_put_u32(skb, GTPA_MS_ADDRESS, pctx->ms_addr.ip4.s_addr) || - nla_put_u64(skb, GTPA_TID, pctx->tid) || (pctx->gtp_version == GTP_V0 && - nla_put_u16(skb, GTPA_FLOW, pctx->flow))) + nla_put_u64(skb, GTPA_TID, pctx->u.v0.tid) && + nla_put_u16(skb, GTPA_FLOW, pctx->u.v0.flow)) || + (pctx->gtp_version == GTP_V1 && + nla_put_u32(skb, GTPA_I_TEI, pctx->u.v1.i_tei) && + nla_put_u32(skb, GTPA_O_TEI, pctx->u.v1.o_tei))) goto nla_put_failure;
genlmsg_end(skb, genlh); @@ -1277,23 +1303,16 @@ static int gtp_genl_tunnel_get(struct sk_buff *skb, struct genl_info *info) gti = netdev_priv(dev);
rcu_read_lock(); - if (info->attrs[GTPA_TID]) { + if (gtp_version == GTP_V0 && + info->attrs[GTPA_TID]) { u64 tid = nla_get_u64(info->attrs[GTPA_TID]);
- /* GTPv1 allows 32-bits tunnel IDs */ - if (gtp_version == GTP_V1 && tid > UINT_MAX) { - err = -EINVAL; - goto err_unlock; - } + pctx = gtp0_pdp_find(gti, tid); + } else if (gtp_version == GTP_V1 && + info->attrs[GTPA_I_TEI]) { + u32 tid = nla_get_u32(info->attrs[GTPA_I_TEI]);
- switch (gtp_version) { - case GTP_V0: - pctx = gtp0_pdp_find(gti, tid); - break; - case GTP_V1: - pctx = gtp1_pdp_find(gti, tid); - break; - } + pctx = gtp1_pdp_find(gti, tid); } else if (info->attrs[GTPA_MS_ADDRESS]) { u32 ip = nla_get_u32(info->attrs[GTPA_MS_ADDRESS]);
@@ -1347,7 +1366,7 @@ gtp_genl_tunnel_dump(struct sk_buff *skb, struct netlink_callback *cb)
for (i = k; i < gti->hash_size; i++) { hlist_for_each_entry_rcu(pctx, >i->tid_hash[i], hlist_tid) { - if (tid && tid != pctx->tid) + if (tid && tid != pctx->u.tid) continue; else tid = 0; @@ -1358,7 +1377,7 @@ gtp_genl_tunnel_dump(struct sk_buff *skb, struct netlink_callback *cb) cb->nlh->nlmsg_type, pctx); if (ret < 0) { cb->args[0] = i; - cb->args[1] = pctx->tid; + cb->args[1] = pctx->u.tid; cb->args[2] = (unsigned long)gti; goto out; } @@ -1378,6 +1397,8 @@ static struct nla_policy gtp_genl_policy[GTPA_MAX + 1] = { [GTPA_MS_ADDRESS] = { .type = NLA_NESTED, }, [GTPA_FLOW] = { .type = NLA_U16, }, [GTPA_NET_NS_FD] = { .type = NLA_U32, }, + [GTPA_I_TEI] = { .type = NLA_U32, }, + [GTPA_O_TEI] = { .type = NLA_U32, }, };
static const struct genl_ops gtp_genl_ops[] = { diff --git a/gtp_nl.h b/gtp_nl.h index c20666d..6dc768f 100644 --- a/gtp_nl.h +++ b/gtp_nl.h @@ -41,6 +41,8 @@ enum gtp_attrs { GTPA_MS_ADDRESS, GTPA_FLOW, GTPA_NET_NS_FD, + GTPA_I_TEI, + GTPA_O_TEI, __GTPA_MAX, }; #define GTPA_MAX (__GTPA_MAX + 1)
Signed-off-by: Andreas Schultz aschultz@tpip.net --- libgtnl/include/linux/gtp_nl.h | 2 +- libgtnl/src/gtp-genl.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/libgtnl/include/linux/gtp_nl.h b/libgtnl/include/linux/gtp_nl.h index a8fdf3a..a1e8ce1 100644 --- a/libgtnl/include/linux/gtp_nl.h +++ b/libgtnl/include/linux/gtp_nl.h @@ -39,7 +39,7 @@ enum gtp_attrs { GTPA_TID, /* 64 bits for GTPv1 */ GTPA_SGSN_ADDRESS, GTPA_MS_ADDRESS, - GTPA_FLOWID, /* only for GTPv0 */ + GTPA_FLOW, /* only for GTPv0 */ GTPA_NET_NS_FD, __GTPA_MAX, }; diff --git a/libgtnl/src/gtp-genl.c b/libgtnl/src/gtp-genl.c index bffa7a5..ddb7cc7 100644 --- a/libgtnl/src/gtp-genl.c +++ b/libgtnl/src/gtp-genl.c @@ -50,7 +50,7 @@ static void gtp_build_payload(struct nlmsghdr *nlh, struct gtp_tunnel *t) mnl_attr_put_u32(nlh, GTPA_SGSN_ADDRESS, t->sgsn_addr.s_addr); mnl_attr_put_u32(nlh, GTPA_MS_ADDRESS, t->ms_addr.s_addr); mnl_attr_put_u64(nlh, GTPA_TID, t->tid); - mnl_attr_put_u16(nlh, GTPA_FLOWID, t->flowid); + mnl_attr_put_u16(nlh, GTPA_FLOW, t->flowid); }
int gtp_add_tunnel(int genl_id, struct mnl_socket *nl, struct gtp_tunnel *t)
Signed-off-by: Andreas Schultz aschultz@tpip.net --- libgtnl/include/libgtpnl/gtpnl.h | 3 +-- libgtnl/src/gtp-rtnl.c | 4 +--- libgtnl/tools/gtp-link-add.c | 5 ++--- 3 files changed, 4 insertions(+), 8 deletions(-)
diff --git a/libgtnl/include/libgtpnl/gtpnl.h b/libgtnl/include/libgtpnl/gtpnl.h index 3d3fd73..49ba03d 100644 --- a/libgtnl/include/libgtpnl/gtpnl.h +++ b/libgtnl/include/libgtpnl/gtpnl.h @@ -16,8 +16,7 @@ int genl_lookup_family(struct mnl_socket *nl, const char *family);
struct in_addr;
-int gtp_dev_create(int dest_ns, const char *gtp_ifname, const char *real_ifname, - int fd0, int fd1); +int gtp_dev_create(int dest_ns, const char *gtp_ifname, int fd0, int fd1); int gtp_dev_config(const char *iface, struct in_addr *net, uint32_t prefix); int gtp_dev_destroy(const char *gtp_ifname);
diff --git a/libgtnl/src/gtp-rtnl.c b/libgtnl/src/gtp-rtnl.c index 76e2021..527015b 100644 --- a/libgtnl/src/gtp-rtnl.c +++ b/libgtnl/src/gtp-rtnl.c @@ -104,8 +104,7 @@ static int gtp_dev_talk(struct nlmsghdr *nlh, uint32_t seq) return ret; }
-int gtp_dev_create(int dest_ns, const char *gtp_ifname, const char *real_ifname, - int fd0, int fd1) +int gtp_dev_create(int dest_ns, const char *gtp_ifname, int fd0, int fd1) { char buf[MNL_SOCKET_BUFFER_SIZE]; struct nlmsghdr *nlh; @@ -122,7 +121,6 @@ int gtp_dev_create(int dest_ns, const char *gtp_ifname, const char *real_ifname,
if (dest_ns >= 0) mnl_attr_put_u32(nlh, IFLA_NET_NS_FD, dest_ns); - mnl_attr_put_u32(nlh, IFLA_LINK, if_nametoindex(real_ifname)); mnl_attr_put_str(nlh, IFLA_IFNAME, gtp_ifname); nest = mnl_attr_nest_start(nlh, IFLA_LINKINFO); mnl_attr_put_str(nlh, IFLA_INFO_KIND, "gtp"); diff --git a/libgtnl/tools/gtp-link-add.c b/libgtnl/tools/gtp-link-add.c index 3d893eb..4ef025e 100644 --- a/libgtnl/tools/gtp-link-add.c +++ b/libgtnl/tools/gtp-link-add.c @@ -43,8 +43,8 @@ int main(int argc, char *argv[]) unsigned int seq, portid, change = 0, flags = 0; struct nlattr *nest, *nest2;
- if (argc != 2) { - printf("Usage: %s [ifname]\n", argv[0]); + if (argc != 1) { + printf("Usage: %s\n", argv[0]); exit(EXIT_FAILURE); }
@@ -62,7 +62,6 @@ int main(int argc, char *argv[]) int fd1 = socket(AF_INET, SOCK_DGRAM, 0); int fd2 = socket(AF_INET, SOCK_DGRAM, 0);
- mnl_attr_put_u32(nlh, IFLA_LINK, if_nametoindex(argv[1])); mnl_attr_put_str(nlh, IFLA_IFNAME, "gtp0"); nest = mnl_attr_nest_start(nlh, IFLA_LINKINFO); mnl_attr_put_str(nlh, IFLA_INFO_KIND, "gtp");
GTPv1 tunnel use separate 32bit Tunnel Endpoint Identifier's for each direction while GTPv0 uses only one 64bit Tunnel IDentifier.
Signed-off-by: Andreas Schultz aschultz@tpip.net --- libgtnl/include/libgtpnl/gtp.h | 4 ++ libgtnl/include/linux/gtp_nl.h | 2 + libgtnl/src/gtp-genl.c | 34 +++++++++-- libgtnl/src/gtp.c | 32 ++++++++-- libgtnl/src/internal.h | 12 +++- libgtnl/src/libgtpnl.map | 4 ++ libgtnl/tools/Makefile.am | 4 +- libgtnl/tools/gtp-tunnel.c | 134 ++++++++++++++++++++++++++++------------- libgtnl/tools/gtpnl.c | 35 ----------- 9 files changed, 172 insertions(+), 89 deletions(-) delete mode 100644 libgtnl/tools/gtpnl.c
diff --git a/libgtnl/include/libgtpnl/gtp.h b/libgtnl/include/libgtpnl/gtp.h index fa09d2a..521339d 100644 --- a/libgtnl/include/libgtpnl/gtp.h +++ b/libgtnl/include/libgtpnl/gtp.h @@ -14,6 +14,8 @@ void gtp_tunnel_set_ms_ip4(struct gtp_tunnel *t, struct in_addr *ms_addr); void gtp_tunnel_set_sgsn_ip4(struct gtp_tunnel *t, struct in_addr *sgsn_addr); void gtp_tunnel_set_version(struct gtp_tunnel *t, uint32_t version); void gtp_tunnel_set_tid(struct gtp_tunnel *t, uint64_t tid); +void gtp_tunnel_set_i_tei(struct gtp_tunnel *t, uint32_t i_tei); +void gtp_tunnel_set_o_tei(struct gtp_tunnel *t, uint32_t o_tei); void gtp_tunnel_set_flowid(struct gtp_tunnel *t, uint16_t flowid);
const int gtp_tunnel_get_ifns(struct gtp_tunnel *t); @@ -22,6 +24,8 @@ const struct in_addr *gtp_tunnel_get_ms_ip4(struct gtp_tunnel *t); const struct in_addr *gtp_tunnel_get_sgsn_ip4(struct gtp_tunnel *t); int gtp_tunnel_get_version(struct gtp_tunnel *t); uint64_t gtp_tunnel_get_tid(struct gtp_tunnel *t); +uint32_t gtp_tunnel_get_i_tei(struct gtp_tunnel *t); +uint32_t gtp_tunnel_get_o_tei(struct gtp_tunnel *t); uint16_t gtp_tunnel_get_flowid(struct gtp_tunnel *t);
#endif diff --git a/libgtnl/include/linux/gtp_nl.h b/libgtnl/include/linux/gtp_nl.h index a1e8ce1..deb92cf 100644 --- a/libgtnl/include/linux/gtp_nl.h +++ b/libgtnl/include/linux/gtp_nl.h @@ -41,6 +41,8 @@ enum gtp_attrs { GTPA_MS_ADDRESS, GTPA_FLOW, /* only for GTPv0 */ GTPA_NET_NS_FD, + GTPA_I_TEI, + GTPA_O_TEI, __GTPA_MAX, }; #define GTPA_MAX (__GTPA_MAX + 1) diff --git a/libgtnl/src/gtp-genl.c b/libgtnl/src/gtp-genl.c index ddb7cc7..3c7deed 100644 --- a/libgtnl/src/gtp-genl.c +++ b/libgtnl/src/gtp-genl.c @@ -49,8 +49,13 @@ static void gtp_build_payload(struct nlmsghdr *nlh, struct gtp_tunnel *t) mnl_attr_put_u32(nlh, GTPA_LINK, t->ifidx); mnl_attr_put_u32(nlh, GTPA_SGSN_ADDRESS, t->sgsn_addr.s_addr); mnl_attr_put_u32(nlh, GTPA_MS_ADDRESS, t->ms_addr.s_addr); - mnl_attr_put_u64(nlh, GTPA_TID, t->tid); - mnl_attr_put_u16(nlh, GTPA_FLOW, t->flowid); + if (t->gtp_version == GTP_V0) { + mnl_attr_put_u64(nlh, GTPA_TID, t->u.v0.tid); + mnl_attr_put_u16(nlh, GTPA_FLOW, t->u.v0.flowid); + } else if (t->gtp_version == GTP_V1) { + mnl_attr_put_u32(nlh, GTPA_I_TEI, t->u.v1.i_tei); + mnl_attr_put_u32(nlh, GTPA_O_TEI, t->u.v1.o_tei); + } }
int gtp_add_tunnel(int genl_id, struct mnl_socket *nl, struct gtp_tunnel *t) @@ -95,7 +100,15 @@ EXPORT_SYMBOL(gtp_del_tunnel);
struct gtp_pdp { uint32_t version; - uint64_t tid; + union { + struct { + uint64_t tid; + } v0; + struct { + uint32_t i_tei; + uint32_t o_tei; + } v1; + } u; struct in_addr sgsn_addr; struct in_addr ms_addr; }; @@ -115,6 +128,8 @@ static int genl_gtp_validate_cb(const struct nlattr *attr, void *data) return MNL_CB_ERROR; } break; + case GTPA_O_TEI: + case GTPA_I_TEI: case GTPA_SGSN_ADDRESS: case GTPA_MS_ADDRESS: case GTPA_VERSION: @@ -138,7 +153,11 @@ static int genl_gtp_attr_cb(const struct nlmsghdr *nlh, void *data)
mnl_attr_parse(nlh, sizeof(*genl), genl_gtp_validate_cb, tb); if (tb[GTPA_TID]) - pdp.tid = mnl_attr_get_u64(tb[GTPA_TID]); + pdp.u.v0.tid = mnl_attr_get_u64(tb[GTPA_TID]); + if (tb[GTPA_I_TEI]) + pdp.u.v1.i_tei = mnl_attr_get_u32(tb[GTPA_I_TEI]); + if (tb[GTPA_O_TEI]) + pdp.u.v1.o_tei = mnl_attr_get_u32(tb[GTPA_O_TEI]); if (tb[GTPA_SGSN_ADDRESS]) { pdp.sgsn_addr.s_addr = mnl_attr_get_u32(tb[GTPA_SGSN_ADDRESS]); @@ -151,7 +170,12 @@ static int genl_gtp_attr_cb(const struct nlmsghdr *nlh, void *data) }
printf("version %u ", pdp.version); - printf("tid %"PRIu64" ms_addr %s ", pdp.tid, inet_ntoa(pdp.sgsn_addr)); + if (pdp.version == GTP_V0) + printf("tid %"PRIu64" ms_addr %s ", + pdp.u.v0.tid, inet_ntoa(pdp.sgsn_addr)); + else if (pdp.version == GTP_V1) + printf("tei %u/%u ms_addr %s ", pdp.u.v1.i_tei, + pdp.u.v1.o_tei, inet_ntoa(pdp.sgsn_addr)); printf("sgsn_addr %s\n", inet_ntoa(pdp.ms_addr));
return MNL_CB_OK; diff --git a/libgtnl/src/gtp.c b/libgtnl/src/gtp.c index 6e3d473..bb5eaaf 100644 --- a/libgtnl/src/gtp.c +++ b/libgtnl/src/gtp.c @@ -71,16 +71,28 @@ EXPORT_SYMBOL(gtp_tunnel_set_version);
void gtp_tunnel_set_tid(struct gtp_tunnel *t, uint64_t tid) { - t->tid = tid; + t->u.v0.tid = tid; } EXPORT_SYMBOL(gtp_tunnel_set_tid);
void gtp_tunnel_set_flowid(struct gtp_tunnel *t, uint16_t flowid) { - t->flowid = flowid; + t->u.v0.flowid = flowid; } EXPORT_SYMBOL(gtp_tunnel_set_flowid);
+void gtp_tunnel_set_i_tei(struct gtp_tunnel *t, uint32_t i_tei) +{ + t->u.v1.i_tei = i_tei; +} +EXPORT_SYMBOL(gtp_tunnel_set_i_tei); + +void gtp_tunnel_set_o_tei(struct gtp_tunnel *t, uint32_t o_tei) +{ + t->u.v1.o_tei = o_tei; +} +EXPORT_SYMBOL(gtp_tunnel_set_o_tei); + const int gtp_tunnel_get_ifns(struct gtp_tunnel *t) { return t->ifns; @@ -113,12 +125,24 @@ EXPORT_SYMBOL(gtp_tunnel_get_version);
uint64_t gtp_tunnel_get_tid(struct gtp_tunnel *t) { - return t->tid; + return t->u.v0.tid; } EXPORT_SYMBOL(gtp_tunnel_get_tid);
uint16_t gtp_tunnel_get_flowid(struct gtp_tunnel *t) { - return t->flowid; + return t->u.v0.flowid; } EXPORT_SYMBOL(gtp_tunnel_get_flowid); + +uint32_t gtp_tunnel_get_i_tei(struct gtp_tunnel *t) +{ + return t->u.v1.i_tei; +} +EXPORT_SYMBOL(gtp_tunnel_get_i_tei); + +uint32_t gtp_tunnel_get_o_tei(struct gtp_tunnel *t) +{ + return t->u.v1.o_tei; +} +EXPORT_SYMBOL(gtp_tunnel_get_o_tei); diff --git a/libgtnl/src/internal.h b/libgtnl/src/internal.h index 68f0135..1754e3b 100644 --- a/libgtnl/src/internal.h +++ b/libgtnl/src/internal.h @@ -17,9 +17,17 @@ struct gtp_tunnel { uint32_t ifidx; struct in_addr ms_addr; struct in_addr sgsn_addr; - uint64_t tid; - uint16_t flowid; int gtp_version; + union { + struct { + uint64_t tid; + uint16_t flowid; + } v0; + struct { + uint32_t i_tei; + uint32_t o_tei; + } v1; + } u; };
#endif diff --git a/libgtnl/src/libgtpnl.map b/libgtnl/src/libgtpnl.map index 2368467..9ca83c7 100644 --- a/libgtnl/src/libgtpnl.map +++ b/libgtnl/src/libgtpnl.map @@ -22,6 +22,8 @@ global: gtp_tunnel_set_version; gtp_tunnel_set_tid; gtp_tunnel_set_flowid; + gtp_tunnel_set_i_tei; + gtp_tunnel_set_o_tei; gtp_tunnel_get_ifns; gtp_tunnel_get_ifidx; gtp_tunnel_get_ms_ip4; @@ -29,6 +31,8 @@ global: gtp_tunnel_get_version; gtp_tunnel_get_tid; gtp_tunnel_get_flowid; + gtp_tunnel_get_i_tei; + gtp_tunnel_get_o_tei;
local: *; }; diff --git a/libgtnl/tools/Makefile.am b/libgtnl/tools/Makefile.am index 7880f3c..89dca32 100644 --- a/libgtnl/tools/Makefile.am +++ b/libgtnl/tools/Makefile.am @@ -3,8 +3,8 @@ include $(top_srcdir)/Make_global.am check_PROGRAMS = gtp-link-add \ gtp-tunnel
-gtp_link_add_SOURCES = gtp-link-add.c gtpnl.c +gtp_link_add_SOURCES = gtp-link-add.c gtp_link_add_LDADD = ../src/libgtpnl.la ${LIBMNL_LIBS}
-gtp_tunnel_SOURCES = gtp-tunnel.c gtpnl.c +gtp_tunnel_SOURCES = gtp-tunnel.c gtp_tunnel_LDADD = ../src/libgtpnl.la ${LIBMNL_LIBS} diff --git a/libgtnl/tools/gtp-tunnel.c b/libgtnl/tools/gtp-tunnel.c index 9c52a27..bb12c3f 100644 --- a/libgtnl/tools/gtp-tunnel.c +++ b/libgtnl/tools/gtp-tunnel.c @@ -28,71 +28,91 @@ #include <arpa/inet.h> #include <sys/socket.h> #include <netinet/in.h> +#include <net/if.h> +#include <inttypes.h>
#include <libmnl/libmnl.h> #include <linux/genetlink.h>
#include <linux/gtp_nl.h> +#include <libgtpnl/gtp.h> #include <libgtpnl/gtpnl.h>
+static void add_usage(const char *name) +{ + printf("%s add <gtp device> <v0> <tid> <ms-addr> <sgsn-addr>\n", + name); + printf("%s add <gtp device> <v1> <i_tei> <o_tei> <ms-addr> <sgsn-addr>\n", + name); +} + static int add_tunnel(int argc, char *argv[], int genl_id, struct mnl_socket *nl) { + struct gtp_tunnel *t; uint32_t gtp_ifidx; struct in_addr ms, sgsn; - struct nlmsghdr *nlh; - char buf[MNL_SOCKET_BUFFER_SIZE]; - uint32_t seq = time(NULL), gtp_version; + uint32_t gtp_version; + int optidx;
- if (argc != 7) { - printf("%s add <gtp device> <v0|v1> <tid> <ms-addr> <sgsn-addr>\n", - argv[0]); + if (argc < 7 || argc > 8) { + add_usage(argv[0]); return EXIT_FAILURE; } - gtp_ifidx = if_nametoindex(argv[2]); + + t = gtp_tunnel_alloc(); + optidx = 2; + + gtp_ifidx = if_nametoindex(argv[optidx]); if (gtp_ifidx == 0) { - fprintf(stderr, "wrong GTP interface %s\n", argv[2]); + fprintf(stderr, "wrong GTP interface %s\n", argv[optidx]); return EXIT_FAILURE; } + gtp_tunnel_set_ifidx(t, gtp_ifidx);
- if (inet_aton(argv[5], &ms) < 0) { - perror("bad address for ms"); - exit(EXIT_FAILURE); - } - - if (inet_aton(argv[6], &sgsn) < 0) { - perror("bad address for sgsn"); - exit(EXIT_FAILURE); - } + optidx++;
- if (strcmp(argv[3], "v0") == 0) + if (strcmp(argv[optidx], "v0") == 0) gtp_version = GTP_V0; - else if (strcmp(argv[3], "v1") == 0) + else if (strcmp(argv[optidx], "v1") == 0) gtp_version = GTP_V1; else { fprintf(stderr, "wrong GTP version %s, use v0 or v1\n", - argv[3]); + argv[optidx]); return EXIT_FAILURE; } + gtp_tunnel_set_version(t, gtp_version);
- nlh = genl_nlmsg_build_hdr(buf, genl_id, NLM_F_EXCL | NLM_F_ACK, ++seq, - GTP_CMD_TUNNEL_NEW); - gtp_build_payload(nlh, atoi(argv[4]), gtp_ifidx, sgsn.s_addr, - ms.s_addr, gtp_version); + if (gtp_version == GTP_V0) + gtp_tunnel_set_tid(t, atoi(argv[optidx++])); + else if (gtp_version == GTP_V1) { + gtp_tunnel_set_i_tei(t, atoi(argv[optidx++])); + gtp_tunnel_set_o_tei(t, atoi(argv[optidx++])); + }
- if (genl_socket_talk(nl, nlh, seq, NULL, NULL) < 0) - perror("genl_socket_talk"); + if (inet_aton(argv[optidx++], &ms) < 0) { + perror("bad address for ms"); + exit(EXIT_FAILURE); + } + gtp_tunnel_set_ms_ip4(t, &ms); + + if (inet_aton(argv[optidx++], &sgsn) < 0) { + perror("bad address for sgsn"); + exit(EXIT_FAILURE); + } + gtp_tunnel_set_sgsn_ip4(t, &sgsn); + + gtp_add_tunnel(genl_id, nl, t);
+ gtp_tunnel_free(t); return 0; }
static int del_tunnel(int argc, char *argv[], int genl_id, struct mnl_socket *nl) { + struct gtp_tunnel *t; uint32_t gtp_ifidx; - char buf[MNL_SOCKET_BUFFER_SIZE]; - struct nlmsghdr *nlh; - uint32_t seq = time(NULL);
if (argc != 5) { printf("%s add <gtp device> <version> <tid>\n", @@ -100,21 +120,44 @@ del_tunnel(int argc, char *argv[], int genl_id, struct mnl_socket *nl) return EXIT_FAILURE; }
- gtp_ifidx = if_nametoindex(argv[2]); + t = gtp_tunnel_alloc();
- nlh = genl_nlmsg_build_hdr(buf, genl_id, NLM_F_ACK, ++seq, - GTP_CMD_TUNNEL_DELETE); - gtp_build_payload(nlh, atoi(argv[4]), gtp_ifidx, 0, 0, atoi(argv[3])); + gtp_ifidx = if_nametoindex(argv[2]); + if (gtp_ifidx == 0) { + fprintf(stderr, "wrong GTP interface %s\n", argv[2]); + return EXIT_FAILURE; + } + gtp_tunnel_set_ifidx(t, gtp_ifidx); + + if (strcmp(argv[3], "v0") == 0) { + gtp_tunnel_set_version(t, GTP_V0); + gtp_tunnel_set_tid(t, atoi(argv[4])); + } else if (strcmp(argv[3], "v1") == 0) { + gtp_tunnel_set_version(t, GTP_V1); + gtp_tunnel_set_i_tei(t, atoi(argv[4])); + } else { + fprintf(stderr, "wrong GTP version %s, use v0 or v1\n", + argv[3]); + return EXIT_FAILURE; + }
- if (genl_socket_talk(nl, nlh, seq, NULL, NULL) < 0) - perror("genl_socket_talk"); + gtp_del_tunnel(genl_id, nl, t);
+ gtp_tunnel_free(t); return 0; }
struct gtp_pdp { uint32_t version; - uint64_t tid; + union { + struct { + uint64_t tid; + } v0; + struct { + uint32_t i_tei; + uint32_t o_tei; + } v1; + } u; struct in_addr sgsn_addr; struct in_addr ms_addr; }; @@ -134,6 +177,8 @@ static int genl_gtp_validate_cb(const struct nlattr *attr, void *data) return MNL_CB_ERROR; } break; + case GTPA_I_TEI: + case GTPA_O_TEI: case GTPA_SGSN_ADDRESS: case GTPA_MS_ADDRESS: case GTPA_VERSION: @@ -152,12 +197,16 @@ static int genl_gtp_validate_cb(const struct nlattr *attr, void *data) static int genl_gtp_attr_cb(const struct nlmsghdr *nlh, void *data) { struct nlattr *tb[GTPA_MAX + 1] = {}; - struct gtp_pdp pdp; + struct gtp_pdp pdp = {}; struct genlmsghdr *genl;
mnl_attr_parse(nlh, sizeof(*genl), genl_gtp_validate_cb, tb); if (tb[GTPA_TID]) - pdp.tid = mnl_attr_get_u64(tb[GTPA_TID]); + pdp.u.v0.tid = mnl_attr_get_u64(tb[GTPA_TID]); + if (tb[GTPA_I_TEI]) + pdp.u.v1.i_tei = mnl_attr_get_u32(tb[GTPA_I_TEI]); + if (tb[GTPA_O_TEI]) + pdp.u.v1.o_tei = mnl_attr_get_u32(tb[GTPA_O_TEI]); if (tb[GTPA_SGSN_ADDRESS]) { pdp.sgsn_addr.s_addr = mnl_attr_get_u32(tb[GTPA_SGSN_ADDRESS]); @@ -170,7 +219,12 @@ static int genl_gtp_attr_cb(const struct nlmsghdr *nlh, void *data) }
printf("version %u ", pdp.version); - printf("tid %llx ms_addr %s ", pdp.tid, inet_ntoa(pdp.ms_addr)); + if (pdp.version == GTP_V0) + printf("tid %"PRIu64" ms_addr %s ", + pdp.u.v0.tid, inet_ntoa(pdp.sgsn_addr)); + else if (pdp.version == GTP_V1) + printf("tei %u/%u ms_addr %s ", pdp.u.v1.i_tei, + pdp.u.v1.o_tei, inet_ntoa(pdp.sgsn_addr)); printf("sgsn_addr %s\n", inet_ntoa(pdp.sgsn_addr));
return MNL_CB_OK; @@ -197,8 +251,6 @@ list_tunnel(int argc, char *argv[], int genl_id, struct mnl_socket *nl) int main(int argc, char *argv[]) { struct mnl_socket *nl; - char buf[MNL_SOCKET_BUFFER_SIZE]; - unsigned int portid; int32_t genl_id; int ret;
diff --git a/libgtnl/tools/gtpnl.c b/libgtnl/tools/gtpnl.c deleted file mode 100644 index 252f0b1..0000000 --- a/libgtnl/tools/gtpnl.c +++ /dev/null @@ -1,35 +0,0 @@ -/* Helper functions */ - -/* (C) 2014 by sysmocom - s.f.m.c. GmbH - * Author: Pablo Neira Ayuso pablo@gnumonks.org - * - * All Rights Reserved - * - * 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. - * - * 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 Affero General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see http://www.gnu.org/licenses/. - * - */ - -#include <stdint.h> -#include <libmnl/libmnl.h> -#include <linux/gtp_nl.h> - -void gtp_build_payload(struct nlmsghdr *nlh, uint64_t tid, uint32_t ifidx, - uint32_t sgsn_addr, uint32_t ms_addr, uint32_t version) -{ - mnl_attr_put_u32(nlh, GTPA_VERSION, version); - mnl_attr_put_u32(nlh, GTPA_LINK, ifidx); - mnl_attr_put_u32(nlh, GTPA_SGSN_ADDRESS, sgsn_addr); - mnl_attr_put_u32(nlh, GTPA_MS_ADDRESS, ms_addr); - mnl_attr_put_u64(nlh, GTPA_TID, tid); -}
Signed-off-by: Andreas Schultz aschultz@tpip.net --- gtp.c | 152 ++++++++++++++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 125 insertions(+), 27 deletions(-)
diff --git a/gtp.c b/gtp.c index 457587e..48e98d8 100644 --- a/gtp.c +++ b/gtp.c @@ -452,6 +452,21 @@ ip4_route_output_gtp(struct net *net, struct flowi4 *fl4, return ip_route_output_key(net, fl4); }
+static struct dst_entry * +ip6_route_output_gtp(struct net *net, struct flowi6 *fl6, + const struct sock *sk, + struct in6_addr *daddr) +{ + memset(fl6, 0, sizeof(*fl6)); + fl6->flowi6_oif = sk->sk_bound_dev_if; + fl6->daddr = *daddr; + fl6->saddr = inet6_sk(sk)->saddr; + fl6->flowi6_tos = RT_CONN_FLAGS(sk); + fl6->flowi6_proto = sk->sk_protocol; + + return ip6_route_output(net, NULL, fl6); +} + static inline void gtp0_push_header(struct sk_buff *skb, struct pdp_ctx *pctx) { @@ -524,8 +539,12 @@ struct gtp_pktinfo { }; union { struct flowi4 fl4; + struct flowi6 fl6; + }; + union { + struct rtable *rt; + struct dst_entry *ndst; }; - struct rtable *rt; struct pdp_ctx *pctx; struct net_device *dev; }; @@ -634,11 +653,101 @@ err: return -EBADMSG; }
+static inline void +gtp_set_pktinfo_ipv6(struct gtp_pktinfo *pktinfo, struct sock *sk, + struct ipv6hdr *ip6h, + struct pdp_ctx *pctx, struct dst_entry *ndst, + struct flowi6 *fl6, struct net_device *dev) +{ + pktinfo->sk = sk; + pktinfo->ip6h = ip6h; + pktinfo->pctx = pctx; + pktinfo->ndst = ndst; + pktinfo->fl6 = *fl6; + pktinfo->dev = dev; +} + static int gtp_ip6_prepare_xmit(struct sk_buff *skb, struct net_device *dev, struct gtp_pktinfo *pktinfo) { - /* TODO IPV6 support */ + struct gtp_instance *gti = netdev_priv(dev); + struct sock *sk; + struct ipv6hdr *ipv6h; + struct pdp_ctx *pctx; + struct dst_entry *ndst; + struct flowi6 fl6; + int mtu; + + /* Read the IP destination address and resolve the PDP context. + * Prepend PDP header with TEI/TID from PDP ctx. + */ + ipv6h = ipv6_hdr(skb); + pctx = ipv6_pdp_find(gti, &ipv6h->daddr); + if (!pctx) { + netdev_dbg(dev, "no PDP ctx found for this packet, skip\n"); + return -ENOENT; + } + netdev_dbg(dev, "found PDP context %p\n", pctx); + + /* Obtain route for the new encapsulated GTP packet */ + switch (pctx->gtp_version) { + case GTP_V0: + sk = gti->sock0->sk; + break; + case GTP_V1: + sk = gti->sock1u->sk; + break; + default: + return -ENOENT; + } + + ndst = ip6_route_output_gtp(sock_net(sk), &fl6, + gti->sock0->sk, + &pctx->sgsn_addr.ip6); + if (IS_ERR(ndst)) { + netdev_dbg(dev, "no route to SSGN %pI6\n", + &pctx->sgsn_addr.ip6.s6_addr); + dev->stats.tx_carrier_errors++; + goto err; + } + + /* There is a routing loop */ + if (ndst->dev == dev) { + netdev_dbg(dev, "circular route to SSGN %pI6\n", + &pctx->sgsn_addr.ip6); + dev->stats.collisions++; + goto err_dst; + } + + skb_dst_drop(skb); + + mtu = dst_mtu(ndst) - dev->hard_header_len - + sizeof(struct ipv6hdr) - sizeof(struct udphdr); + switch (pctx->gtp_version) { + case GTP_V0: + mtu -= sizeof(struct gtp0_header); + break; + case GTP_V1: + mtu -= sizeof(struct gtp1_header); + break; + } + ndst->ops->update_pmtu(ndst, NULL, skb, mtu); + + if (!skb_is_gso(skb) && skb->len > mtu) { + netdev_dbg(dev, "packet too big, fragmentation needed\n"); + memset(IPCB(skb), 0, sizeof(*IPCB(skb))); + icmp_send(skb, ICMP_DEST_UNREACH, ICMP_FRAG_NEEDED, + htonl(mtu)); + goto err_dst; + } + + gtp_set_pktinfo_ipv6(pktinfo, sk, ipv6h, pctx, ndst, &fl6, dev); + return 0; +err_dst: + dst_release(ndst); +err: + return -EBADMSG; }
static inline int @@ -657,15 +766,23 @@ gtp_udp_tunnel_xmit(struct sk_buff *skb, __be16 port, }
static inline int -gtp_ip6tunnel_xmit(struct sk_buff *skb, struct gtp_pktinfo *pktinfo) +gtp_ip6tunnel_xmit(struct sk_buff *skb, __be16 port, + struct gtp_pktinfo *pktinfo) { - /* TODO IPV6 support */ + netdev_dbg(pktinfo->dev, "gtp -> IP src: %pI6 dst: %pI6\n", + &pktinfo->ip6h->saddr, &pktinfo->ip6h->daddr); + + return udp_tunnel6_xmit_skb(pktinfo->ndst, pktinfo->sk, skb, + pktinfo->ndst->dev, + &pktinfo->fl6.saddr, + &pktinfo->fl6.daddr, + 0, + ip6_dst_hoplimit(pktinfo->ndst), + port, port, false); }
static netdev_tx_t gtp_dev_xmit(struct sk_buff *skb, struct net_device *dev) { - struct udphdr *uh; - unsigned int payload_len; struct gtp_pktinfo pktinfo; unsigned int proto = ntohs(skb->protocol); int gtph_len, err = -EINVAL; @@ -713,28 +830,9 @@ static netdev_tx_t gtp_dev_xmit(struct sk_buff *skb, struct net_device *dev) case ETH_P_IP: err = gtp_udp_tunnel_xmit(skb, gtph_port, &pktinfo); break; - case ETH_P_IPV6: - /* Annotate length of the encapsulated packet */ - payload_len = skb->len; - - /* Push down and install the UDP header. */ - skb_push(skb, sizeof(struct udphdr)); - skb_reset_transport_header(skb); - - uh = udp_hdr(skb); - - uh->source = uh->dest = gtph_port; - uh->len = htons(sizeof(struct udphdr) + payload_len + gtph_len); - uh->check = 0;
- netdev_dbg(dev, "gtp -> UDP src: %u dst: %u (len %u)\n", - ntohs(uh->source), ntohs(uh->dest), ntohs(uh->len)); - - nf_reset(skb); - - netdev_dbg(dev, "Good, now packet leaving from GGSN to SGSN\n"); - - err = gtp_ip6tunnel_xmit(skb, &pktinfo); + case ETH_P_IPV6: + err = gtp_ip6tunnel_xmit(skb, gtph_port, &pktinfo); break; }
Signed-off-by: Andreas Schultz aschultz@tpip.net --- gtp.c | 1 + libgtnl/AUTHORS | 1 + 2 files changed, 2 insertions(+)
diff --git a/gtp.c b/gtp.c index 48e98d8..f569d62 100644 --- a/gtp.c +++ b/gtp.c @@ -3,6 +3,7 @@ /* (C) 2012-2014 by sysmocom - s.f.m.c. GmbH * Author: Harald Welte hwelte@sysmocom.de * Pablo Neira Ayuso pablo@gnumonks.org + * Andreas Schultz aschultz@travelping.com * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License diff --git a/libgtnl/AUTHORS b/libgtnl/AUTHORS index 7deed5b..649d40b 100644 --- a/libgtnl/AUTHORS +++ b/libgtnl/AUTHORS @@ -1 +1,2 @@ Pablo Neira Ayuso pablo@gnumonks.org +Andreas Schultz aschultz@travelping.com
Signed-off-by: Andreas Schultz aschultz@tpip.net --- gtp.c | 35 ++++++++++++++++++++++++++++------- 1 file changed, 28 insertions(+), 7 deletions(-)
diff --git a/gtp.c b/gtp.c index f569d62..e46638a 100644 --- a/gtp.c +++ b/gtp.c @@ -1178,24 +1178,45 @@ static int ipv4_pdp_add(struct net_device *dev, struct genl_info *info) }
if (found) { + struct pdp_ctx *repl_pctx; + if (info->nlhdr->nlmsg_flags & NLM_F_EXCL) return -EEXIST; - if (info->nlhdr->nlmsg_flags & NLM_F_REPLACE) - return -EOPNOTSUPP;
- ipv4_pdp_fill(pctx, info); + repl_pctx = kmemdup(pctx, sizeof(struct pdp_ctx), GFP_KERNEL); + if (repl_pctx == NULL) + return -ENOMEM; + + ipv4_pdp_fill(repl_pctx, info); + + /* only the SGSN can be changed */ + if (pctx->af != repl_pctx->af || + pctx->gtp_version != repl_pctx->gtp_version || + memcpy(&pctx->u, &repl_pctx->u, sizeof(pctx->u) != 0)) { + kfree(repl_pctx); + return -EINVAL; + } + + hlist_replace_rcu(&pctx->hlist_addr, &repl_pctx->hlist_addr); + hlist_replace_rcu(&pctx->hlist_tid, &repl_pctx->hlist_tid);
- if (pctx->gtp_version == GTP_V0) + kfree_rcu(pctx, rcu_head); + + if (repl_pctx->gtp_version == GTP_V0) netdev_dbg(dev, "GTPv0-U: update tunnel id = %llx (pdp %p)\n", - pctx->u.v0.tid, pctx); - else if (pctx->gtp_version == GTP_V1) + repl_pctx->u.v0.tid, repl_pctx); + else if (repl_pctx->gtp_version == GTP_V1) netdev_dbg(dev, "GTPv1-U: update tunnel id = %x/%x (pdp %p)\n", - pctx->u.v1.i_tei, pctx->u.v1.o_tei, pctx); + repl_pctx->u.v1.i_tei, + repl_pctx->u.v1.o_tei, repl_pctx);
return 0;
}
+ if (info->nlhdr->nlmsg_flags & NLM_F_REPLACE) + return -ENXIO; + pctx = kmalloc(sizeof(struct pdp_ctx), GFP_KERNEL); if (pctx == NULL) return -ENOMEM;