]> www.infradead.org Git - users/dwmw2/openconnect.git/commitdiff
Revamp MTU detection
authorDavid Woodhouse <dwmw2@infradead.org>
Fri, 24 May 2019 15:53:05 +0000 (16:53 +0100)
committerDavid Woodhouse <dwmw2@infradead.org>
Tue, 4 Jun 2019 00:20:42 +0000 (01:20 +0100)
Signed-off-by: David Woodhouse <dwmw2@infradead.org>
dtls.c
www/changelog.xml

diff --git a/dtls.c b/dtls.c
index 2f262934f0a3abaf16510d9cf1972c4d8e180052..f0a6cb567c8ea8323f2b832aa93eb9e3ad04f523 100644 (file)
--- a/dtls.c
+++ b/dtls.c
@@ -508,222 +508,172 @@ int dtls_mainloop(struct openconnect_info *vpninfo, int *timeout, int readable)
        return work_done;
 }
 
-#define MTU_ID_SIZE 4
-#define MTU_MAX_TRIES 10
-#define MTU_TIMEOUT_MS 2400
+/* This symbol is missing in glibc < 2.22 (bug 18643). */
+#if defined(__linux__) && !defined(HAVE_IPV6_PATHMTU)
+# define HAVE_IPV6_PATHMTU 1
+# define IPV6_PATHMTU 61
+#endif
+
+#define PKT_INTERVAL_MS 50
 
 /* Performs a binary search to detect MTU.
  * @buf: is preallocated with MTU size
- * @id: a unique ID for our DPD exchange
  *
  * Returns: new MTU or 0
  */
-static int detect_mtu_ipv4(struct openconnect_info *vpninfo, unsigned char *buf)
+static int probe_mtu(struct openconnect_info *vpninfo, unsigned char *buf)
 {
-       int max, min, cur, ret, orig_min;
+       int max, min, cur, ret, absolute_min, last;
        int tries = 0; /* Number of loops in bin search - includes resends */
-       char id[MTU_ID_SIZE];
+       uint32_t id, id_len;
+       struct timeval start_tv, now_tv, last_tv;
 
-       cur = max = vpninfo->ip_info.mtu;
-       orig_min = min = vpninfo->ip_info.mtu/2;
+       absolute_min = 576;
+       if (vpninfo->ip_info.addr6)
+               absolute_min = 1280;
 
-       vpn_progress(vpninfo, PRG_DEBUG,
-                    _("Initiating IPv4 MTU detection (min=%d, max=%d)\n"), min, max);
-
-       while (max > min) {
-               /* Common case will be that the negotiated MTU is correct.
-                  So try that first. Then search lower values. */
-               if (!tries)
-                       cur = max;
-               else
-                       cur = (min + max + 1) / 2;
-
-       next_rnd:
-               /* Generate unique ID */
-               if (openconnect_random(id, sizeof(id)) < 0)
-                       goto fail;
+       /* We'll assume that it is at least functional, and permits the bare
+        * minimum MTU for the protocol(s) it transports. All else is mad. */
+       min = absolute_min;
 
-       next_nornd:
-               if (tries++ >= MTU_MAX_TRIES) {
-                       if (orig_min == min) {
-                               /* Hm, we never got *anything* back successfully? */
-                               vpn_progress(vpninfo, PRG_ERR,
-                                            _("Too long time in MTU detect loop; assuming negotiated MTU.\n"));
-                               goto fail;
-                       } else {
-                               vpn_progress(vpninfo, PRG_ERR,
-                                            _("Too long time in MTU detect loop; MTU set to %d.\n"), min);
-                               return min;
-                       }
-               }
+       /* First send a probe at the configured maximum. Most of the time, this
+          one will probably work. */
+       last = cur = max = vpninfo->ip_info.mtu;
 
-               buf[0] = AC_PKT_DPD_OUT;
-               memcpy(&buf[1], id, sizeof(id));
+       if (max <= min)
+               goto fail;
 
-               vpn_progress(vpninfo, PRG_TRACE,
-                            _("Sending MTU DPD probe (%u bytes, min=%u, max=%u)\n"), cur, min, max);
-               ret = openconnect_dtls_write(vpninfo, buf, cur+1);
-               if (ret != cur+1) {
-                       vpn_progress(vpninfo, PRG_ERR,
-                                    _("Failed to send DPD request (%d %d)\n"), cur, ret);
-                       /* If it didn't even manage to send, it took basically zero time.
-                          So don't count it as a 'try' for the purpose of our timeout. */
-                       max = --cur;
-                       tries--;
-                       goto next_rnd;
-               }
+       /* Generate unique ID */
+       if (openconnect_random(&id, sizeof(id)) < 0)
+               goto fail;
 
-       reread:
-               memset(buf, 0, sizeof(id)+1);
+       vpn_progress(vpninfo, PRG_DEBUG,
+                    _("Initiating MTU detection (min=%d, max=%d)\n"), min, max);
 
-               ret = openconnect_dtls_read(vpninfo, buf, cur+1, MTU_TIMEOUT_MS);
-               if (ret > 0 && (buf[0] != AC_PKT_DPD_RESP || memcmp(&buf[1], id, sizeof(id)) != 0)) {
-                       vpn_progress(vpninfo, PRG_DEBUG,
-                            _("Received unexpected packet (%.2x) in MTU detection; skipping.\n"), (unsigned)buf[0]);
-                       goto reread;
-               }
+       gettimeofday(&start_tv, NULL);
+       last_tv = start_tv;
 
-               /* Timeout. Either it was too large, or it just got lost. Try again
-                * with a smaller value, but don't actually reduce 'max' because we
-                * don't *know* it was too large. */
-               if (ret == -ETIMEDOUT) {
-                       int next = (min + cur + 1) / 2;
+       while (1) {
+               int wait_ms;
 
-                       if (next < cur && next > min) {
-                               vpn_progress(vpninfo, PRG_DEBUG,
-                                            _("Timeout while waiting for DPD response; trying %d\n"),
-                                            next);
-                               cur = next;
-                               /* We don't set 'max' because we don't *know* it won't get through */
-                               goto next_rnd;
-                       } else {
-                               vpn_progress(vpninfo, PRG_DEBUG,
-                                            _("Timeout while waiting for DPD response; resending probe.\n"));
-                               goto next_nornd;
+#ifdef HAVE_IPV6_PATHMTU
+               if (vpninfo->peer_addr->sa_family == AF_INET6) {
+                       struct ip6_mtuinfo mtuinfo;
+                       socklen_t len = sizeof(mtuinfo);
+                       int newmax;
+
+                       if (getsockopt(vpninfo->dtls_fd, IPPROTO_IPV6, IPV6_PATHMTU, &mtuinfo, &len) >= 0) {
+                               newmax = mtuinfo.ip6m_mtu;
+                               if (newmax > 0) {
+                                       newmax = dtls_set_mtu(vpninfo, newmax) - /*ipv6*/40 - /*udp*/20 - /*oc dtls*/1;
+                                       if (absolute_min > newmax)
+                                               goto fail;
+                                       if (max > newmax)
+                                               max = newmax;
+                                       if (cur > newmax)
+                                               cur = newmax;
+                               }
                        }
                }
-
-               if (ret <= 0) {
-                       vpn_progress(vpninfo, PRG_ERR,
-                                    _("Failed to recv DPD request (%d)\n"), cur);
-                       goto fail;
-               }
-
-               vpn_progress(vpninfo, PRG_TRACE,
-                            _("Received MTU DPD probe (%u bytes of %u)\n"), ret, cur);
-
-               /* If we reached the max, success */
-               if (cur == max)
-                       break;
-
-               min = cur;
-       }
-
-       return cur;
- fail:
-       return 0;
-}
-
-#if defined(IPPROTO_IPV6)
-
-/* This symbol is missing in glibc < 2.22 (bug 18643). */
-#if defined(__linux__) && !defined(HAVE_IPV6_PATHMTU)
-# define HAVE_IPV6_PATHMTU 1
-# define IPV6_PATHMTU 61
 #endif
 
-/* Verifies whether current MTU is ok, or detects new MTU using IPv6's ICMP6 messages
- * @buf: is preallocated with MTU size
- * @id: a unique ID for our DPD exchange
- *
- * Returns: new MTU or 0
- */
-static int detect_mtu_ipv6(struct openconnect_info *vpninfo, unsigned char *buf)
-{
-       int max, cur, ret;
-       int max_resends = 5; /* maximum number of resends */
-       char id[MTU_ID_SIZE];
-       unsigned re_use_rnd_val = 0;
-
-       cur = max = vpninfo->ip_info.mtu;
-
-       vpn_progress(vpninfo, PRG_DEBUG,
-            _("Initiating IPv6 MTU detection\n"));
-
-       while(max_resends-- > 0) {
-               /* generate unique ID */
-               if (!re_use_rnd_val) {
-                       if (openconnect_random(id, sizeof(id)) < 0)
-                               goto fail;
-               } else {
-                       re_use_rnd_val = 0;
-               }
-
                buf[0] = AC_PKT_DPD_OUT;
-               memcpy(&buf[1], id, sizeof(id));
+               id_len = id + cur;
+               memcpy(&buf[1], &id_len, sizeof(id_len));
 
                vpn_progress(vpninfo, PRG_TRACE,
-                    _("Sending MTU DPD probe (%u bytes)\n"), cur);
-               ret = openconnect_dtls_write(vpninfo, buf, cur+1);
-               if (ret != cur+1) {
+                            _("Sending MTU DPD probe (%u bytes)\n"), cur);
+               ret = openconnect_dtls_write(vpninfo, buf, cur + 1);
+               if (ret != cur + 1) {
                        vpn_progress(vpninfo, PRG_ERR,
-                                    _("Failed to send DPD request (%d)\n"), cur);
-                       goto mtu6_fail;
+                                    _("Failed to send DPD request (%d %d)\n"), cur, ret);
+                       if (cur == max) {
+                               max = --cur;
+                               if (cur >= absolute_min)
+                                       continue;
+                       }
+                       goto fail;
+               }
+               if (last == cur)
+                       tries++;
+               else {
+                       tries = 0;
+                       last = cur;
                }
 
- reread:
                memset(buf, 0, sizeof(id)+1);
-               ret = openconnect_dtls_read(vpninfo, buf, cur+1, MTU_TIMEOUT_MS);
-
-               /* timeout, probably our original request was lost,
-                * let's resend the DPD */
-               if (ret == -ETIMEDOUT) {
-                       vpn_progress(vpninfo, PRG_DEBUG,
-                            _("Timeout while waiting for DPD response; resending probe.\n"));
-                       re_use_rnd_val = 1;
-                       continue;
+       keep_waiting:
+               gettimeofday(&now_tv, NULL);
+
+               if (now_tv.tv_sec > start_tv.tv_sec + 10) {
+                        if (absolute_min == min) {
+                                /* Hm, we never got *anything* back successfully? */
+                                vpn_progress(vpninfo, PRG_ERR,
+                                             _("Too long time in MTU detect loop; assuming negotiated MTU.\n"));
+                                goto fail;
+                        } else {
+                                vpn_progress(vpninfo, PRG_ERR,
+                                             _("Too long time in MTU detect loop; MTU set to %d.\n"), min);
+                               ret = min;
+                               goto out;
+                        }
                }
 
-               /* something unexpected was received, let's ignore it */
-               if (ret > 0 && (buf[0] != AC_PKT_DPD_RESP || memcmp(&buf[1], id, sizeof(id)) != 0)) {
+
+               wait_ms = PKT_INTERVAL_MS -
+                       ((now_tv.tv_sec - last_tv.tv_sec) * 1000) -
+                       ((now_tv.tv_usec - last_tv.tv_usec) / 1000);
+               if (wait_ms < 0 || wait_ms > PKT_INTERVAL_MS)
+                       wait_ms = PKT_INTERVAL_MS;
+
+               ret = openconnect_dtls_read(vpninfo, buf, max+1, wait_ms);
+               if (ret > 0 && (buf[0] != AC_PKT_DPD_RESP || !memcpy(&id_len, &buf[1], sizeof(id_len)) ||
+                               id_len != id + ret - 1)) {
                        vpn_progress(vpninfo, PRG_DEBUG,
-                            _("Received unexpected packet (%.2x) in MTU detection; skipping.\n"), (unsigned)buf[0]);
-                       goto reread;
+                                    _("Received unexpected packet (%.2x) in MTU detection; skipping.\n"), (unsigned)buf[0]);
+                       goto keep_waiting;
                }
 
-               vpn_progress(vpninfo, PRG_TRACE,
-                    _("Received MTU DPD probe (%u bytes)\n"), cur);
+               if (ret == -ETIMEDOUT) {
+                       if (tries >= 6) {
+                               vpn_progress(vpninfo, PRG_DEBUG,
+                                            _("No response to size %u after %d tries; declare MTU is %u\n"),
+                                            last, tries, min);
+                               ret = min;
+                               goto out;
+                       }
+               } else if (ret < 0) {
+                       vpn_progress(vpninfo, PRG_ERR,
+                                    _("Failed to recv DPD request (%d)\n"), ret);
+                       goto fail;
+               } else if (ret > 0) {
+                       vpn_progress(vpninfo, PRG_TRACE,
+                                    _("Received MTU DPD probe (%u bytes)\n"), ret - 1);
+                       ret--;
+                       tries = 0;
+               }
 
-               /* we received what we expected, move on */
-               break;
-       }
+               if (ret == max)
+                       goto out;
 
-#ifdef HAVE_IPV6_PATHMTU
-       /* If we received back our DPD packet, do nothing; otherwise,
-        * attempt to get MTU from the ICMP6 packet we received */
-       if (ret <= 0) {
-               struct ip6_mtuinfo mtuinfo;
-               socklen_t len = sizeof(mtuinfo);
-               max = 0;
-               vpn_progress(vpninfo, PRG_ERR,
-                    _("Failed to recv DPD request (%d)\n"), cur);
- mtu6_fail:
-               if (getsockopt(vpninfo->dtls_fd, IPPROTO_IPV6, IPV6_PATHMTU, &mtuinfo, &len) >= 0) {
-                       max = mtuinfo.ip6m_mtu;
-                       if (max >= 0 && max < cur) {
-                               cur = dtls_set_mtu(vpninfo, max) - /*ipv6*/40 - /*udp*/20 - /*oc dtls*/1;
+               if (ret > min) {
+                       min = ret;
+                       if (min >= last) {
+                               cur = (min + max + 1) / 2;
+                       } else {
+                               cur = (min + last + 1) / 2;
                        }
+               } else {
+                       cur = (min + last + 1) / 2;
                }
        }
-#else
- mtu6_fail:
-#endif
-
-       return cur;
  fail:
-       return 0;
+       ret = 0;
+ out:
+       return ret;
 }
-#endif
+
+
 
 void dtls_detect_mtu(struct openconnect_info *vpninfo)
 {
@@ -731,7 +681,7 @@ void dtls_detect_mtu(struct openconnect_info *vpninfo)
        int prev_mtu = vpninfo->ip_info.mtu;
        unsigned char *buf;
 
-       if (vpninfo->ip_info.mtu < 1+MTU_ID_SIZE)
+       if (vpninfo->ip_info.mtu < 1 + sizeof(uint32_t))
                return;
 
        /* detect MTU */
@@ -741,17 +691,9 @@ void dtls_detect_mtu(struct openconnect_info *vpninfo)
                return;
        }
 
-       if (vpninfo->peer_addr->sa_family == AF_INET) { /* IPv4 */
-               mtu = detect_mtu_ipv4(vpninfo, buf);
-               if (mtu == 0)
-                       goto skip_mtu;
-#if defined(IPPROTO_IPV6)
-       } else if (vpninfo->peer_addr->sa_family == AF_INET6) { /* IPv6 */
-               mtu = detect_mtu_ipv6(vpninfo, buf);
-               if (mtu == 0)
-                       goto skip_mtu;
-#endif
-       }
+       mtu = probe_mtu(vpninfo, buf);
+       if (mtu == 0)
+               goto skip_mtu;
 
        vpninfo->ip_info.mtu = mtu;
        if (prev_mtu != vpninfo->ip_info.mtu) {
index e349dc461cdbfe3b5143149c5b6292691ad862a9..b5fc071566d5a7736b9006b2876f6cc6d8890a12 100644 (file)
@@ -15,7 +15,7 @@
 <ul>
    <li><b>OpenConnect HEAD</b>
      <ul>
-       <li><i>No changelog entries yet</i></li>
+       <li>Rework DTLS MTU detection. (<a href="https://gitlab.com/openconnect/openconnect/issues/10">#10</a>)</li>
      </ul><br/>
   </li>
   <li><b><a href="ftp://ftp.infradead.org/pub/openconnect/openconnect-8.03.tar.gz">OpenConnect v8.03</a></b>