2 * OpenConnect (SSL + DTLS) VPN client
4 * Copyright © 2008 Intel Corporation.
5 * Copyright © 2008 Nick Andrew <nick@nick-andrew.net>
7 * Author: David Woodhouse <dwmw2@infradead.org>
9 * This program is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU Lesser General Public License
11 * version 2.1, as published by the Free Software Foundation.
13 * This program is distributed in the hope that it will be useful, but
14 * WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * Lesser General Public License for more details.
18 * You should have received a copy of the GNU Lesser General Public
19 * License along with this library; if not, write to:
21 * Free Software Foundation, Inc.
22 * 51 Franklin Street, Fifth Floor,
23 * Boston, MA 02110-1301 USA
31 #include <arpa/inet.h>
33 #include <openssl/ssl.h>
34 #include <openssl/err.h>
36 #include "openconnect.h"
39 * Data packets are encapsulated in the SSL stream as follows:
41 * 0000: Magic "STF\x1"
42 * 0004: Big-endian 16-bit length (not including 8-byte header)
43 * 0006: Byte packet type (see openconnect.h)
47 static char data_hdr[8] = {
50 AC_PKT_DATA, /* Type */
54 static struct pkt keepalive_pkt = {
55 .hdr = { 'S', 'T', 'F', 1, 0, 0, AC_PKT_KEEPALIVE, 0 },
58 static struct pkt dpd_pkt = {
59 .hdr = { 'S', 'T', 'F', 1, 0, 0, AC_PKT_DPD_OUT, 0 },
62 static struct pkt dpd_resp_pkt = {
63 .hdr = { 'S', 'T', 'F', 1, 0, 0, AC_PKT_DPD_RESP, 0 },
67 static int start_cstp_connection(struct openconnect_info *vpninfo)
72 struct vpn_option **next_dtls_option = &vpninfo->dtls_options;
73 struct vpn_option **next_cstp_option = &vpninfo->cstp_options;
74 struct vpn_option *old_cstp_opts = vpninfo->cstp_options;
75 struct vpn_option *old_dtls_opts = vpninfo->dtls_options;
76 const char *old_addr = vpninfo->vpn_addr;
77 const char *old_netmask = vpninfo->vpn_netmask;
78 struct split_include *inc;
80 /* Clear old options which will be overwritten */
81 vpninfo->vpn_addr = vpninfo->vpn_netmask = NULL;
82 vpninfo->cstp_options = vpninfo->dtls_options = NULL;
83 vpninfo->vpn_domain = vpninfo->vpn_proxy_pac = NULL;
86 vpninfo->vpn_dns[i] = vpninfo->vpn_nbns[i] = NULL;
88 for (inc = vpninfo->split_includes; inc; ) {
89 struct split_include *next = inc->next;
93 for (inc = vpninfo->split_excludes; inc; ) {
94 struct split_include *next = inc->next;
98 vpninfo->split_includes = vpninfo->split_excludes = NULL;
100 openconnect_SSL_printf(vpninfo->https_ssl, "CONNECT /CSCOSSLC/tunnel HTTP/1.1\r\n");
101 openconnect_SSL_printf(vpninfo->https_ssl, "Host: %s\r\n", vpninfo->hostname);
102 openconnect_SSL_printf(vpninfo->https_ssl, "User-Agent: %s\r\n", vpninfo->useragent);
103 openconnect_SSL_printf(vpninfo->https_ssl, "Cookie: webvpn=%s\r\n", vpninfo->cookie);
104 openconnect_SSL_printf(vpninfo->https_ssl, "X-CSTP-Version: 1\r\n");
105 openconnect_SSL_printf(vpninfo->https_ssl, "X-CSTP-Hostname: %s\r\n", vpninfo->localname);
106 if (vpninfo->deflate)
107 openconnect_SSL_printf(vpninfo->https_ssl, "X-CSTP-Accept-Encoding: deflate;q=1.0\r\n");
108 openconnect_SSL_printf(vpninfo->https_ssl, "X-CSTP-MTU: %d\r\n", vpninfo->mtu);
109 /* To enable IPv6, send 'IPv6,IPv4'.
110 We don't know how most of that works yet though. */
111 openconnect_SSL_printf(vpninfo->https_ssl, "X-CSTP-Address-Type: IPv4\r\n");
112 openconnect_SSL_printf(vpninfo->https_ssl, "X-DTLS-Master-Secret: ");
113 for (i = 0; i < sizeof(vpninfo->dtls_secret); i++)
114 openconnect_SSL_printf(vpninfo->https_ssl, "%02X", vpninfo->dtls_secret[i]);
115 openconnect_SSL_printf(vpninfo->https_ssl, "\r\nX-DTLS-CipherSuite: %s\r\n\r\n",
116 vpninfo->dtls_ciphers?:"AES256-SHA:AES128-SHA:DES-CBC3-SHA:DES-CBC-SHA");
118 if (openconnect_SSL_gets(vpninfo->https_ssl, buf, 65536) < 0) {
119 vpninfo->progress(vpninfo, PRG_ERR, "Error fetching HTTPS response\n");
122 openconnect_close_https(vpninfo);
124 if (openconnect_open_https(vpninfo)) {
125 vpninfo->progress(vpninfo, PRG_ERR,
126 "Failed to open HTTPS connection to %s\n",
135 if (strncmp(buf, "HTTP/1.1 200 ", 13)) {
136 if (!strncmp(buf, "HTTP/1.1 503 ", 13)) {
137 /* "Service Unavailable. Why? */
138 char *reason = "<unknown>";
139 while ((i = openconnect_SSL_gets(vpninfo->https_ssl, buf, sizeof(buf)))) {
140 if (!strncmp(buf, "X-Reason: ", 10)) {
145 vpninfo->progress(vpninfo, PRG_ERR, "VPN service unavailable; reason: %s\n",
149 vpninfo->progress(vpninfo, PRG_ERR,
150 "Got inappropriate HTTP CONNECT response: %s\n",
152 if (!strncmp(buf, "HTTP/1.1 401 ", 13))
157 vpninfo->progress(vpninfo, PRG_INFO,
158 "Got CONNECT response: %s\n", buf);
160 /* We may have advertised it, but we only do it if the server agrees */
161 vpninfo->deflate = 0;
163 while ((i = openconnect_SSL_gets(vpninfo->https_ssl, buf, sizeof(buf)))) {
164 struct vpn_option *new_option;
165 char *colon = strchr(buf, ':');
174 if (strncmp(buf, "X-DTLS-", 7) &&
175 strncmp(buf, "X-CSTP-", 7))
178 new_option = malloc(sizeof(*new_option));
180 vpninfo->progress(vpninfo, PRG_ERR, "No memory for options\n");
183 new_option->option = strdup(buf);
184 new_option->value = strdup(colon);
185 new_option->next = NULL;
187 if (!new_option->option || !new_option->value) {
188 vpninfo->progress(vpninfo, PRG_ERR, "No memory for options\n");
192 vpninfo->progress(vpninfo, PRG_TRACE, "%s: %s\n", buf, colon);
194 if (!strncmp(buf, "X-DTLS-", 7)) {
195 *next_dtls_option = new_option;
196 next_dtls_option = &new_option->next;
199 /* CSTP options... */
200 *next_cstp_option = new_option;
201 next_cstp_option = &new_option->next;
204 if (!strcmp(buf + 7, "Keepalive")) {
205 vpninfo->ssl_times.keepalive = atol(colon);
206 } else if (!strcmp(buf + 7, "DPD")) {
207 vpninfo->ssl_times.dpd = atol(colon);
208 } else if (!strcmp(buf + 7, "Content-Encoding")) {
209 if (!strcmp(colon, "deflate"))
210 vpninfo->deflate = 1;
212 vpninfo->progress(vpninfo, PRG_ERR,
213 "Unknown CSTP-Content-Encoding %s\n",
217 } else if (!strcmp(buf + 7, "MTU")) {
218 vpninfo->mtu = atol(colon);
219 } else if (!strcmp(buf + 7, "Address")) {
220 vpninfo->vpn_addr = new_option->value;
221 } else if (!strcmp(buf + 7, "Netmask")) {
222 vpninfo->vpn_netmask = new_option->value;
223 } else if (!strcmp(buf + 7, "DNS")) {
225 for (j = 0; j < 3; j++) {
226 if (!vpninfo->vpn_dns[j]) {
227 vpninfo->vpn_dns[j] = new_option->value;
231 } else if (!strcmp(buf + 7, "NBNS")) {
233 for (j = 0; j < 3; j++) {
234 if (!vpninfo->vpn_nbns[j]) {
235 vpninfo->vpn_nbns[j] = new_option->value;
239 } else if (!strcmp(buf + 7, "Default-Domain")) {
240 vpninfo->vpn_domain = new_option->value;
241 } else if (!strcmp(buf + 7, "MSIE-Proxy-PAC-URL")) {
242 vpninfo->vpn_proxy_pac = new_option->value;
243 } else if (!strcmp(buf + 7, "Split-Include")) {
244 struct split_include *inc = malloc(sizeof(*inc));
247 inc->route = new_option->value;
248 inc->next = vpninfo->split_includes;
249 vpninfo->split_includes = inc;
250 } else if (!strcmp(buf + 7, "Split-Exclude")) {
251 struct split_include *exc = malloc(sizeof(*exc));
254 exc->route = new_option->value;
255 exc->next = vpninfo->split_excludes;
256 vpninfo->split_excludes = exc;
260 if (!vpninfo->vpn_addr) {
261 vpninfo->progress(vpninfo, PRG_ERR, "No IP address received. Aborting\n");
264 if (!vpninfo->vpn_netmask)
265 vpninfo->vpn_netmask = "255.255.255.255";
267 if (strcmp(old_addr, vpninfo->vpn_addr)) {
268 vpninfo->progress(vpninfo, PRG_ERR, "Reconnect gave different IP address (%s != %s)\n",
269 vpninfo->vpn_addr, old_addr);
274 if (strcmp(old_netmask, vpninfo->vpn_netmask)) {
275 vpninfo->progress(vpninfo, PRG_ERR, "Reconnect gave different netmask (%s != %s)\n",
276 vpninfo->vpn_netmask, old_netmask);
281 while (old_dtls_opts) {
282 struct vpn_option *tmp = old_dtls_opts;
283 old_dtls_opts = old_dtls_opts->next;
288 while (old_cstp_opts) {
289 struct vpn_option *tmp = old_cstp_opts;
290 old_cstp_opts = old_cstp_opts->next;
295 vpninfo->progress(vpninfo, PRG_INFO, "CSTP connected. DPD %d, Keepalive %d\n",
296 vpninfo->ssl_times.dpd, vpninfo->ssl_times.keepalive);
298 BIO_set_nbio(SSL_get_rbio(vpninfo->https_ssl), 1);
299 BIO_set_nbio(SSL_get_wbio(vpninfo->https_ssl), 1);
301 fcntl(vpninfo->ssl_fd, F_SETFL, fcntl(vpninfo->ssl_fd, F_GETFL) | O_NONBLOCK);
302 if (vpninfo->select_nfds <= vpninfo->ssl_fd)
303 vpninfo->select_nfds = vpninfo->ssl_fd + 1;
305 FD_SET(vpninfo->ssl_fd, &vpninfo->select_rfds);
306 FD_SET(vpninfo->ssl_fd, &vpninfo->select_efds);
308 vpninfo->ssl_times.last_rx = vpninfo->ssl_times.last_tx = time(NULL);
313 int make_cstp_connection(struct openconnect_info *vpninfo)
317 if (!vpninfo->https_ssl && (ret = openconnect_open_https(vpninfo)))
320 if (vpninfo->deflate) {
321 vpninfo->deflate_adler32 = 1;
322 vpninfo->inflate_adler32 = 1;
324 if (inflateInit2(&vpninfo->inflate_strm, -12) ||
325 deflateInit2(&vpninfo->deflate_strm, Z_DEFAULT_COMPRESSION,
326 Z_DEFLATED, -12, 9, Z_DEFAULT_STRATEGY)) {
327 vpninfo->progress(vpninfo, PRG_ERR, "Compression setup failed\n");
328 vpninfo->deflate = 0;
331 if (!vpninfo->deflate_pkt) {
332 vpninfo->deflate_pkt = malloc(sizeof(struct pkt) + 2048);
333 if (!vpninfo->deflate_pkt) {
334 vpninfo->progress(vpninfo, PRG_ERR, "Allocation of deflate buffer failed\n");
335 vpninfo->deflate = 0;
337 memset(vpninfo->deflate_pkt, 0, sizeof(struct pkt));
338 memcpy(vpninfo->deflate_pkt->hdr, data_hdr, 8);
339 vpninfo->deflate_pkt->hdr[6] = AC_PKT_COMPRESSED;
343 return start_cstp_connection(vpninfo);
346 static int cstp_reconnect(struct openconnect_info *vpninfo)
352 timeout = vpninfo->reconnect_timeout;
353 interval = vpninfo->reconnect_interval;
355 while ((ret = make_cstp_connection(vpninfo))) {
358 vpninfo->progress(vpninfo, PRG_INFO,
359 "sleep %ds, remaining timeout %ds\n",
365 interval += vpninfo->reconnect_interval;
366 if (interval > RECONNECT_INTERVAL_MAX)
367 interval = RECONNECT_INTERVAL_MAX;
372 static int inflate_and_queue_packet(struct openconnect_info *vpninfo, int type, void *buf, int len)
374 struct pkt *new = malloc(sizeof(struct pkt) + vpninfo->mtu);
382 vpninfo->inflate_strm.next_in = buf;
383 vpninfo->inflate_strm.avail_in = len - 4;
385 vpninfo->inflate_strm.next_out = new->data;
386 vpninfo->inflate_strm.avail_out = vpninfo->mtu;
387 vpninfo->inflate_strm.total_out = 0;
389 if (inflate(&vpninfo->inflate_strm, Z_SYNC_FLUSH)) {
390 vpninfo->progress(vpninfo, PRG_ERR, "inflate failed\n");
395 new->len = vpninfo->inflate_strm.total_out;
397 vpninfo->inflate_adler32 = adler32(vpninfo->inflate_adler32,
398 new->data, new->len);
400 if (vpninfo->inflate_adler32 != ntohl( *(uint32_t *) (buf + len - 4) )) {
401 vpninfo->quit_reason = "Compression (inflate) adler32 failure";
404 vpninfo->progress(vpninfo, PRG_TRACE,
405 "Received compressed data packet of %ld bytes\n",
406 vpninfo->inflate_strm.total_out);
408 queue_packet(&vpninfo->incoming_queue, new);
412 int cstp_mainloop(struct openconnect_info *vpninfo, int *timeout)
414 unsigned char buf[16384];
418 /* FIXME: The poll() handling here is fairly simplistic. Actually,
419 if the SSL connection stalls it could return a WANT_WRITE error
420 on _either_ of the SSL_read() or SSL_write() calls. In that case,
421 we should probably remove POLLIN from the events we're looking for,
422 and add POLLOUT. As it is, though, it'll just chew CPU time in that
423 fairly unlikely situation, until the write backlog clears. */
424 while ( (len = SSL_read(vpninfo->https_ssl, buf, sizeof(buf))) > 0) {
427 if (buf[0] != 'S' || buf[1] != 'T' ||
428 buf[2] != 'F' || buf[3] != 1 || buf[7])
431 payload_len = (buf[4] << 8) + buf[5];
432 if (len != 8 + payload_len) {
433 vpninfo->progress(vpninfo, PRG_ERR,
434 "Unexpected packet length. SSL_read returned %d but packet is\n",
436 vpninfo->progress(vpninfo, PRG_ERR,
437 "%02x %02x %02x %02x %02x %02x %02x %02x\n",
438 buf[0], buf[1], buf[2], buf[3],
439 buf[4], buf[5], buf[6], buf[7]);
442 vpninfo->ssl_times.last_rx = time(NULL);
445 vpninfo->progress(vpninfo, PRG_TRACE,
446 "Got CSTP DPD request\n");
447 vpninfo->owe_ssl_dpd_response = 1;
450 case AC_PKT_DPD_RESP:
451 vpninfo->progress(vpninfo, PRG_TRACE,
452 "Got CSTP DPD response\n");
455 case AC_PKT_KEEPALIVE:
456 vpninfo->progress(vpninfo, PRG_TRACE,
457 "Got CSTP Keepalive\n");
461 vpninfo->progress(vpninfo, PRG_TRACE,
462 "Received uncompressed data packet of %d bytes\n",
464 queue_new_packet(&vpninfo->incoming_queue, AF_INET, buf + 8,
469 case AC_PKT_DISCONN: {
471 for (i = 0; i < payload_len; i++) {
472 if (!isprint(buf[payload_len + 8 + i]))
473 buf[payload_len + 8 + i] = '.';
475 buf[payload_len + 8] = 0;
476 vpninfo->progress(vpninfo, PRG_ERR,
477 "Received server disconnect: %02x '%s'\n", buf[8], buf + 9);
478 vpninfo->quit_reason = "Server request";
481 case AC_PKT_COMPRESSED:
482 if (!vpninfo->deflate) {
483 vpninfo->progress(vpninfo, PRG_ERR, "Compressed packet received in !deflate mode\n");
486 inflate_and_queue_packet(vpninfo, AF_INET, buf + 8, payload_len);
490 case AC_PKT_TERM_SERVER:
491 vpninfo->progress(vpninfo, PRG_ERR, "received server terminate packet\n");
492 vpninfo->quit_reason = "Server request";
497 vpninfo->progress(vpninfo, PRG_ERR,
498 "Unknown packet %02x %02x %02x %02x %02x %02x %02x %02x\n",
499 buf[0], buf[1], buf[2], buf[3],
500 buf[4], buf[5], buf[6], buf[7]);
501 vpninfo->quit_reason = "Unknown packet received";
505 ret = SSL_get_error(vpninfo->https_ssl, len);
506 if (ret == SSL_ERROR_SYSCALL || ret == SSL_ERROR_ZERO_RETURN) {
507 vpninfo->progress(vpninfo, PRG_ERR,
508 "SSL read error %d (server probably closed connection); reconnecting.\n",
514 /* If SSL_write() fails we are expected to try again. With exactly
515 the same data, at exactly the same location. So we keep the
516 packet we had before.... */
517 if (vpninfo->current_ssl_pkt) {
519 vpninfo->ssl_times.last_tx = time(NULL);
520 FD_CLR(vpninfo->ssl_fd, &vpninfo->select_wfds);
521 ret = SSL_write(vpninfo->https_ssl,
522 vpninfo->current_ssl_pkt->hdr,
523 vpninfo->current_ssl_pkt->len + 8);
525 ret = SSL_get_error(vpninfo->https_ssl, ret);
527 case SSL_ERROR_WANT_WRITE:
528 /* Waiting for the socket to become writable -- it's
529 probably stalled, and/or the buffers are full */
530 FD_SET(vpninfo->ssl_fd, &vpninfo->select_wfds);
532 case SSL_ERROR_WANT_READ:
533 if (ka_stalled_dpd_time(&vpninfo->ssl_times, timeout))
537 vpninfo->progress(vpninfo, PRG_ERR, "SSL_write failed: %d\n", ret);
538 report_ssl_errors(vpninfo);
542 if (ret != vpninfo->current_ssl_pkt->len + 8) {
543 vpninfo->progress(vpninfo, PRG_ERR, "SSL wrote too few bytes! Asked for %d, sent %d\n",
544 vpninfo->current_ssl_pkt->len + 8, ret);
545 vpninfo->quit_reason = "Internal error";
548 /* Don't free the 'special' packets */
549 if (vpninfo->current_ssl_pkt != vpninfo->deflate_pkt &&
550 vpninfo->current_ssl_pkt != &dpd_pkt &&
551 vpninfo->current_ssl_pkt != &dpd_resp_pkt &&
552 vpninfo->current_ssl_pkt != &keepalive_pkt)
553 free(vpninfo->current_ssl_pkt);
555 vpninfo->current_ssl_pkt = NULL;
558 if (vpninfo->owe_ssl_dpd_response) {
559 vpninfo->owe_ssl_dpd_response = 0;
560 vpninfo->current_ssl_pkt = &dpd_resp_pkt;
561 goto handle_outgoing;
564 switch (keepalive_action(&vpninfo->ssl_times, timeout)) {
566 /* Not that this will ever happen; we don't even process
567 the setting when we're asked for it. */
568 vpninfo->progress(vpninfo, PRG_ERR, "CSTP rekey due but we don't know how\n");
569 time(&vpninfo->ssl_times.last_rekey);
575 vpninfo->progress(vpninfo, PRG_ERR, "CSTP Dead Peer Detection detected dead peer!\n");
577 openconnect_close_https(vpninfo);
579 /* It's already deflated in the old stream. Extremely
580 non-trivial to reconstitute it; just throw it away */
581 if (vpninfo->current_ssl_pkt == vpninfo->deflate_pkt)
582 vpninfo->current_ssl_pkt = NULL;
584 if (cstp_reconnect(vpninfo)) {
585 vpninfo->progress(vpninfo, PRG_ERR, "Reconnect failed\n");
586 vpninfo->quit_reason = "CSTP reconnect failed";
589 /* I think we can leave DTLS to its own devices; when we reconnect
590 with the same master secret, we do seem to get the same sessid */
594 vpninfo->progress(vpninfo, PRG_TRACE, "Send CSTP DPD\n");
596 vpninfo->current_ssl_pkt = &dpd_pkt;
597 goto handle_outgoing;
600 /* No need to send an explicit keepalive
601 if we have real data to send */
602 if (vpninfo->dtls_fd == -1 && vpninfo->outgoing_queue)
605 vpninfo->progress(vpninfo, PRG_TRACE, "Send CSTP Keepalive\n");
607 vpninfo->current_ssl_pkt = &keepalive_pkt;
608 goto handle_outgoing;
614 /* Service outgoing packet queue, if no DTLS */
615 while (vpninfo->dtls_fd == -1 && vpninfo->outgoing_queue) {
616 struct pkt *this = vpninfo->outgoing_queue;
617 vpninfo->outgoing_queue = this->next;
618 vpninfo->outgoing_qlen--;
620 /* FIXME: Don't know how to handle IPv6 yet */
621 if (this->type != AF_INET)
624 if (vpninfo->deflate) {
625 unsigned char *adler;
628 vpninfo->deflate_strm.next_in = this->data;
629 vpninfo->deflate_strm.avail_in = this->len;
630 vpninfo->deflate_strm.next_out = (void *)vpninfo->deflate_pkt->data;
631 vpninfo->deflate_strm.avail_out = 2040;
632 vpninfo->deflate_strm.total_out = 0;
634 ret = deflate(&vpninfo->deflate_strm, Z_SYNC_FLUSH);
636 vpninfo->progress(vpninfo, PRG_ERR, "deflate failed %d\n", ret);
640 vpninfo->deflate_pkt->hdr[4] = (vpninfo->deflate_strm.total_out + 4) >> 8;
641 vpninfo->deflate_pkt->hdr[5] = (vpninfo->deflate_strm.total_out + 4) & 0xff;
643 /* Add ongoing adler32 to tail of compressed packet */
644 vpninfo->deflate_adler32 = adler32(vpninfo->deflate_adler32,
645 this->data, this->len);
647 adler = &vpninfo->deflate_pkt->data[vpninfo->deflate_strm.total_out];
648 *(adler++) = vpninfo->deflate_adler32 >> 24;
649 *(adler++) = (vpninfo->deflate_adler32 >> 16) & 0xff;
650 *(adler++) = (vpninfo->deflate_adler32 >> 8) & 0xff;
651 *(adler) = vpninfo->deflate_adler32 & 0xff;
653 vpninfo->deflate_pkt->len = vpninfo->deflate_strm.total_out + 4;
655 vpninfo->progress(vpninfo, PRG_TRACE,
656 "Sending compressed data packet of %d bytes\n",
659 vpninfo->current_ssl_pkt = vpninfo->deflate_pkt;
662 memcpy(this->hdr, data_hdr, 8);
663 this->hdr[4] = this->len >> 8;
664 this->hdr[5] = this->len & 0xff;
666 vpninfo->progress(vpninfo, PRG_TRACE,
667 "Sending uncompressed data packet of %d bytes\n",
670 vpninfo->current_ssl_pkt = this;
672 goto handle_outgoing;
675 /* Work is not done if we just got rid of packets off the queue */
679 int cstp_bye(struct openconnect_info *vpninfo, char *reason)
681 unsigned char *bye_pkt;
684 /* already lost connection? */
685 if (!vpninfo->https_ssl)
688 reason_len = strlen(reason);
689 bye_pkt = malloc(reason_len + 9);
693 memcpy(bye_pkt, data_hdr, 8);
694 memcpy(bye_pkt + 9, reason, reason_len);
696 bye_pkt[4] = (reason_len + 1) >> 8;
697 bye_pkt[5] = (reason_len + 1) & 0xff;
698 bye_pkt[6] = AC_PKT_DISCONN;
701 SSL_write(vpninfo->https_ssl, bye_pkt, reason_len + 9);
704 vpninfo->progress(vpninfo, PRG_INFO,
705 "Send BYE packet: %s\n", reason);