Releasing 1.0.20.
[tinc] / src / bsd / tunemu.c
1 /*
2  *  tunemu - Tun device emulation for Darwin
3  *  Copyright (C) 2009 Friedrich Schöller <friedrich.schoeller@gmail.com>
4  *  
5  *  This program is free software: you can redistribute it and/or modify
6  *  it under the terms of the GNU General Public License as published by
7  *  the Free Software Foundation, either version 3 of the License, or
8  *  (at your option) any later version.
9  *  
10  *  This program is distributed in the hope that it will be useful,
11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  *  GNU General Public License for more details.
14  *  
15  *  You should have received a copy of the GNU General Public License
16  *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
17  *  
18  */
19
20 #include "tunemu.h"
21
22 #include <sys/socket.h>
23 #include <unistd.h>
24 #include <sys/ioctl.h>
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <memory.h>
28 #include <util.h>
29 #include <pcap.h>
30 #include <stdarg.h>
31 #include <errno.h>
32 #include <stdint.h>
33 #include <stdint.h>
34 #include <ctype.h>
35 #include <fcntl.h>
36
37 #define PPPPROTO_CTL 1
38
39 #define PPP_IP          0x21
40 #define PPP_IPV6        0x57
41
42 #define SC_LOOP_TRAFFIC 0x00000200
43
44 #define PPPIOCNEWUNIT   _IOWR('t', 62, int)
45 #define PPPIOCSFLAGS    _IOW('t', 89, int)
46 #define PPPIOCSNPMODE   _IOW('t', 75, struct npioctl)
47 #define PPPIOCATTCHAN   _IOW('t', 56, int)
48 #define PPPIOCGCHAN             _IOR('t', 55, int)
49 #define PPPIOCCONNECT   _IOW('t', 58, int)
50 #define PPPIOCGUNIT             _IOR('t', 86, int)
51
52 struct sockaddr_ppp
53 {
54         u_int8_t ppp_len;
55         u_int8_t ppp_family;
56         u_int16_t ppp_proto;
57         u_int32_t ppp_cookie;
58 };
59
60 enum NPmode
61 {
62         NPMODE_PASS,
63     NPMODE_DROP,
64     NPMODE_ERROR,
65     NPMODE_QUEUE
66 };
67
68 struct npioctl
69 {
70         int protocol;
71         enum NPmode mode;
72 };
73
74 #define PPP_KEXT_PATH "/System/Library/Extensions/PPP.kext"
75
76 #define ERROR_BUFFER_SIZE 1024
77
78 char tunemu_error[ERROR_BUFFER_SIZE];
79
80 static int pcap_use_count = 0;
81 static pcap_t *pcap = NULL;
82
83 static int data_buffer_length = 0;
84 static char *data_buffer = NULL;
85
86 static void tun_error(char *format, ...)
87 {
88         va_list vl;
89         va_start(vl, format);
90         vsnprintf(tunemu_error, ERROR_BUFFER_SIZE, format, vl);
91         va_end(vl);
92 }
93
94 static void tun_noerror()
95 {
96         *tunemu_error = 0;
97 }
98
99 static void closeall()
100 {
101     int fd = getdtablesize();
102         while (fd--)
103                 close(fd);
104
105     open("/dev/null", O_RDWR, 0);
106     dup(0);
107     dup(0);
108 }
109
110 static int ppp_load_kext()
111 {
112         int pid = fork();
113         if (pid < 0)
114         {
115                 tun_error("fork for ppp kext: %s", strerror(errno));
116                 return -1;
117         }
118
119         if (pid == 0)
120         {
121                 closeall();
122                 execle("/sbin/kextload", "kextload", PPP_KEXT_PATH, NULL, NULL);
123                 exit(1);
124         }
125
126         int status;
127         while (waitpid(pid, &status, 0) < 0)
128         {
129                 if (errno == EINTR)
130                         continue;
131
132                 tun_error("waitpid for ppp kext: %s", strerror(errno));
133                 return -1;
134         }
135
136         if (WEXITSTATUS(status) != 0)
137         {
138                 tun_error("could not load ppp kext \"%s\"", PPP_KEXT_PATH);
139                 return -1;
140         }
141
142         tun_noerror();
143         return 0;
144 }
145
146 static int ppp_new_instance()
147 {
148         // create ppp socket
149     int ppp_sockfd = socket(PF_PPP, SOCK_RAW, PPPPROTO_CTL);
150     if (ppp_sockfd < 0)
151         {
152                 if (ppp_load_kext() < 0)
153                         return -1;
154
155                 ppp_sockfd = socket(PF_PPP, SOCK_RAW, PPPPROTO_CTL);
156                 if (ppp_sockfd < 0)
157                 {
158                         tun_error("creating ppp socket: %s", strerror(errno));
159                         return -1;
160                 }
161         }
162
163         // connect to ppp procotol
164     struct sockaddr_ppp pppaddr;
165     pppaddr.ppp_len = sizeof(struct sockaddr_ppp);
166     pppaddr.ppp_family = AF_PPP;
167     pppaddr.ppp_proto = PPPPROTO_CTL;
168     pppaddr.ppp_cookie = 0;
169     if (connect(ppp_sockfd, (struct sockaddr *)&pppaddr, sizeof(struct sockaddr_ppp)) < 0)
170         {
171                 tun_error("connecting ppp socket: %s", strerror(errno));
172                 close(ppp_sockfd);
173                 return -1;
174     }
175
176         tun_noerror();
177         return ppp_sockfd;
178 }
179
180 static int ppp_new_unit(int *unit_number)
181 {
182         int fd = ppp_new_instance();
183         if (fd < 0)
184                 return -1;
185
186         // create ppp unit
187         if (ioctl(fd, PPPIOCNEWUNIT, unit_number) < 0)
188         {
189                 tun_error("creating ppp unit: %s", strerror(errno));
190                 close(fd);
191                 return -1;
192     }
193
194         tun_noerror();
195         return fd;
196 }
197
198 static int ppp_setup_unit(int unit_fd)
199 {
200         // send traffic to program
201         int flags = SC_LOOP_TRAFFIC;
202         if (ioctl(unit_fd, PPPIOCSFLAGS, &flags) < 0)
203         {
204                 tun_error("setting ppp loopback mode: %s", strerror(errno));
205                 return -1;
206     }
207
208         // allow packets
209         struct npioctl npi;
210         npi.protocol = PPP_IP;
211         npi.mode = NPMODE_PASS;
212         if (ioctl(unit_fd, PPPIOCSNPMODE, &npi) < 0)
213         {
214                 tun_error("starting ppp unit: %s", strerror(errno));
215                 return -1;
216         }
217
218         tun_noerror();
219         return 0;
220 }
221
222 static int open_pcap()
223 {
224         if (pcap != NULL)
225         {
226                 pcap_use_count++;
227                 return 0;
228         }
229
230         char errbuf[PCAP_ERRBUF_SIZE];
231         pcap = pcap_open_live("lo0", BUFSIZ, 0, 1, errbuf);
232         pcap_use_count = 1;
233
234         if (pcap == NULL)
235         {
236                 tun_error("opening pcap: %s", errbuf);
237                 return -1;
238         }
239
240         tun_noerror();
241         return 0;
242 }
243
244 static void close_pcap()
245 {
246         if (pcap == NULL)
247                 return;
248
249         pcap_use_count--;
250         if (pcap_use_count == 0)
251         {
252                 pcap_close(pcap);
253                 pcap = NULL;
254         }
255 }
256
257 static void allocate_data_buffer(int size)
258 {
259         if (data_buffer_length < size)
260         {
261                 free(data_buffer);
262                 data_buffer_length = size;
263                 data_buffer = malloc(data_buffer_length);
264         }
265 }
266
267 static void make_device_name(tunemu_device device, int unit_number)
268 {
269         snprintf(device, sizeof(tunemu_device), "ppp%d", unit_number);
270 }
271
272 static int check_device_name(tunemu_device device)
273 {
274         if (strlen(device) < 4)
275                 return -1;
276
277         int unit_number = atoi(device + 3);
278         if (unit_number < 0 || unit_number > 999)
279                 return -1;
280
281         tunemu_device compare;
282         make_device_name(compare, unit_number);
283
284         if (strcmp(device, compare) != 0)
285                 return -1;
286
287         return 0;
288 }
289
290 int tunemu_open(tunemu_device device)
291 {
292         int ppp_unit_number = -1;
293         if (device[0] != 0)
294         {
295                 if (check_device_name(device) < 0)
296                 {
297                         tun_error("invalid device name \"%s\"", device);
298                         return -1;
299                 }
300
301                 ppp_unit_number = atoi(device + 3);
302         }
303
304         int ppp_unit_fd = ppp_new_unit(&ppp_unit_number);
305         if (ppp_unit_fd < 0)
306                 return -1;
307
308         if (ppp_setup_unit(ppp_unit_fd) < 0)
309         {
310                 close(ppp_unit_fd);
311                 return -1;
312         }
313
314         if (open_pcap() < 0)
315         {
316                 close(ppp_unit_fd);
317                 return -1;
318         }
319
320         make_device_name(device, ppp_unit_number);
321
322         return ppp_unit_fd;
323 }
324
325 int tunemu_close(int ppp_sockfd)
326 {
327         int ret = close(ppp_sockfd);
328
329         if (ret == 0)
330                 close_pcap();
331
332         return ret;
333 }
334
335 int tunemu_read(int ppp_sockfd, char *buffer, int length)
336 {
337         allocate_data_buffer(length + 2);
338
339         length = read(ppp_sockfd, data_buffer, length + 2);
340         if (length < 0)
341         {
342                 tun_error("reading packet: %s", strerror(errno));
343                 return length;
344         }
345         tun_noerror();
346
347         length -= 2;
348         if (length < 0)
349                 return 0;
350
351         memcpy(buffer, data_buffer + 2, length);
352
353         return length;
354 }
355
356 int tunemu_write(int ppp_sockfd, char *buffer, int length)
357 {
358         allocate_data_buffer(length + 4);
359
360         data_buffer[0] = 0x02;
361         data_buffer[1] = 0x00;
362         data_buffer[2] = 0x00;
363         data_buffer[3] = 0x00;
364
365         memcpy(data_buffer + 4, buffer, length);
366
367         if (pcap == NULL)
368         {
369                 tun_error("pcap not open");
370                 return -1;
371         }
372
373         length = pcap_inject(pcap, data_buffer, length + 4);
374         if (length < 0)
375         {
376                 tun_error("injecting packet: %s", pcap_geterr(pcap));
377                 return length;
378         }
379         tun_noerror();
380
381         length -= 4;
382         if (length < 0)
383                 return 0;
384
385         return length;
386 }