Asynchronous DTLS (re)connection
[platform/upstream/openconnect.git] / mainloop.c
1 /*
2  * Open AnyConnect (SSL + DTLS) client
3  *
4  * © 2008 David Woodhouse <dwmw2@infradead.org>
5  *
6  * Permission to use, copy, modify, and/or distribute this software
7  * for any purpose with or without fee is hereby granted, provided
8  * that the above copyright notice and this permission notice appear
9  * in all copies.
10  *
11  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
12  * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
13  * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
14  * AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
15  * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
16  * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
17  * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
18  * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
19  */
20
21 #include <errno.h>
22 #include <poll.h>
23 #include <limits.h>
24 #include <sys/select.h>
25 #include <signal.h>
26 #include <arpa/inet.h>
27
28 #include "anyconnect.h"
29
30 void queue_packet(struct pkt **q, struct pkt *new)
31 {
32         while (*q)
33                 q = &(*q)->next;
34
35         new->next = NULL;
36         *q = new;
37 }
38
39 int queue_new_packet(struct pkt **q, int type, void *buf, int len)
40 {
41         struct pkt *new = malloc(sizeof(struct pkt) + len);
42         if (!new)
43                 return -ENOMEM;
44
45         new->type = type;
46         new->len = len;
47         new->next = NULL;
48         memcpy(new->data, buf, len);
49         queue_packet(q, new);
50         return 0;
51 }
52
53 int vpn_add_pollfd(struct anyconnect_info *vpninfo, int fd, short events)
54 {
55         vpninfo->nfds++;
56         vpninfo->pfds = realloc(vpninfo->pfds, sizeof(struct pollfd) * vpninfo->nfds);
57         if (!vpninfo->pfds) {
58                 fprintf(stderr, "Failed to reallocate pfds\n");
59                 exit(1);
60         }
61         vpninfo->pfds[vpninfo->nfds - 1].fd = fd;
62         vpninfo->pfds[vpninfo->nfds - 1].events = events;
63
64         return vpninfo->nfds - 1;
65 }
66
67 static int killed;
68
69 static void handle_sigint(int sig)
70 {
71         killed = sig;
72 }
73
74 int vpn_mainloop(struct anyconnect_info *vpninfo)
75 {
76         struct sigaction sa;
77         memset(&sa, 0, sizeof(sa));
78         sa.sa_handler = handle_sigint;
79         
80         sigaction(SIGINT, &sa, NULL);
81         sigaction(SIGHUP, &sa, NULL);
82
83         while (!vpninfo->quit_reason) {
84                 int did_work = 0;
85                 int timeout = INT_MAX;
86
87                 if (vpninfo->new_dtls_ssl)
88                         dtls_try_handshake(vpninfo);
89
90                 if (!vpninfo->dtls_ssl && !vpninfo->new_dtls_ssl &&
91                     vpninfo->new_dtls_started + vpninfo->dtls_attempt_period < time(NULL)) {
92                         if (verbose)
93                                 printf("Attempt new DTLS connection\n");
94                         connect_dtls_socket(vpninfo);
95                 }
96                 if (vpninfo->dtls_ssl)
97                         did_work += dtls_mainloop(vpninfo, &timeout);
98
99                 if (vpninfo->quit_reason)
100                         break;
101
102                 did_work += ssl_mainloop(vpninfo, &timeout);
103                 if (vpninfo->quit_reason)
104                         break;
105                 
106                 did_work += tun_mainloop(vpninfo, &timeout);
107                 if (vpninfo->quit_reason)
108                         break;
109
110                 if (killed) {
111                         if (killed == SIGHUP)
112                                 vpninfo->quit_reason = "Client received SIGHUP";
113                         else if (killed == SIGINT)
114                                 vpninfo->quit_reason = "Client received SIGINT";
115                         else
116                                 vpninfo->quit_reason = "Client killed";
117                         break;
118                 }
119
120                 if (did_work)
121                         continue;
122
123                 if (verbose)
124                         printf("Did no work; sleeping for %d ms...\n", timeout);
125
126                 poll(vpninfo->pfds, vpninfo->nfds, timeout);
127                 if (vpninfo->pfds[vpninfo->ssl_pfd].revents & POLL_HUP) {
128                         fprintf(stderr, "Server closed connection!\n");
129                         /* OpenSSL doesn't seem to cope properly with this... */
130                         exit(1);
131                 }
132         }
133
134         ssl_bye(vpninfo, vpninfo->quit_reason);
135         printf("Sent quit message: %s\n", vpninfo->quit_reason);
136
137         if (vpninfo->vpnc_script) {
138                 setenv("TUNDEV", vpninfo->ifname, 1);
139                 setenv("reason", "disconnect", 1);
140                 system(vpninfo->vpnc_script);
141         }
142
143         return 0;
144 }
145
146 /* Called when the socket is unwritable, to get the deadline for DPD.
147    Returns 1 if DPD deadline has already arrived. */
148 int ka_stalled_dpd_time(struct keepalive_info *ka, int *timeout)
149 {
150         time_t now, due;
151
152         if (!ka->dpd) {
153                 printf("no dpd\n");
154                 return 0;
155         }
156
157         time(&now);
158         due = ka->last_rx + (2 * ka->dpd);
159
160         if (now > due)
161                 return 1;
162
163         printf("ka_stalled in %d seconds\n", (int)(due - now));
164         if (*timeout > (due - now) * 1000)
165                 *timeout = (due - now) * 1000;
166
167         return 0;
168 }
169
170
171 int keepalive_action(struct keepalive_info *ka, int *timeout)
172 {
173         time_t now = time(NULL);
174
175         if (ka->rekey) {
176                 time_t due = ka->last_rekey + ka->rekey;
177
178                 if (now >= due)
179                         return KA_REKEY;
180
181                 if (*timeout > (due - now) * 1000)
182                         *timeout = (due - now) * 1000;
183         }
184
185         /* DPD is bidirectional -- PKT 3 out, PKT 4 back */
186         if (ka->dpd) {
187                 time_t due = ka->last_rx + ka->dpd;
188                 time_t overdue = ka->last_rx + (2 * ka->dpd);
189
190                 /* Peer didn't respond */
191                 if (now > overdue)
192                         return KA_DPD_DEAD;
193
194                 /* If we already have DPD outstanding, don't flood. Repeat by
195                    all means, but only after half the DPD period. */
196                 if (ka->last_dpd > ka->last_rx)
197                         due = ka->last_dpd + ka->dpd / 2;
198
199                 /* We haven't seen a packet from this host for $DPD seconds.
200                    Prod it to see if it's still alive */
201                 if (now >= due) {
202                         ka->last_dpd = now;
203                         return KA_DPD;
204                 }
205                 if (*timeout > (due - now) * 1000)
206                         *timeout = (due - now) * 1000;
207         }
208
209         /* Keepalive is just client -> server */
210         if (ka->keepalive) {
211                 time_t due = ka->last_tx + ka->keepalive;
212
213                 /* If we haven't sent anything for $KEEPALIVE seconds, send a
214                    dummy packet (which the server will discard) */
215                 if (now >= due)
216                         return KA_KEEPALIVE;
217
218                 if (*timeout > (due - now) * 1000)
219                         *timeout = (due - now) * 1000;
220         }
221
222         return KA_NONE;
223 }