~ubuntu-branches/ubuntu/quantal/openconnect/quantal

« back to all changes in this revision

Viewing changes to tun.c

  • Committer: Bazaar Package Importer
  • Author(s): Ross Burton
  • Date: 2009-01-15 13:26:25 UTC
  • Revision ID: james.westby@ubuntu.com-20090115132625-p7a4di5gbq9aevfw
Tags: upstream-0.99
Import upstream version 0.99

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
 * OpenConnect (SSL + DTLS) VPN client
 
3
 *
 
4
 * Copyright © 2008 Intel Corporation.
 
5
 *
 
6
 * Author: David Woodhouse <dwmw2@infradead.org>
 
7
 *
 
8
 * This program is free software; you can redistribute it and/or
 
9
 * modify it under the terms of the GNU Lesser General Public License
 
10
 * version 2.1, as published by the Free Software Foundation.
 
11
 *
 
12
 * This program is distributed in the hope that it will be useful, but
 
13
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 
14
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 
15
 * Lesser General Public License for more details.
 
16
 *
 
17
 * You should have received a copy of the GNU Lesser General Public
 
18
 * License along with this library; if not, write to:
 
19
 *
 
20
 *   Free Software Foundation, Inc.
 
21
 *   51 Franklin Street, Fifth Floor,
 
22
 *   Boston, MA 02110-1301 USA
 
23
 */
 
24
 
 
25
#include <string.h>
 
26
#include <sys/socket.h>
 
27
#include <sys/ioctl.h>
 
28
#ifdef __linux__
 
29
#include <linux/if_tun.h>
 
30
#endif
 
31
#include <sys/types.h>
 
32
#include <sys/stat.h>
 
33
#include <fcntl.h>
 
34
#include <unistd.h>
 
35
#include <netinet/in.h>
 
36
#include <net/if.h>
 
37
#include <arpa/inet.h>
 
38
#include <errno.h>
 
39
 
 
40
#include "openconnect.h"
 
41
 
 
42
static int local_config_tun(struct openconnect_info *vpninfo, int mtu_only)
 
43
{
 
44
        struct ifreq ifr;
 
45
        int net_fd;
 
46
 
 
47
        net_fd = socket(PF_INET, SOCK_DGRAM, 0);
 
48
        if (net_fd < 0) {
 
49
                perror("open net");
 
50
                return -EINVAL;
 
51
        }
 
52
        memset(&ifr, 0, sizeof(ifr));
 
53
        strncpy(ifr.ifr_name, vpninfo->ifname, sizeof(ifr.ifr_name) - 1);
 
54
 
 
55
        if (!mtu_only) {
 
56
                struct sockaddr_in *addr = (struct sockaddr_in *) &ifr.ifr_addr;
 
57
 
 
58
                if (ioctl(net_fd, SIOCGIFFLAGS, &ifr) < 0)
 
59
                        perror("SIOCGIFFLAGS");
 
60
 
 
61
                ifr.ifr_flags |= IFF_UP | IFF_POINTOPOINT; 
 
62
                if (ioctl(net_fd, SIOCSIFFLAGS, &ifr) < 0)
 
63
                        perror("SIOCSIFFLAGS");
 
64
 
 
65
                addr->sin_family = AF_INET;
 
66
                addr->sin_addr.s_addr = inet_addr(vpninfo->vpn_addr);
 
67
                if (ioctl(net_fd, SIOCSIFADDR, &ifr) < 0)
 
68
                        perror("SIOCSIFADDR");
 
69
        }
 
70
 
 
71
        ifr.ifr_mtu = vpninfo->mtu;
 
72
        if (ioctl(net_fd, SIOCSIFMTU, &ifr) < 0)
 
73
                perror("SIOCSIFMTU");
 
74
 
 
75
        close(net_fd);
 
76
 
 
77
        return 0;
 
78
}
 
79
 
 
80
static int setenv_int(const char *opt, int value)
 
81
{
 
82
        char buf[16];
 
83
        sprintf(buf, "%d", value);
 
84
        return setenv(opt, buf, 1);
 
85
}
 
86
 
 
87
static int process_split_include(struct openconnect_info *vpninfo,
 
88
                                 char *route, int *nr_incs)
 
89
{
 
90
        struct in_addr addr;
 
91
        int masklen;
 
92
        char envname[80];
 
93
        char *slash;
 
94
 
 
95
        slash = strchr(route, '/');
 
96
        if (!slash) {
 
97
        badinc:
 
98
                vpninfo->progress(vpninfo, PRG_ERR,
 
99
                                  "Discard bad split include: \"%s\"\n",
 
100
                                  route);
 
101
                return -EINVAL;
 
102
        }
 
103
 
 
104
        *slash = 0;
 
105
        if (!inet_aton(route, &addr)) {
 
106
                *slash = '/';
 
107
                goto badinc;
 
108
        }
 
109
 
 
110
        envname[79] = 0;
 
111
        snprintf(envname, 79, "CISCO_SPLIT_INC_%d_ADDR", *nr_incs);
 
112
        setenv(envname, route, 1);
 
113
 
 
114
        /* Put it back how we found it */
 
115
        *slash = '/';
 
116
 
 
117
        if (!inet_aton(slash+1, &addr))
 
118
                goto badinc;
 
119
 
 
120
        snprintf(envname, 79, "CISCO_SPLIT_INC_%d_MASK", *nr_incs);
 
121
        setenv(envname, slash+1, 1);
 
122
 
 
123
        for (masklen = 0; masklen < 32; masklen++) {
 
124
                if (ntohl(addr.s_addr) >= (0xffffffff << masklen))
 
125
                        break;
 
126
        }
 
127
        masklen = 32 - masklen;
 
128
                    
 
129
        snprintf(envname, 79, "CISCO_SPLIT_INC_%d_MASKLEN", *nr_incs);
 
130
        setenv_int(envname, masklen);
 
131
 
 
132
        (*nr_incs)++;
 
133
        return 0;
 
134
}
 
135
 
 
136
static int appendenv(const char *opt, const char *new)
 
137
{
 
138
        char buf[1024];
 
139
        char *old = getenv(opt);
 
140
 
 
141
        buf[1023] = 0;
 
142
        if (old)
 
143
                snprintf(buf, 1023, "%s %s", old, new);
 
144
        else
 
145
                snprintf(buf, 1023, "%s", new);
 
146
 
 
147
        return setenv(opt, buf, 1);
 
148
}
 
149
 
 
150
static void set_script_env(struct openconnect_info *vpninfo)
 
151
{
 
152
        struct sockaddr_in *sin = (void *)vpninfo->peer_addr;
 
153
 
 
154
        setenv("VPNGATEWAY", inet_ntoa(sin->sin_addr), 1);
 
155
        setenv("TUNDEV", vpninfo->ifname, 1);
 
156
        setenv("reason", "connect", 1);
 
157
        unsetenv("CISCO_BANNER");
 
158
        unsetenv("CISCO_SPLIT_INC");
 
159
 
 
160
        setenv_int("INTERNAL_IP4_MTU", vpninfo->mtu);
 
161
 
 
162
        setenv("INTERNAL_IP4_ADDRESS", vpninfo->vpn_addr, 1);
 
163
        setenv("INTERNAL_IP4_NETMASK", vpninfo->vpn_netmask, 1);
 
164
        
 
165
        if (vpninfo->vpn_dns[0])
 
166
                setenv("INTERNAL_IP4_DNS", vpninfo->vpn_dns[0], 1);
 
167
        else
 
168
                unsetenv("INTERNAL_IP4_DNS");
 
169
        if (vpninfo->vpn_dns[1])
 
170
                appendenv("INTERNAL_IP4_DNS", vpninfo->vpn_dns[1]);
 
171
        if (vpninfo->vpn_dns[2])
 
172
                appendenv("INTERNAL_IP4_DNS", vpninfo->vpn_dns[2]);
 
173
 
 
174
        if (vpninfo->vpn_nbns[0])
 
175
                setenv("INTERNAL_IP4_NBNS", vpninfo->vpn_nbns[0], 1);
 
176
        else
 
177
                unsetenv("INTERNAL_IP4_NBNS");
 
178
        if (vpninfo->vpn_nbns[1])
 
179
                appendenv("INTERNAL_IP4_NBNS", vpninfo->vpn_nbns[1]);
 
180
        if (vpninfo->vpn_nbns[2])
 
181
                appendenv("INTERNAL_IP4_NBNS", vpninfo->vpn_nbns[2]);
 
182
 
 
183
        if (vpninfo->vpn_domain)
 
184
                setenv("CISCO_DEF_DOMAIN", vpninfo->vpn_domain, 1);
 
185
        else unsetenv ("CISCO_DEF_DOMAIN");
 
186
 
 
187
        if (vpninfo->split_includes) {
 
188
                struct split_include *this = vpninfo->split_includes;
 
189
                int nr_split_includes = 0;
 
190
 
 
191
                while (this) {
 
192
                        process_split_include(vpninfo, this->route,
 
193
                                              &nr_split_includes);
 
194
                        this = this->next;
 
195
                }
 
196
                setenv_int("CISCO_SPLIT_INC", nr_split_includes);
 
197
        }                       
 
198
                        
 
199
                        
 
200
}
 
201
 
 
202
static int script_config_tun(struct openconnect_info *vpninfo)
 
203
{
 
204
        if (vpninfo->peer_addr->sa_family != AF_INET) {
 
205
                vpninfo->progress(vpninfo, PRG_ERR, "Script cannot handle anything but Legacy IP\n");
 
206
                return -EINVAL;
 
207
        }
 
208
 
 
209
        set_script_env(vpninfo);
 
210
 
 
211
        system(vpninfo->vpnc_script);
 
212
        return 0;
 
213
}
 
214
 
 
215
 
 
216
/* Set up a tuntap device. */
 
217
int setup_tun(struct openconnect_info *vpninfo)
 
218
{
 
219
        struct ifreq ifr;
 
220
        int tun_fd;
 
221
 
 
222
        if (vpninfo->script_tun) {
 
223
                pid_t child;
 
224
                int fds[2];
 
225
 
 
226
                if (socketpair(AF_UNIX, SOCK_DGRAM, 0, fds)) {
 
227
                        perror("socketpair");
 
228
                        exit(1);
 
229
                }
 
230
                tun_fd = fds[0];
 
231
                child = fork();
 
232
                if (child < 0) {
 
233
                        perror("fork");
 
234
                        exit(1);
 
235
                } else if (!child) {
 
236
                        close(tun_fd);
 
237
                        setenv_int("VPNFD", fds[1]);
 
238
                        execl("/bin/sh", "/bin/sh", "-c", vpninfo->vpnc_script, NULL);
 
239
                        perror("execl");
 
240
                        exit(1);
 
241
                }
 
242
                close(fds[1]);
 
243
                vpninfo->script_tun = child;
 
244
                vpninfo->ifname = "(script)";
 
245
        } else {
 
246
#ifdef IFF_TUN /* Linux */
 
247
                tun_fd = open("/dev/net/tun", O_RDWR);
 
248
                if (tun_fd < 0) {
 
249
                        vpninfo->progress(vpninfo, PRG_ERR,
 
250
                                          "Failed to open tun device: %s\n",
 
251
                                          strerror(errno));
 
252
                        exit(1);
 
253
                }
 
254
                memset(&ifr, 0, sizeof(ifr));
 
255
                ifr.ifr_flags = IFF_TUN | IFF_NO_PI;
 
256
                if (vpninfo->ifname)
 
257
                        strncpy(ifr.ifr_name, vpninfo->ifname,
 
258
                                sizeof(ifr.ifr_name) - 1);
 
259
                if (ioctl(tun_fd, TUNSETIFF, (void *) &ifr) < 0) {
 
260
                        vpninfo->progress(vpninfo, PRG_ERR,
 
261
                                          "TUNSETIFF failed: %s\n",
 
262
                                          strerror(errno));
 
263
                        exit(1);
 
264
                }
 
265
                if (!vpninfo->ifname)
 
266
                        vpninfo->ifname = strdup(ifr.ifr_name);
 
267
 
 
268
#else /* BSD et al have /dev/tun$x devices */
 
269
                static char tun_name[80];
 
270
                int i;
 
271
                for (i=0; i < 255; i++) {
 
272
                        sprintf(tun_name, "/dev/tun%d", i);
 
273
                        tun_fd = open(tun_name, O_RDWR);
 
274
                        if (tun_fd >= 0)
 
275
                                break;
 
276
                }
 
277
                if (tun_fd < 0) {
 
278
                        perror("open tun");
 
279
                        exit(1);
 
280
                }
 
281
                vpninfo->ifname = tun_name + 5;
 
282
#endif
 
283
                if (vpninfo->vpnc_script) {
 
284
                        script_config_tun(vpninfo);
 
285
                        /* We have to set the MTU for ourselves, because the script doesn't */
 
286
                        local_config_tun(vpninfo, 1);
 
287
                } else 
 
288
                        local_config_tun(vpninfo, 0);
 
289
        }
 
290
 
 
291
        fcntl(tun_fd, F_SETFD, FD_CLOEXEC);
 
292
 
 
293
        vpninfo->tun_fd = tun_fd;
 
294
        
 
295
        if (vpninfo->select_nfds <= tun_fd)
 
296
                vpninfo->select_nfds = tun_fd + 1;
 
297
 
 
298
        FD_SET(tun_fd, &vpninfo->select_rfds);
 
299
 
 
300
        fcntl(vpninfo->tun_fd, F_SETFL, fcntl(vpninfo->tun_fd, F_GETFL) | O_NONBLOCK);
 
301
 
 
302
        return 0;
 
303
}
 
304
 
 
305
int tun_mainloop(struct openconnect_info *vpninfo, int *timeout)
 
306
{
 
307
        char buf[2000];
 
308
        int len;
 
309
        int work_done = 0;
 
310
 
 
311
        if (FD_ISSET(vpninfo->tun_fd, &vpninfo->select_rfds)) {
 
312
                while ((len = read(vpninfo->tun_fd, buf, sizeof(buf))) > 0) {
 
313
                        if (queue_new_packet(&vpninfo->outgoing_queue, AF_INET, buf, len))
 
314
                                break;
 
315
 
 
316
                        work_done = 1;
 
317
                        vpninfo->outgoing_qlen++;
 
318
                        if (vpninfo->outgoing_qlen == vpninfo->max_qlen) {
 
319
                                FD_CLR(vpninfo->tun_fd, &vpninfo->select_rfds);
 
320
                                break;
 
321
                        }
 
322
                }
 
323
        } else if (vpninfo->outgoing_qlen < vpninfo->max_qlen) {
 
324
                FD_SET(vpninfo->tun_fd, &vpninfo->select_rfds);
 
325
        }
 
326
 
 
327
        /* The kernel returns -ENOMEM when the queue is full, so theoretically
 
328
           we could handle that and retry... but it doesn't let us poll() for
 
329
           the no-longer-full situation, so let's not bother. */
 
330
        while (vpninfo->incoming_queue) {
 
331
                struct pkt *this = vpninfo->incoming_queue;
 
332
                vpninfo->incoming_queue = this->next;
 
333
                if (write(vpninfo->tun_fd, this->data, this->len) < 0 &&
 
334
                    errno == ENOTCONN) {
 
335
                        vpninfo->quit_reason = "Client connection terminated";
 
336
                        return 1;
 
337
                }
 
338
        }
 
339
        /* Work is not done if we just got rid of packets off the queue */
 
340
        return work_done;
 
341
}