Add new configuration of GTP interfaces that allow specifying a port to listen on (as opposed to having to get sockets from a userspace control plane). This allows GTP interfaces to be configured and the data path tested without requiring a GTP-C daemon.
Signed-off-by: Tom Herbert <t...@quantonium.net> --- drivers/net/gtp.c | 215 ++++++++++++++++++++++++++++++++++++----------- include/uapi/linux/gtp.h | 5 ++ 2 files changed, 169 insertions(+), 51 deletions(-) diff --git a/drivers/net/gtp.c b/drivers/net/gtp.c index 1c580df4cfc5..dc1fcd3034af 100644 --- a/drivers/net/gtp.c +++ b/drivers/net/gtp.c @@ -93,6 +93,9 @@ struct gtp_dev { struct sock *sk0; struct sock *sk1u; + struct socket *sock0; + struct socket *sock1u; + struct net_device *dev; unsigned int role; @@ -451,26 +454,33 @@ static void gtp_encap_destroy(struct sock *sk) } } -static void gtp_encap_disable_sock(struct sock *sk) +static void gtp_encap_release(struct gtp_dev *gtp) { - if (!sk) - return; + if (gtp->sk0) { + if (gtp->sock0) { + udp_tunnel_sock_release(gtp->sock0); + gtp->sock0 = NULL; + } else { + gtp_encap_destroy(gtp->sk0); + } - gtp_encap_destroy(sk); -} + gtp->sk0 = NULL; + } -static void gtp_encap_disable(struct gtp_dev *gtp) -{ - gtp_encap_disable_sock(gtp->sk0); - gtp_encap_disable_sock(gtp->sk1u); + if (gtp->sk1u) { + if (gtp->sock1u) { + udp_tunnel_sock_release(gtp->sock1u); + gtp->sock1u = NULL; + } else { + gtp_encap_destroy(gtp->sk1u); + } + + gtp->sk1u = NULL; + } } static int gtp_dev_init(struct net_device *dev) { - struct gtp_dev *gtp = netdev_priv(dev); - - gtp->dev = dev; - dev->tstats = netdev_alloc_pcpu_stats(struct pcpu_sw_netstats); if (!dev->tstats) return -ENOMEM; @@ -482,7 +492,8 @@ static void gtp_dev_uninit(struct net_device *dev) { struct gtp_dev *gtp = netdev_priv(dev); - gtp_encap_disable(gtp); + gtp_encap_release(gtp); + free_percpu(dev->tstats); } @@ -751,6 +762,8 @@ static void gtp_link_setup(struct net_device *dev) sizeof(struct udphdr) + sizeof(struct gtp0_header); + gtp->dev = dev; + gro_cells_init(>p->gro_cells, dev); } @@ -764,13 +777,19 @@ static int gtp_newlink(struct net *src_net, struct net_device *dev, struct netlink_ext_ack *extack) { unsigned int role = GTP_ROLE_GGSN; + bool have_fd, have_ports; bool is_ipv6 = false; struct gtp_dev *gtp; struct gtp_net *gn; int hashsize, err; - if (!data[IFLA_GTP_FD0] && !data[IFLA_GTP_FD1]) + have_fd = !!data[IFLA_GTP_FD0] || !!data[IFLA_GTP_FD1]; + have_ports = !!data[IFLA_GTP_PORT0] || !!data[IFLA_GTP_PORT1]; + + if (!(have_fd ^ have_ports)) { + /* Either got fd(s) or port(s) */ return -EINVAL; + } if (data[IFLA_GTP_ROLE]) { role = nla_get_u32(data[IFLA_GTP_ROLE]); @@ -831,7 +850,7 @@ static int gtp_newlink(struct net *src_net, struct net_device *dev, out_hashtable: gtp_hashtable_free(gtp); out_encap: - gtp_encap_disable(gtp); + gtp_encap_release(gtp); return err; } @@ -840,7 +859,7 @@ static void gtp_dellink(struct net_device *dev, struct list_head *head) struct gtp_dev *gtp = netdev_priv(dev); gro_cells_destroy(>p->gro_cells); - gtp_encap_disable(gtp); + gtp_encap_release(gtp); gtp_hashtable_free(gtp); list_del_rcu(>p->list); unregister_netdevice_queue(dev, head); @@ -851,6 +870,8 @@ static const struct nla_policy gtp_policy[IFLA_GTP_MAX + 1] = { [IFLA_GTP_FD1] = { .type = NLA_U32 }, [IFLA_GTP_PDP_HASHSIZE] = { .type = NLA_U32 }, [IFLA_GTP_ROLE] = { .type = NLA_U32 }, + [IFLA_GTP_PORT0] = { .type = NLA_U16 }, + [IFLA_GTP_PORT1] = { .type = NLA_U16 }, }; static int gtp_validate(struct nlattr *tb[], struct nlattr *data[], @@ -949,11 +970,35 @@ static void gtp_hashtable_free(struct gtp_dev *gtp) kfree(gtp->tid_hash); } -static struct sock *gtp_encap_enable_socket(int fd, int type, - struct gtp_dev *gtp, - bool is_ipv6) +static int gtp_encap_enable_sock(struct socket *sock, int type, + struct gtp_dev *gtp) { struct udp_tunnel_sock_cfg tuncfg = {NULL}; + + switch (type) { + case UDP_ENCAP_GTP0: + tuncfg.encap_rcv = gtp0_udp_encap_recv; + break; + case UDP_ENCAP_GTP1U: + tuncfg.encap_rcv = gtp1u_udp_encap_recv; + break; + default: + pr_debug("Unknown encap type %u\n", type); + return -EINVAL; + } + + tuncfg.sk_user_data = gtp; + tuncfg.encap_type = type; + tuncfg.encap_destroy = gtp_encap_destroy; + + setup_udp_tunnel_sock(sock_net(sock->sk), sock, &tuncfg); + + return 0; +} + +static struct sock *gtp_encap_enable_fd(int fd, int type, struct gtp_dev *gtp, + bool is_ipv6) +{ struct socket *sock; struct sock *sk; int err; @@ -986,60 +1031,128 @@ static struct sock *gtp_encap_enable_socket(int fd, int type, sk = sock->sk; sock_hold(sk); - switch (type) { - case UDP_ENCAP_GTP0: - tuncfg.encap_rcv = gtp0_udp_encap_recv; - break; - case UDP_ENCAP_GTP1U: - tuncfg.encap_rcv = gtp1u_udp_encap_recv; - break; - default: - pr_debug("Unknown encap type %u\n", type); - sk = ERR_PTR(-EINVAL); - goto out_sock; - } - - tuncfg.sk_user_data = gtp; - tuncfg.encap_type = type; - tuncfg.encap_destroy = gtp_encap_destroy; - - setup_udp_tunnel_sock(sock_net(sock->sk), sock, &tuncfg); + err = gtp_encap_enable_sock(sock, type, gtp); + if (err < 0) + sk = ERR_PTR(err); out_sock: sockfd_put(sock); return sk; } +static struct socket *gtp_create_sock(struct net *net, bool ipv6, + __be16 port, u32 flags) +{ + struct socket *sock; + struct udp_port_cfg udp_conf; + int err; + + memset(&udp_conf, 0, sizeof(udp_conf)); + +#if GTP_IPV6 + if (ipv6) { + udp_conf.family = AF_INET6; + udp_conf.ipv6_v6only = 1; + } else { + udp_conf.family = AF_INET; + } +#else + udp_conf.family = AF_INET; +#endif + + udp_conf.local_udp_port = port; + + /* Open UDP socket */ + err = udp_sock_create(net, &udp_conf, &sock); + if (err) + return ERR_PTR(err); + + return sock; +} + static int gtp_encap_enable(struct gtp_dev *gtp, struct nlattr *data[], bool is_ipv6) { + int err; + + struct socket *sock0 = NULL, *sock1u = NULL; struct sock *sk0 = NULL, *sk1u = NULL; if (data[IFLA_GTP_FD0]) { u32 fd0 = nla_get_u32(data[IFLA_GTP_FD0]); - sk0 = gtp_encap_enable_socket(fd0, UDP_ENCAP_GTP0, gtp, - is_ipv6); - if (IS_ERR(sk0)) - return PTR_ERR(sk0); + sk0 = gtp_encap_enable_fd(fd0, UDP_ENCAP_GTP0, gtp, is_ipv6); + if (IS_ERR(sk0)) { + err = PTR_ERR(sk0); + sk0 = NULL; + goto out_err; + } + } else if (data[IFLA_GTP_PORT0]) { + __be16 port = nla_get_u16(data[IFLA_GTP_PORT0]); + + sock0 = gtp_create_sock(dev_net(gtp->dev), is_ipv6, port, 0); + if (IS_ERR(sock0)) { + err = PTR_ERR(sock0); + sock0 = NULL; + goto out_err; + } + + err = gtp_encap_enable_sock(sock0, UDP_ENCAP_GTP0, gtp); + if (err) + goto out_err; } if (data[IFLA_GTP_FD1]) { u32 fd1 = nla_get_u32(data[IFLA_GTP_FD1]); - sk1u = gtp_encap_enable_socket(fd1, UDP_ENCAP_GTP1U, gtp, - is_ipv6); + sk1u = gtp_encap_enable_fd(fd1, UDP_ENCAP_GTP1U, gtp, is_ipv6); if (IS_ERR(sk1u)) { - if (sk0) - gtp_encap_disable_sock(sk0); - return PTR_ERR(sk1u); + err = PTR_ERR(sk1u); + sk1u = NULL; + goto out_err; + } + } else if (data[IFLA_GTP_PORT1]) { + __be16 port = nla_get_u16(data[IFLA_GTP_PORT1]); + + sock1u = gtp_create_sock(dev_net(gtp->dev), is_ipv6, port, 0); + if (IS_ERR(sock1u)) { + err = PTR_ERR(sock1u); + sock1u = NULL; + goto out_err; } + + err = gtp_encap_enable_sock(sock1u, UDP_ENCAP_GTP1U, gtp); + if (err) + goto out_err; + } + + if (sock0) { + gtp->sock0 = sock0; + gtp->sk0 = sock0->sk; + } else { + gtp->sk0 = sk0; } - gtp->sk0 = sk0; - gtp->sk1u = sk1u; + if (sock1u) { + gtp->sock1u = sock1u; + gtp->sk1u = sock1u->sk; + } else { + gtp->sk1u = sk1u; + } return 0; + +out_err: + if (sk0) + gtp_encap_destroy(sk0); + if (sk1u) + gtp_encap_destroy(sk1u); + if (sock0) + udp_tunnel_sock_release(sock0); + if (sock1u) + udp_tunnel_sock_release(sock1u); + + return err; } static struct gtp_dev *gtp_find_dev(struct net *src_net, struct nlattr *nla[]) @@ -1624,8 +1737,8 @@ static const struct genl_ops gtp_genl_ops[] = { }; static struct genl_family gtp_genl_family __ro_after_init = { - .name = "gtp", - .version = 0, + .name = GTP_GENL_NAME, + .version = GTP_GENL_VERSION, .hdrsize = 0, .maxattr = GTPA_MAX, .netnsok = true, diff --git a/include/uapi/linux/gtp.h b/include/uapi/linux/gtp.h index 8eec519fa754..0da18aa88be8 100644 --- a/include/uapi/linux/gtp.h +++ b/include/uapi/linux/gtp.h @@ -9,6 +9,11 @@ enum gtp_genl_cmds { GTP_CMD_MAX, }; +/* NETLINK_GENERIC related info + */ +#define GTP_GENL_NAME "gtp" +#define GTP_GENL_VERSION 0 + enum gtp_version { GTP_V0 = 0, GTP_V1, -- 2.11.0