Skip to content

Commit

Permalink
Revamp MTU detection
Browse files Browse the repository at this point in the history
Signed-off-by: David Woodhouse <dwmw2@infradead.org>
  • Loading branch information
dwmw2 committed Jun 4, 2019
1 parent 347060a commit 0120a25
Show file tree
Hide file tree
Showing 2 changed files with 131 additions and 189 deletions.
318 changes: 130 additions & 188 deletions dtls.c
Expand Up @@ -508,230 +508,180 @@ 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)
{
int mtu = vpninfo->ip_info.mtu;
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 */
Expand All @@ -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) {
Expand Down
2 changes: 1 addition & 1 deletion www/changelog.xml
Expand Up @@ -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>
Expand Down

0 comments on commit 0120a25

Please sign in to comment.