fou, fou6: ICMP error handlers for FoU and GUE
authorStefano Brivio <sbrivio@redhat.com>
Thu, 8 Nov 2018 11:19:23 +0000 (12:19 +0100)
committerDavid S. Miller <davem@davemloft.net>
Fri, 9 Nov 2018 01:13:08 +0000 (17:13 -0800)
As the destination port in FoU and GUE receiving sockets doesn't
necessarily match the remote destination port, we can't associate errors
to the encapsulating tunnels with a socket lookup -- we need to blindly
try them instead. This means we don't even know if we are handling errors
for FoU or GUE without digging into the packets.

Hence, implement a single handler for both, one for IPv4 and one for IPv6,
that will check whether the packet that generated the ICMP error used a
direct IP encapsulation or if it had a GUE header, and send the error to
the matching protocol handler, if any.

Signed-off-by: Stefano Brivio <sbrivio@redhat.com>
Reviewed-by: Sabrina Dubroca <sd@queasysnail.net>
Signed-off-by: David S. Miller <davem@davemloft.net>
net/ipv4/fou.c
net/ipv4/protocol.c
net/ipv6/fou6.c

index 500a59906b8719eb40fc3f37a0dc535b10b3069e..0d0ad19ecb8700712148536723a09d6df035f78c 100644 (file)
@@ -3,6 +3,7 @@
 #include <linux/socket.h>
 #include <linux/skbuff.h>
 #include <linux/ip.h>
+#include <linux/icmp.h>
 #include <linux/udp.h>
 #include <linux/types.h>
 #include <linux/kernel.h>
@@ -1003,15 +1004,82 @@ static int gue_build_header(struct sk_buff *skb, struct ip_tunnel_encap *e,
        return 0;
 }
 
+static int gue_err_proto_handler(int proto, struct sk_buff *skb, u32 info)
+{
+       const struct net_protocol *ipprot = rcu_dereference(inet_protos[proto]);
+
+       if (ipprot && ipprot->err_handler) {
+               if (!ipprot->err_handler(skb, info))
+                       return 0;
+       }
+
+       return -ENOENT;
+}
+
+static int gue_err(struct sk_buff *skb, u32 info)
+{
+       int transport_offset = skb_transport_offset(skb);
+       struct guehdr *guehdr;
+       size_t optlen;
+       int ret;
+
+       if (skb->len < sizeof(struct udphdr) + sizeof(struct guehdr))
+               return -EINVAL;
+
+       guehdr = (struct guehdr *)&udp_hdr(skb)[1];
+
+       switch (guehdr->version) {
+       case 0: /* Full GUE header present */
+               break;
+       case 1: {
+               /* Direct encasulation of IPv4 or IPv6 */
+               skb_set_transport_header(skb, -(int)sizeof(struct icmphdr));
+
+               switch (((struct iphdr *)guehdr)->version) {
+               case 4:
+                       ret = gue_err_proto_handler(IPPROTO_IPIP, skb, info);
+                       goto out;
+#if IS_ENABLED(CONFIG_IPV6)
+               case 6:
+                       ret = gue_err_proto_handler(IPPROTO_IPV6, skb, info);
+                       goto out;
+#endif
+               default:
+                       ret = -EOPNOTSUPP;
+                       goto out;
+               }
+       }
+       default: /* Undefined version */
+               return -EOPNOTSUPP;
+       }
+
+       if (guehdr->control)
+               return -ENOENT;
+
+       optlen = guehdr->hlen << 2;
+
+       if (validate_gue_flags(guehdr, optlen))
+               return -EINVAL;
+
+       skb_set_transport_header(skb, -(int)sizeof(struct icmphdr));
+       ret = gue_err_proto_handler(guehdr->proto_ctype, skb, info);
+
+out:
+       skb_set_transport_header(skb, transport_offset);
+       return ret;
+}
+
 
 static const struct ip_tunnel_encap_ops fou_iptun_ops = {
        .encap_hlen = fou_encap_hlen,
        .build_header = fou_build_header,
+       .err_handler = gue_err,
 };
 
 static const struct ip_tunnel_encap_ops gue_iptun_ops = {
        .encap_hlen = gue_encap_hlen,
        .build_header = gue_build_header,
+       .err_handler = gue_err,
 };
 
 static int ip_tunnel_encap_add_fou_ops(void)
index 32a691b7ce2c7e79eab6491b52457a11e666f7d3..92d249e053be6720c235a98c99b8c514ff525d92 100644 (file)
@@ -29,6 +29,7 @@
 #include <net/protocol.h>
 
 struct net_protocol __rcu *inet_protos[MAX_INET_PROTOS] __read_mostly;
+EXPORT_SYMBOL(inet_protos);
 const struct net_offload __rcu *inet_offloads[MAX_INET_PROTOS] __read_mostly;
 EXPORT_SYMBOL(inet_offloads);
 
index 6de3c04b0f30f9b761920e268dce93beb0a02bfb..bd675c61deb1fcc6bfe9f332637590451327fd05 100644 (file)
@@ -4,6 +4,7 @@
 #include <linux/skbuff.h>
 #include <linux/ip.h>
 #include <linux/udp.h>
+#include <linux/icmpv6.h>
 #include <linux/types.h>
 #include <linux/kernel.h>
 #include <net/fou.h>
@@ -69,14 +70,87 @@ static int gue6_build_header(struct sk_buff *skb, struct ip_tunnel_encap *e,
        return 0;
 }
 
+static int gue6_err_proto_handler(int proto, struct sk_buff *skb,
+                                 struct inet6_skb_parm *opt,
+                                 u8 type, u8 code, int offset, u32 info)
+{
+       const struct inet6_protocol *ipprot;
+
+       ipprot = rcu_dereference(inet6_protos[proto]);
+       if (ipprot && ipprot->err_handler) {
+               if (!ipprot->err_handler(skb, opt, type, code, offset, info))
+                       return 0;
+       }
+
+       return -ENOENT;
+}
+
+static int gue6_err(struct sk_buff *skb, struct inet6_skb_parm *opt,
+                   u8 type, u8 code, int offset, __be32 info)
+{
+       int transport_offset = skb_transport_offset(skb);
+       struct guehdr *guehdr;
+       size_t optlen;
+       int ret;
+
+       if (skb->len < sizeof(struct udphdr) + sizeof(struct guehdr))
+               return -EINVAL;
+
+       guehdr = (struct guehdr *)&udp_hdr(skb)[1];
+
+       switch (guehdr->version) {
+       case 0: /* Full GUE header present */
+               break;
+       case 1: {
+               /* Direct encasulation of IPv4 or IPv6 */
+               skb_set_transport_header(skb, -(int)sizeof(struct icmp6hdr));
+
+               switch (((struct iphdr *)guehdr)->version) {
+               case 4:
+                       ret = gue6_err_proto_handler(IPPROTO_IPIP, skb, opt,
+                                                    type, code, offset, info);
+                       goto out;
+               case 6:
+                       ret = gue6_err_proto_handler(IPPROTO_IPV6, skb, opt,
+                                                    type, code, offset, info);
+                       goto out;
+               default:
+                       ret = -EOPNOTSUPP;
+                       goto out;
+               }
+       }
+       default: /* Undefined version */
+               return -EOPNOTSUPP;
+       }
+
+       if (guehdr->control)
+               return -ENOENT;
+
+       optlen = guehdr->hlen << 2;
+
+       if (validate_gue_flags(guehdr, optlen))
+               return -EINVAL;
+
+       skb_set_transport_header(skb, -(int)sizeof(struct icmp6hdr));
+       ret = gue6_err_proto_handler(guehdr->proto_ctype, skb,
+                                    opt, type, code, offset, info);
+
+out:
+       skb_set_transport_header(skb, transport_offset);
+       return ret;
+}
+
+
 static const struct ip6_tnl_encap_ops fou_ip6tun_ops = {
        .encap_hlen = fou_encap_hlen,
        .build_header = fou6_build_header,
+       .err_handler = gue6_err,
 };
 
 static const struct ip6_tnl_encap_ops gue_ip6tun_ops = {
        .encap_hlen = gue_encap_hlen,
        .build_header = gue6_build_header,
+       .err_handler = gue6_err,
 };
 
 static int ip6_tnl_encap_add_fou_ops(void)