On Fri, Dec 11, 2020 at 4:29 AM Jonas Bonn <jo...@norrbonn.se> wrote: > > 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 <jo...@norrbonn.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 <pa...@netfilter.org> > + * (C) 2020 by Jonas Bonn <jo...@norrbonn.se> > * > * Author: Harald Welte <hwe...@sysmocom.de> > * Pablo Neira Ayuso <pa...@netfilter.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); I don't see a need to set inner header on receive path, we are any ways removing outer header from this packet in same function.
> + 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; > + iptunnel_pull_header() can set the protocol, so it would be better to pass the correct 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)); > No need to call skb_tunnel_rx() given iptunnel_pull_header() function is already called and it does take care of clearing the context. > 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; > +} > + IPv6 related functionality needs to be protected by IS_ENABLED(CONFIG_IPV6). > 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 >