ipv6: ICMPV6: add response to ICMPV6 RFC 8335 PROBE messages
authorAndreas Roeseler <andreas.a.roeseler@gmail.com>
Sat, 26 Jun 2021 14:07:46 +0000 (09:07 -0500)
committerDavid S. Miller <davem@davemloft.net>
Mon, 28 Jun 2021 21:29:45 +0000 (14:29 -0700)
This patch builds off of commit 2b246b2569cd2ac6ff700d0dce56b8bae29b1842
and adds functionality to respond to ICMPV6 PROBE requests.

Add icmp_build_probe function to construct PROBE requests for both
ICMPV4 and ICMPV6.

Modify icmpv6_rcv to detect ICMPV6 PROBE messages and call the
icmpv6_echo_reply handler.

Modify icmpv6_echo_reply to build a PROBE response message based on the
queried interface.

This patch has been tested using a branch of the iputils git repo which can
be found here: https://github.com/Juniper-Clinic-2020/iputils/tree/probe-request

Signed-off-by: Andreas Roeseler <andreas.a.roeseler@gmail.com>
Reviewed-by: Willem de Bruijn <willemb@google.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
include/net/icmp.h
net/ipv4/icmp.c
net/ipv6/icmp.c

index fd84adc47963353b295c29b83dd8c977edf2a527..caddf4a59ad118603e8d11d77accf87200f8c917 100644 (file)
@@ -57,5 +57,6 @@ int icmp_rcv(struct sk_buff *skb);
 int icmp_err(struct sk_buff *skb, u32 info);
 int icmp_init(void);
 void icmp_out_count(struct net *net, unsigned char type);
+bool icmp_build_probe(struct sk_buff *skb, struct icmphdr *icmphdr);
 
 #endif /* _ICMP_H */
index 0a57f1892e7e0e5366bcbe51bee2cd4c69bf9388..c695d294a5dfad1e1a2ebe2ccc837f2f3be15dd3 100644 (file)
@@ -993,14 +993,8 @@ static bool icmp_redirect(struct sk_buff *skb)
 
 static bool icmp_echo(struct sk_buff *skb)
 {
-       struct icmp_ext_hdr *ext_hdr, _ext_hdr;
-       struct icmp_ext_echo_iio *iio, _iio;
        struct icmp_bxm icmp_param;
-       struct net_device *dev;
-       char buff[IFNAMSIZ];
        struct net *net;
-       u16 ident_len;
-       u8 status;
 
        net = dev_net(skb_dst(skb)->dev);
        /* should there be an ICMP stat for ignored echos? */
@@ -1013,20 +1007,46 @@ static bool icmp_echo(struct sk_buff *skb)
        icmp_param.data_len        = skb->len;
        icmp_param.head_len        = sizeof(struct icmphdr);
 
-       if (icmp_param.data.icmph.type == ICMP_ECHO) {
+       if (icmp_param.data.icmph.type == ICMP_ECHO)
                icmp_param.data.icmph.type = ICMP_ECHOREPLY;
-               goto send_reply;
-       }
-       if (!net->ipv4.sysctl_icmp_echo_enable_probe)
+       else if (!icmp_build_probe(skb, &icmp_param.data.icmph))
                return true;
+
+       icmp_reply(&icmp_param, skb);
+       return true;
+}
+
+/*     Helper for icmp_echo and icmpv6_echo_reply.
+ *     Searches for net_device that matches PROBE interface identifier
+ *             and builds PROBE reply message in icmphdr.
+ *
+ *     Returns false if PROBE responses are disabled via sysctl
+ */
+
+bool icmp_build_probe(struct sk_buff *skb, struct icmphdr *icmphdr)
+{
+       struct icmp_ext_hdr *ext_hdr, _ext_hdr;
+       struct icmp_ext_echo_iio *iio, _iio;
+       struct net *net = dev_net(skb->dev);
+       struct net_device *dev;
+       char buff[IFNAMSIZ];
+       u16 ident_len;
+       u8 status;
+
+       if (!net->ipv4.sysctl_icmp_echo_enable_probe)
+               return false;
+
        /* We currently only support probing interfaces on the proxy node
         * Check to ensure L-bit is set
         */
-       if (!(ntohs(icmp_param.data.icmph.un.echo.sequence) & 1))
-               return true;
+       if (!(ntohs(icmphdr->un.echo.sequence) & 1))
+               return false;
        /* Clear status bits in reply message */
-       icmp_param.data.icmph.un.echo.sequence &= htons(0xFF00);
-       icmp_param.data.icmph.type = ICMP_EXT_ECHOREPLY;
+       icmphdr->un.echo.sequence &= htons(0xFF00);
+       if (icmphdr->type == ICMP_EXT_ECHO)
+               icmphdr->type = ICMP_EXT_ECHOREPLY;
+       else
+               icmphdr->type = ICMPV6_EXT_ECHO_REPLY;
        ext_hdr = skb_header_pointer(skb, 0, sizeof(_ext_hdr), &_ext_hdr);
        /* Size of iio is class_type dependent.
         * Only check header here and assign length based on ctype in the switch statement
@@ -1087,8 +1107,8 @@ static bool icmp_echo(struct sk_buff *skb)
                goto send_mal_query;
        }
        if (!dev) {
-               icmp_param.data.icmph.code = ICMP_EXT_CODE_NO_IF;
-               goto send_reply;
+               icmphdr->code = ICMP_EXT_CODE_NO_IF;
+               return true;
        }
        /* Fill bits in reply message */
        if (dev->flags & IFF_UP)
@@ -1098,14 +1118,13 @@ static bool icmp_echo(struct sk_buff *skb)
        if (!list_empty(&rcu_dereference(dev->ip6_ptr)->addr_list))
                status |= ICMP_EXT_ECHOREPLY_IPV6;
        dev_put(dev);
-       icmp_param.data.icmph.un.echo.sequence |= htons(status);
-send_reply:
-       icmp_reply(&icmp_param, skb);
-               return true;
+       icmphdr->un.echo.sequence |= htons(status);
+       return true;
 send_mal_query:
-       icmp_param.data.icmph.code = ICMP_EXT_CODE_MAL_QUERY;
-       goto send_reply;
+       icmphdr->code = ICMP_EXT_CODE_MAL_QUERY;
+       return true;
 }
+EXPORT_SYMBOL_GPL(icmp_build_probe);
 
 /*
  *     Handle ICMP Timestamp requests.
index e8398ffb5e35c1279ebb69573a6232d2840776ac..a7c31ab67c5d6a8d8bea964af45e821861c28171 100644 (file)
@@ -725,6 +725,7 @@ static void icmpv6_echo_reply(struct sk_buff *skb)
        struct ipcm6_cookie ipc6;
        u32 mark = IP6_REPLY_MARK(net, skb->mark);
        bool acast;
+       u8 type;
 
        if (ipv6_addr_is_multicast(&ipv6_hdr(skb)->daddr) &&
            net->ipv6.sysctl.icmpv6_echo_ignore_multicast)
@@ -740,8 +741,13 @@ static void icmpv6_echo_reply(struct sk_buff *skb)
            !(net->ipv6.sysctl.anycast_src_echo_reply && acast))
                saddr = NULL;
 
+       if (icmph->icmp6_type == ICMPV6_EXT_ECHO_REQUEST)
+               type = ICMPV6_EXT_ECHO_REPLY;
+       else
+               type = ICMPV6_ECHO_REPLY;
+
        memcpy(&tmp_hdr, icmph, sizeof(tmp_hdr));
-       tmp_hdr.icmp6_type = ICMPV6_ECHO_REPLY;
+       tmp_hdr.icmp6_type = type;
 
        memset(&fl6, 0, sizeof(fl6));
        if (net->ipv6.sysctl.flowlabel_reflect & FLOWLABEL_REFLECT_ICMPV6_ECHO_REPLIES)
@@ -752,7 +758,7 @@ static void icmpv6_echo_reply(struct sk_buff *skb)
        if (saddr)
                fl6.saddr = *saddr;
        fl6.flowi6_oif = icmp6_iif(skb);
-       fl6.fl6_icmp_type = ICMPV6_ECHO_REPLY;
+       fl6.fl6_icmp_type = type;
        fl6.flowi6_mark = mark;
        fl6.flowi6_uid = sock_net_uid(net, NULL);
        security_skb_classify_flow(skb, flowi6_to_flowi_common(&fl6));
@@ -783,13 +789,17 @@ static void icmpv6_echo_reply(struct sk_buff *skb)
 
        msg.skb = skb;
        msg.offset = 0;
-       msg.type = ICMPV6_ECHO_REPLY;
+       msg.type = type;
 
        ipcm6_init_sk(&ipc6, np);
        ipc6.hlimit = ip6_sk_dst_hoplimit(np, &fl6, dst);
        ipc6.tclass = ipv6_get_dsfield(ipv6_hdr(skb));
        ipc6.sockc.mark = mark;
 
+       if (icmph->icmp6_type == ICMPV6_EXT_ECHO_REQUEST)
+               if (!icmp_build_probe(skb, (struct icmphdr *)&tmp_hdr))
+                       goto out_dst_release;
+
        if (ip6_append_data(sk, icmpv6_getfrag, &msg,
                            skb->len + sizeof(struct icmp6hdr),
                            sizeof(struct icmp6hdr), &ipc6, &fl6,
@@ -911,6 +921,11 @@ static int icmpv6_rcv(struct sk_buff *skb)
                if (!net->ipv6.sysctl.icmpv6_echo_ignore_all)
                        icmpv6_echo_reply(skb);
                break;
+       case ICMPV6_EXT_ECHO_REQUEST:
+               if (!net->ipv6.sysctl.icmpv6_echo_ignore_all &&
+                   net->ipv4.sysctl_icmp_echo_enable_probe)
+                       icmpv6_echo_reply(skb);
+               break;
 
        case ICMPV6_ECHO_REPLY:
                success = ping_rcv(skb);