dtls.c 19.2 KB
Newer Older
David Woodhouse's avatar
David Woodhouse committed
1
/*
2
 * OpenConnect (SSL + DTLS) VPN client
David Woodhouse's avatar
David Woodhouse committed
3
 *
David Woodhouse's avatar
David Woodhouse committed
4
 * Copyright © 2008-2015 Intel Corporation.
David Woodhouse's avatar
David Woodhouse committed
5
 *
6 7 8
 * Author: David Woodhouse <dwmw2@infradead.org>
 *
 * This program is free software; you can redistribute it and/or
David Woodhouse's avatar
David Woodhouse committed
9
 * modify it under the terms of the GNU Lesser General Public License
10
 * version 2.1, as published by the Free Software Foundation.
David Woodhouse's avatar
David Woodhouse committed
11
 *
12
 * This program is distributed in the hope that it will be useful, but
David Woodhouse's avatar
David Woodhouse committed
13 14 15
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
David Woodhouse's avatar
David Woodhouse committed
16 17
 */

18 19
#include <config.h>

David Woodhouse's avatar
David Woodhouse committed
20
#include <errno.h>
David Woodhouse's avatar
David Woodhouse committed
21 22
#include <sys/types.h>
#include <unistd.h>
23
#include <fcntl.h>
Dirk Hohndel's avatar
Dirk Hohndel committed
24
#include <string.h>
25 26 27
#include <errno.h>
#include <stdlib.h>
#include <stdio.h>
Kevin Cernekee's avatar
Kevin Cernekee committed
28 29 30 31
#ifndef _WIN32
#include <netinet/in.h>
#include <sys/socket.h>
#endif
David Woodhouse's avatar
David Woodhouse committed
32

33
#include "openconnect-internal.h"
David Woodhouse's avatar
David Woodhouse committed
34

David Woodhouse's avatar
David Woodhouse committed
35 36 37 38 39 40
/*
 * The master-secret is generated randomly by the client. The server
 * responds with a DTLS Session-ID. These, done over the HTTPS
 * connection, are enough to 'resume' a DTLS session, bypassing all
 * the normal setup of a normal DTLS connection.
 *
41 42 43 44
 * Cisco use a version of the protocol which predates RFC4347, but
 * isn't quite the same as the pre-RFC version of the protocol which
 * was in OpenSSL 0.9.8e -- it includes backports of some later
 * OpenSSL patches.
45
 *
Nick Andrew's avatar
Nick Andrew committed
46 47
 * The openssl/ directory of this source tree should contain both a
 * small patch against OpenSSL 0.9.8e to make it support Cisco's
48 49
 * snapshot of the protocol, and a larger patch against newer OpenSSL
 * which gives us an option to use the old protocol again.
50
 *
51 52 53 54 55 56
 * Cisco's server also seems to respond to the official version of the
 * protocol, with a change in the ChangeCipherSpec packet which implies
 * that it does know the difference and isn't just repeating the version
 * number seen in the ClientHello. But although I can make the handshake
 * complete by hacking tls1_mac() to use the _old_ protocol version
 * number when calculating the MAC, the server still seems to be ignoring
57 58
 * my subsequent data packets. So we use the old protocol, which is what
 * their clients use anyway.
Nick Andrew's avatar
Nick Andrew committed
59
 */
David Woodhouse's avatar
David Woodhouse committed
60

61
#if defined(OPENCONNECT_OPENSSL)
62 63
#define DTLS_SEND SSL_write
#define DTLS_RECV SSL_read
64
#elif defined(OPENCONNECT_GNUTLS)
65 66 67 68
#define DTLS_SEND gnutls_record_send
#define DTLS_RECV gnutls_record_recv
#endif

69 70
char *openconnect_bin2hex(const char *prefix, const uint8_t *data, unsigned len)
{
71 72
	struct oc_text_buf *buf;
	char *p = NULL;
73

74
	buf = buf_alloc();
75 76
	if (prefix)
		buf_append(buf, "%s", prefix);
77 78 79 80 81
	buf_append_hex(buf, data, len);

	if (!buf_error(buf)) {
		p = buf->data;
		buf->data = NULL;
82
	}
83 84 85
	buf_free(buf);

	return p;
86 87
}

88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106
char *openconnect_bin2base64(const char *prefix, const uint8_t *data, unsigned len)
{
	struct oc_text_buf *buf;
	char *p = NULL;

	buf = buf_alloc();
	if (prefix)
		buf_append(buf, "%s", prefix);
	buf_append_base64(buf, data, len);

	if (!buf_error(buf)) {
		p = buf->data;
		buf->data = NULL;
	}
	buf_free(buf);

	return p;
}

107
static int connect_dtls_socket(struct openconnect_info *vpninfo)
108
{
109
	int dtls_fd, ret;
110

111 112 113 114 115 116 117
	/* Sanity check for the removal of new_dtls_{fd,ssl} */
	if (vpninfo->dtls_fd != -1) {
		vpn_progress(vpninfo, PRG_ERR, _("DTLS connection attempted with an existing fd\n"));
		vpninfo->dtls_attempt_period = 0;
		return -EINVAL;
	}

118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137
	if (!vpninfo->dtls_addr) {
		vpn_progress(vpninfo, PRG_ERR, _("No DTLS address\n"));
		vpninfo->dtls_attempt_period = 0;
		return -EINVAL;
	}

	if (!vpninfo->dtls_cipher) {
		/* We probably didn't offer it any ciphers it liked */
		vpn_progress(vpninfo, PRG_ERR, _("Server offered no DTLS cipher option\n"));
		vpninfo->dtls_attempt_period = 0;
		return -EINVAL;
	}

	if (vpninfo->proxy) {
		/* XXX: Theoretically, SOCKS5 proxies can do UDP too */
		vpn_progress(vpninfo, PRG_ERR, _("No DTLS when connected via proxy\n"));
		vpninfo->dtls_attempt_period = 0;
		return -EINVAL;
	}

138 139
	dtls_fd = udp_connect(vpninfo);
	if (dtls_fd < 0)
140 141 142 143 144
		return -EINVAL;


	ret = start_dtls_handshake(vpninfo, dtls_fd);
	if (ret) {
145
		closesocket(dtls_fd);
146 147 148
		return ret;
	}

149 150
	vpninfo->dtls_state = DTLS_CONNECTING;

151
	vpninfo->dtls_fd = dtls_fd;
152 153 154
	monitor_fd_new(vpninfo, dtls);
	monitor_read_fd(vpninfo, dtls);
	monitor_except_fd(vpninfo, dtls);
155 156 157 158 159 160

	time(&vpninfo->new_dtls_started);

	return dtls_try_handshake(vpninfo);
}

161
void dtls_close(struct openconnect_info *vpninfo)
162 163
{
	if (vpninfo->dtls_ssl) {
David Woodhouse's avatar
David Woodhouse committed
164
		dtls_ssl_free(vpninfo);
165
		closesocket(vpninfo->dtls_fd);
166 167 168
		unmonitor_read_fd(vpninfo, dtls);
		unmonitor_write_fd(vpninfo, dtls);
		unmonitor_except_fd(vpninfo, dtls);
169 170 171
		vpninfo->dtls_ssl = NULL;
		vpninfo->dtls_fd = -1;
	}
172
	vpninfo->dtls_state = DTLS_SLEEPING;
173
}
David Woodhouse's avatar
David Woodhouse committed
174

175
static int dtls_reconnect(struct openconnect_info *vpninfo)
176
{
177
	dtls_close(vpninfo);
178 179 180 181

	if (vpninfo->dtls_state == DTLS_DISABLED)
		return -EINVAL;

182
	vpninfo->dtls_state = DTLS_SLEEPING;
183
	return connect_dtls_socket(vpninfo);
184 185
}

186
int dtls_setup(struct openconnect_info *vpninfo, int dtls_attempt_period)
David Woodhouse's avatar
David Woodhouse committed
187
{
188
	struct oc_vpn_option *dtls_opt = vpninfo->dtls_options;
David Woodhouse's avatar
David Woodhouse committed
189
	int dtls_port = 0;
David Woodhouse's avatar
David Woodhouse committed
190

191 192 193
	if (vpninfo->dtls_state == DTLS_DISABLED)
		return -EINVAL;

194 195 196 197
	vpninfo->dtls_attempt_period = dtls_attempt_period;
	if (!dtls_attempt_period)
		return 0;

David Woodhouse's avatar
David Woodhouse committed
198
	while (dtls_opt) {
199
		vpn_progress(vpninfo, PRG_DEBUG,
200 201
			     _("DTLS option %s : %s\n"),
			     dtls_opt->option, dtls_opt->value);
David Woodhouse's avatar
David Woodhouse committed
202

203
		if (!strcmp(dtls_opt->option, "X-DTLS-Port")) {
David Woodhouse's avatar
David Woodhouse committed
204
			dtls_port = atol(dtls_opt->value);
205
		} else if (!strcmp(dtls_opt->option, "X-DTLS-Keepalive")) {
206
			vpninfo->dtls_times.keepalive = atol(dtls_opt->value);
207
		} else if (!strcmp(dtls_opt->option, "X-DTLS-DPD")) {
David Woodhouse's avatar
David Woodhouse committed
208 209 210
			int j = atol(dtls_opt->value);
			if (j && (!vpninfo->dtls_times.dpd || j < vpninfo->dtls_times.dpd))
				vpninfo->dtls_times.dpd = j;
211
		} else if (!strcmp(dtls_opt->option, "X-DTLS-Rekey-Method")) {
212 213 214 215 216 217
			if (!strcmp(dtls_opt->value, "new-tunnel"))
				vpninfo->dtls_times.rekey_method = REKEY_TUNNEL;
			else if (!strcmp(dtls_opt->value, "ssl"))
				vpninfo->dtls_times.rekey_method = REKEY_SSL;
			else
				vpninfo->dtls_times.rekey_method = REKEY_NONE;
218
		} else if (!strcmp(dtls_opt->option, "X-DTLS-Rekey-Time")) {
219
			vpninfo->dtls_times.rekey = atol(dtls_opt->value);
David Woodhouse's avatar
David Woodhouse committed
220
		}
Nick Andrew's avatar
Nick Andrew committed
221

David Woodhouse's avatar
David Woodhouse committed
222 223
		dtls_opt = dtls_opt->next;
	}
224
	if (!dtls_port) {
225
		vpninfo->dtls_attempt_period = 0;
David Woodhouse's avatar
David Woodhouse committed
226
		return -EINVAL;
227
	}
228 229
	if (vpninfo->dtls_times.rekey <= 0)
		vpninfo->dtls_times.rekey_method = REKEY_NONE;
230

231
	if (udp_sockaddr(vpninfo, dtls_port)) {
232
		vpninfo->dtls_attempt_period = 0;
233 234
		return -EINVAL;
	}
235 236
	if (connect_dtls_socket(vpninfo))
		return -EINVAL;
237

238
	vpn_progress(vpninfo, PRG_DEBUG,
239
		     _("DTLS initialised. DPD %d, Keepalive %d\n"),
240
		     vpninfo->dtls_times.dpd, vpninfo->dtls_times.keepalive);
241 242

	return 0;
David Woodhouse's avatar
David Woodhouse committed
243 244
}

245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276
int udp_tos_update(struct openconnect_info *vpninfo, struct pkt *pkt)
{
	int tos;

	/* Extract TOS field from IP header (IPv4 and IPv6 differ) */
	switch(pkt->data[0] >> 4) {
		case 4:
			tos = pkt->data[1];
			break;
		case 6:
			tos = (load_be16(pkt->data) >> 4) & 0xff;
			break;
		default:
			vpn_progress(vpninfo, PRG_ERR,
				     _("Unknown packet (len %d) received: %02x %02x %02x %02x...\n"),
				     pkt->len, pkt->data[0], pkt->data[1], pkt->data[2], pkt->data[3]);
			return -EINVAL;
	}

	/* set the actual value */
	if (tos != vpninfo->dtls_tos_current) {
		vpn_progress(vpninfo, PRG_DEBUG, _("TOS this: %d, TOS last: %d\n"),
			     tos, vpninfo->dtls_tos_current);
		if (setsockopt(vpninfo->dtls_fd, vpninfo->dtls_tos_proto,
			       vpninfo->dtls_tos_optname, (void *)&tos, sizeof(tos)))
			vpn_perror(vpninfo, _("UDP setsockopt"));
		else
			vpninfo->dtls_tos_current = tos;
	}
	return 0;
}

277
int dtls_mainloop(struct openconnect_info *vpninfo, int *timeout, int readable)
278
{
279
	int work_done = 0;
280
	char magic_pkt;
281

282 283 284 285 286 287
	if (vpninfo->dtls_need_reconnect) {
		vpninfo->dtls_need_reconnect = 0;
		dtls_reconnect(vpninfo);
		return 1;
	}

288
	if (vpninfo->dtls_state == DTLS_CONNECTING) {
289
		dtls_try_handshake(vpninfo);
290
		vpninfo->delay_tunnel_reason = "DTLS MTU detection";
291
		return 0;
292
	}
293 294 295 296 297

	if (vpninfo->dtls_state == DTLS_SLEEPING) {
		int when = vpninfo->new_dtls_started + vpninfo->dtls_attempt_period - time(NULL);

		if (when <= 0) {
298
			vpn_progress(vpninfo, PRG_DEBUG, _("Attempt new DTLS connection\n"));
299 300
			if (connect_dtls_socket(vpninfo) < 0)
				*timeout = 1000;
301 302 303
		} else if ((when * 1000) < *timeout) {
			*timeout = when * 1000;
		}
304
		return 0;
305
	}
306

307
	while (readable) {
308
		int len = MAX(16384, vpninfo->ip_info.mtu);
309 310
		unsigned char *buf;

David Woodhouse's avatar
David Woodhouse committed
311 312 313
		if (!vpninfo->dtls_pkt) {
			vpninfo->dtls_pkt = malloc(sizeof(struct pkt) + len);
			if (!vpninfo->dtls_pkt) {
314
				vpn_progress(vpninfo, PRG_ERR, _("Allocation failed\n"));
315 316 317 318
				break;
			}
		}

David Woodhouse's avatar
David Woodhouse committed
319
		buf = vpninfo->dtls_pkt->data - 1;
320
		len = DTLS_RECV(vpninfo->dtls_ssl, buf, len + 1);
321 322
		if (len <= 0)
			break;
323

324
		vpn_progress(vpninfo, PRG_TRACE,
325 326
			     _("Received DTLS packet 0x%02x of %d bytes\n"),
			     buf[0], len);
327

328
		vpninfo->dtls_times.last_rx = time(NULL);
329

330
		switch (buf[0]) {
David Woodhouse's avatar
David Woodhouse committed
331
		case AC_PKT_DATA:
David Woodhouse's avatar
David Woodhouse committed
332 333 334
			vpninfo->dtls_pkt->len = len - 1;
			queue_packet(&vpninfo->incoming_queue, vpninfo->dtls_pkt);
			vpninfo->dtls_pkt = NULL;
335 336 337
			work_done = 1;
			break;

338
		case AC_PKT_DPD_OUT:
339
			vpn_progress(vpninfo, PRG_DEBUG, _("Got DTLS DPD request\n"));
340 341 342

			/* FIXME: What if the packet doesn't get through? */
			magic_pkt = AC_PKT_DPD_RESP;
343
			if (DTLS_SEND(vpninfo->dtls_ssl, &magic_pkt, 1) != 1)
344 345
				vpn_progress(vpninfo, PRG_ERR,
					     _("Failed to send DPD response. Expect disconnect\n"));
346 347
			continue;

David Woodhouse's avatar
David Woodhouse committed
348
		case AC_PKT_DPD_RESP:
349
			vpn_progress(vpninfo, PRG_DEBUG, _("Got DTLS DPD response\n"));
350 351
			break;

352
		case AC_PKT_KEEPALIVE:
353
			vpn_progress(vpninfo, PRG_DEBUG, _("Got DTLS Keepalive\n"));
354 355
			break;

356 357 358 359 360 361
		case AC_PKT_COMPRESSED:
			if (!vpninfo->dtls_compr) {
				vpn_progress(vpninfo, PRG_ERR,
					     _("Compressed DTLS packet received when compression not enabled\n"));
				goto unknown_pkt;
			}
362 363
			decompress_and_queue_packet(vpninfo, vpninfo->dtls_compr,
						    vpninfo->dtls_pkt->data, len - 1);
364
			break;
365
		default:
366
			vpn_progress(vpninfo, PRG_ERR,
367 368
				     _("Unknown DTLS packet type %02x, len %d\n"),
				     buf[0], len);
369 370
			if (1) {
				/* Some versions of OpenSSL have bugs with receiving out-of-order
Nick Andrew's avatar
Nick Andrew committed
371
				 * packets. Not only do they wrongly decide to drop packets if
372 373 374 375 376
				 * two packets get swapped in transit, but they also _fail_ to
				 * drop the packet in non-blocking mode; instead they return
				 * the appropriate length of garbage. So don't abort... for now. */
				break;
			} else {
377
			unknown_pkt:
378 379 380 381
				vpninfo->quit_reason = "Unknown packet received";
				return 1;
			}

382
		}
383
	}
384

385
	switch (keepalive_action(&vpninfo->dtls_times, timeout)) {
386 387 388
	case KA_REKEY: {
		int ret;

389
		vpn_progress(vpninfo, PRG_INFO, _("DTLS rekey due\n"));
390

391 392 393 394 395
		if (vpninfo->dtls_times.rekey_method == REKEY_SSL) {
			time(&vpninfo->new_dtls_started);
			vpninfo->dtls_state = DTLS_CONNECTING;
			ret = dtls_try_handshake(vpninfo);
			if (ret) {
396 397
				vpn_progress(vpninfo, PRG_ERR, _("DTLS Rehandshake failed; reconnecting.\n"));
				return connect_dtls_socket(vpninfo);
398
			}
399 400
		}

401
		return 1;
402
	}
403 404

	case KA_DPD_DEAD:
405
		vpn_progress(vpninfo, PRG_ERR, _("DTLS Dead Peer Detection detected dead peer!\n"));
406
		/* Fall back to SSL, and start a new DTLS connection */
407
		dtls_reconnect(vpninfo);
408
		return 1;
409 410

	case KA_DPD:
411
		vpn_progress(vpninfo, PRG_DEBUG, _("Send DTLS DPD\n"));
412

413
		magic_pkt = AC_PKT_DPD_OUT;
414 415 416 417
		if (DTLS_SEND(vpninfo->dtls_ssl, &magic_pkt, 1) != 1)
			vpn_progress(vpninfo, PRG_ERR,
				     _("Failed to send DPD request. Expect disconnect\n"));

418 419 420 421
		/* last_dpd will just have been set */
		vpninfo->dtls_times.last_tx = vpninfo->dtls_times.last_dpd;
		work_done = 1;
		break;
422

423 424 425
	case KA_KEEPALIVE:
		/* No need to send an explicit keepalive
		   if we have real data to send */
426
		if (vpninfo->outgoing_queue.head)
427
			break;
428

429
		vpn_progress(vpninfo, PRG_DEBUG, _("Send DTLS Keepalive\n"));
430

431
		magic_pkt = AC_PKT_KEEPALIVE;
432 433 434
		if (DTLS_SEND(vpninfo->dtls_ssl, &magic_pkt, 1) != 1)
			vpn_progress(vpninfo, PRG_ERR,
				     _("Failed to send keepalive request. Expect disconnect\n"));
435 436 437
		time(&vpninfo->dtls_times.last_tx);
		work_done = 1;
		break;
438

439 440
	case KA_NONE:
		;
441 442
	}

443
	/* Service outgoing packet queue */
444
	unmonitor_write_fd(vpninfo, dtls);
445 446
	while (vpninfo->outgoing_queue.head) {
		struct pkt *this = dequeue_packet(&vpninfo->outgoing_queue);
447
		struct pkt *send_pkt = this;
448
		int ret;
449

450 451
		/* If TOS optname is set, we want to copy the TOS/TCLASS header
		   to the outer UDP packet */
452 453
		if (vpninfo->dtls_tos_optname)
			udp_tos_update(vpninfo, this);
454

455
		/* One byte of header */
456
		this->cstp.hdr[7] = AC_PKT_DATA;
Nick Andrew's avatar
Nick Andrew committed
457

458 459 460 461 462 463
		/* We can compress into vpninfo->deflate_pkt unless CSTP
		 * currently has a compressed packet pending — which it
		 * shouldn't if DTLS is active. */
		if (vpninfo->dtls_compr &&
		    vpninfo->current_ssl_pkt != vpninfo->deflate_pkt &&
		    !compress_packet(vpninfo, vpninfo->dtls_compr, this)) {
464
				send_pkt = vpninfo->deflate_pkt;
465
				send_pkt->cstp.hdr[7] = AC_PKT_COMPRESSED;
466 467
		}

468
#ifdef OPENCONNECT_OPENSSL
469
		ret = SSL_write(vpninfo->dtls_ssl, &send_pkt->cstp.hdr[7], send_pkt->len + 1);
470 471 472
		if (ret <= 0) {
			ret = SSL_get_error(vpninfo->dtls_ssl, ret);

473
			if (ret == SSL_ERROR_WANT_WRITE) {
474
				monitor_write_fd(vpninfo, dtls);
475
				requeue_packet(&vpninfo->outgoing_queue, this);
476 477 478
			} else if (ret != SSL_ERROR_WANT_READ) {
				/* If it's a real error, kill the DTLS connection and
				   requeue the packet to be sent over SSL */
479
				vpn_progress(vpninfo, PRG_ERR,
480 481
					     _("DTLS got write error %d. Falling back to SSL\n"),
					     ret);
482
				openconnect_report_ssl_errors(vpninfo);
483
				dtls_reconnect(vpninfo);
484
				requeue_packet(&vpninfo->outgoing_queue, this);
485
				work_done = 1;
486
			}
487
			return work_done;
488
		}
489
#else /* GnuTLS */
490
		ret = gnutls_record_send(vpninfo->dtls_ssl, &send_pkt->cstp.hdr[7], send_pkt->len + 1);
491
		if (ret <= 0) {
492
			if (ret != GNUTLS_E_AGAIN && ret != GNUTLS_E_INTERRUPTED) {
493 494 495
				vpn_progress(vpninfo, PRG_ERR,
					     _("DTLS got write error: %s. Falling back to SSL\n"),
					     gnutls_strerror(ret));
496
				dtls_reconnect(vpninfo);
497
				work_done = 1;
498 499
			} else {
				/* Wake me up when it becomes writeable */
500
				monitor_write_fd(vpninfo, dtls);
501
			}
502

503
			requeue_packet(&vpninfo->outgoing_queue, this);
504
			return work_done;
505 506
		}
#endif
507
		time(&vpninfo->dtls_times.last_tx);
508
		vpn_progress(vpninfo, PRG_TRACE,
509
			     _("Sent DTLS packet of %d bytes; DTLS send returned %d\n"),
510
			     this->len, ret);
511
		free(this);
512
	}
513

514
	return work_done;
515
}
516

David Woodhouse's avatar
David Woodhouse committed
517 518 519 520 521 522 523
/* 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
524

525 526 527 528 529
/* Performs a binary search to detect MTU.
 * @buf: is preallocated with MTU size
 *
 * Returns: new MTU or 0
 */
David Woodhouse's avatar
David Woodhouse committed
530
static int probe_mtu(struct openconnect_info *vpninfo, unsigned char *buf)
531
{
David Woodhouse's avatar
David Woodhouse committed
532
	int max, min, cur, ret, absolute_min, last;
533
	int tries = 0; /* Number of loops in bin search - includes resends */
David Woodhouse's avatar
David Woodhouse committed
534 535
	uint32_t id, id_len;
	struct timeval start_tv, now_tv, last_tv;
536

David Woodhouse's avatar
David Woodhouse committed
537 538 539
	absolute_min = 576;
	if (vpninfo->ip_info.addr6)
		absolute_min = 1280;
540

David Woodhouse's avatar
David Woodhouse committed
541 542 543
	/* 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;
544

David Woodhouse's avatar
David Woodhouse committed
545 546 547
	/* First send a probe at the configured maximum. Most of the time, this
	   one will probably work. */
	last = cur = max = vpninfo->ip_info.mtu;
548

David Woodhouse's avatar
David Woodhouse committed
549 550
	if (max <= min)
		goto fail;
551

David Woodhouse's avatar
David Woodhouse committed
552 553 554
	/* Generate unique ID */
	if (openconnect_random(&id, sizeof(id)) < 0)
		goto fail;
555

David Woodhouse's avatar
David Woodhouse committed
556 557
	vpn_progress(vpninfo, PRG_DEBUG,
		     _("Initiating MTU detection (min=%d, max=%d)\n"), min, max);
558

David Woodhouse's avatar
David Woodhouse committed
559 560
	gettimeofday(&start_tv, NULL);
	last_tv = start_tv;
561

David Woodhouse's avatar
David Woodhouse committed
562 563
	while (1) {
		int wait_ms;
564

David Woodhouse's avatar
David Woodhouse committed
565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581
#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;
				}
582
			}
583
		}
584 585
#endif

586
		buf[0] = AC_PKT_DPD_OUT;
David Woodhouse's avatar
David Woodhouse committed
587 588
		id_len = id + cur;
		memcpy(&buf[1], &id_len, sizeof(id_len));
589

590
		vpn_progress(vpninfo, PRG_TRACE,
David Woodhouse's avatar
David Woodhouse committed
591 592 593
			     _("Sending MTU DPD probe (%u bytes)\n"), cur);
		ret = openconnect_dtls_write(vpninfo, buf, cur + 1);
		if (ret != cur + 1) {
594
			vpn_progress(vpninfo, PRG_ERR,
David Woodhouse's avatar
David Woodhouse committed
595 596 597 598 599 600 601 602 603 604 605 606 607
				     _("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;
608 609 610
		}

		memset(buf, 0, sizeof(id)+1);
David Woodhouse's avatar
David Woodhouse committed
611 612 613 614 615 616 617 618 619 620 621 622 623 624 625
	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;
                        }
626
		}
627

David Woodhouse's avatar
David Woodhouse committed
628 629 630 631 632 633 634 635 636 637

		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)) {
638
			vpn_progress(vpninfo, PRG_DEBUG,
David Woodhouse's avatar
David Woodhouse committed
639 640
				     _("Received unexpected packet (%.2x) in MTU detection; skipping.\n"), (unsigned)buf[0]);
			goto keep_waiting;
641 642
		}

David Woodhouse's avatar
David Woodhouse committed
643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660
		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;
		}
661

David Woodhouse's avatar
David Woodhouse committed
662 663
		if (ret == max)
			goto out;
664

David Woodhouse's avatar
David Woodhouse committed
665 666 667 668 669 670
		if (ret > min) {
			min = ret;
			if (min >= last) {
				cur = (min + max + 1) / 2;
			} else {
				cur = (min + last + 1) / 2;
671
			}
David Woodhouse's avatar
David Woodhouse committed
672 673
		} else {
			cur = (min + last + 1) / 2;
674 675 676
		}
	}
 fail:
David Woodhouse's avatar
David Woodhouse committed
677 678 679
	ret = 0;
 out:
	return ret;
680
}
David Woodhouse's avatar
David Woodhouse committed
681 682


683

684
void dtls_detect_mtu(struct openconnect_info *vpninfo)
685
{
686
	int mtu;
687 688 689
	int prev_mtu = vpninfo->ip_info.mtu;
	unsigned char *buf;

David Woodhouse's avatar
David Woodhouse committed
690
	if (vpninfo->ip_info.mtu < 1 + sizeof(uint32_t))
691 692 693 694 695 696 697 698 699
		return;

	/* detect MTU */
	buf = calloc(1, 1 + vpninfo->ip_info.mtu);
	if (!buf) {
		vpn_progress(vpninfo, PRG_ERR, _("Allocation failed\n"));
		return;
	}

David Woodhouse's avatar
David Woodhouse committed
700 701 702
	mtu = probe_mtu(vpninfo, buf);
	if (mtu == 0)
		goto skip_mtu;
703 704 705 706 707 708 709 710 711 712 713 714 715

	vpninfo->ip_info.mtu = mtu;
	if (prev_mtu != vpninfo->ip_info.mtu) {
		vpn_progress(vpninfo, PRG_INFO,
		     _("Detected MTU of %d bytes (was %d)\n"), vpninfo->ip_info.mtu, prev_mtu);
	} else {
		vpn_progress(vpninfo, PRG_DEBUG,
		     _("No change in MTU after detection (was %d)\n"), prev_mtu);
	}

 skip_mtu:
	free(buf);
}