Move macOS-specific code into a subdirectory
[tinc] / src / bsd / darwin / 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         uint8_t ppp_len;
54         uint8_t ppp_family;
55         uint16_t ppp_proto;
56         uint32_t ppp_cookie;
57 };
58
59 enum NPmode {
60         NPMODE_PASS,
61         NPMODE_DROP,
62         NPMODE_ERROR,
63         NPMODE_QUEUE
64 };
65
66 struct npioctl {
67         int protocol;
68         enum NPmode mode;
69 };
70
71 #define PPP_KEXT_PATH "/System/Library/Extensions/PPP.kext"
72
73 #define ERROR_BUFFER_SIZE 1024
74
75 char tunemu_error[ERROR_BUFFER_SIZE];
76
77 static int pcap_use_count = 0;
78 static pcap_t *pcap = NULL;
79
80 static size_t data_buffer_length = 0;
81 static uint8_t *data_buffer = NULL;
82
83 static void tun_error(char *format, ...) ATTR_FORMAT(printf, 1, 2);
84 static void tun_error(char *format, ...) {
85         va_list vl;
86         va_start(vl, format);
87         vsnprintf(tunemu_error, sizeof(tunemu_error), format, vl);
88         va_end(vl);
89 }
90
91 static void tun_noerror() {
92         *tunemu_error = 0;
93 }
94
95 static void closeall() {
96         int fd = getdtablesize();
97
98         while(fd--) {
99                 close(fd);
100         }
101
102         open("/dev/null", O_RDWR, 0);
103         dup(0);
104         dup(0);
105 }
106
107 static int ppp_load_kext() {
108         int pid = fork();
109
110         if(pid < 0) {
111                 tun_error("fork for ppp kext: %s", strerror(errno));
112                 return -1;
113         }
114
115         if(pid == 0) {
116                 closeall();
117                 execle("/sbin/kextload", "kextload", PPP_KEXT_PATH, NULL, NULL);
118                 exit(1);
119         }
120
121         int status;
122
123         while(waitpid(pid, &status, 0) < 0) {
124                 if(errno == EINTR) {
125                         continue;
126                 }
127
128                 tun_error("waitpid for ppp kext: %s", strerror(errno));
129                 return -1;
130         }
131
132         if(WEXITSTATUS(status) != 0) {
133                 tun_error("could not load ppp kext \"%s\"", PPP_KEXT_PATH);
134                 return -1;
135         }
136
137         tun_noerror();
138         return 0;
139 }
140
141 static int ppp_new_instance() {
142         // create ppp socket
143         int ppp_sockfd = socket(PF_PPP, SOCK_RAW, PPPPROTO_CTL);
144
145         if(ppp_sockfd < 0) {
146                 if(ppp_load_kext() < 0) {
147                         return -1;
148                 }
149
150                 ppp_sockfd = socket(PF_PPP, SOCK_RAW, PPPPROTO_CTL);
151
152                 if(ppp_sockfd < 0) {
153                         tun_error("creating ppp socket: %s", strerror(errno));
154                         return -1;
155                 }
156         }
157
158         // connect to ppp procotol
159         struct sockaddr_ppp pppaddr;
160         pppaddr.ppp_len = sizeof(struct sockaddr_ppp);
161         pppaddr.ppp_family = AF_PPP;
162         pppaddr.ppp_proto = PPPPROTO_CTL;
163         pppaddr.ppp_cookie = 0;
164
165         if(connect(ppp_sockfd, (struct sockaddr *)&pppaddr, sizeof(struct sockaddr_ppp)) < 0) {
166                 tun_error("connecting ppp socket: %s", strerror(errno));
167                 close(ppp_sockfd);
168                 return -1;
169         }
170
171         tun_noerror();
172         return ppp_sockfd;
173 }
174
175 static int ppp_new_unit(int *unit_number) {
176         int fd = ppp_new_instance();
177
178         if(fd < 0) {
179                 return -1;
180         }
181
182         // create ppp unit
183         if(ioctl(fd, PPPIOCNEWUNIT, unit_number) < 0) {
184                 tun_error("creating ppp unit: %s", strerror(errno));
185                 close(fd);
186                 return -1;
187         }
188
189         tun_noerror();
190         return fd;
191 }
192
193 static int ppp_setup_unit(int unit_fd) {
194         // send traffic to program
195         int flags = SC_LOOP_TRAFFIC;
196
197         if(ioctl(unit_fd, PPPIOCSFLAGS, &flags) < 0) {
198                 tun_error("setting ppp loopback mode: %s", strerror(errno));
199                 return -1;
200         }
201
202         // allow packets
203         struct npioctl npi;
204         npi.protocol = PPP_IP;
205         npi.mode = NPMODE_PASS;
206
207         if(ioctl(unit_fd, PPPIOCSNPMODE, &npi) < 0) {
208                 tun_error("starting ppp unit: %s", strerror(errno));
209                 return -1;
210         }
211
212         tun_noerror();
213         return 0;
214 }
215
216 static int open_pcap() {
217         if(pcap != NULL) {
218                 pcap_use_count++;
219                 return 0;
220         }
221
222         char errbuf[PCAP_ERRBUF_SIZE];
223         pcap = pcap_open_live("lo0", BUFSIZ, 0, 1, errbuf);
224         pcap_use_count = 1;
225
226         if(pcap == NULL) {
227                 tun_error("opening pcap: %s", errbuf);
228                 return -1;
229         }
230
231         tun_noerror();
232         return 0;
233 }
234
235 static void close_pcap() {
236         if(pcap == NULL) {
237                 return;
238         }
239
240         pcap_use_count--;
241
242         if(pcap_use_count == 0) {
243                 pcap_close(pcap);
244                 pcap = NULL;
245         }
246 }
247
248 static void allocate_data_buffer(size_t size) {
249         if(data_buffer_length < size) {
250                 free(data_buffer);
251                 data_buffer_length = size;
252                 data_buffer = malloc(data_buffer_length);
253         }
254 }
255
256 static void make_device_name(tunemu_device device, int unit_number) {
257         snprintf(device, sizeof(tunemu_device), "ppp%d", unit_number);
258 }
259
260 static int check_device_name(tunemu_device device) {
261         if(strlen(device) < 4) {
262                 return -1;
263         }
264
265         int unit_number = atoi(device + 3);
266
267         if(unit_number < 0 || unit_number > 999) {
268                 return -1;
269         }
270
271         tunemu_device compare;
272         make_device_name(compare, unit_number);
273
274         if(strcmp(device, compare) != 0) {
275                 return -1;
276         }
277
278         return 0;
279 }
280
281 int tunemu_open(tunemu_device device) {
282         int ppp_unit_number = -1;
283
284         if(device[0] != 0) {
285                 if(check_device_name(device) < 0) {
286                         tun_error("invalid device name \"%s\"", device);
287                         return -1;
288                 }
289
290                 ppp_unit_number = atoi(device + 3);
291         }
292
293         int ppp_unit_fd = ppp_new_unit(&ppp_unit_number);
294
295         if(ppp_unit_fd < 0) {
296                 return -1;
297         }
298
299         if(ppp_setup_unit(ppp_unit_fd) < 0) {
300                 close(ppp_unit_fd);
301                 return -1;
302         }
303
304         if(open_pcap() < 0) {
305                 close(ppp_unit_fd);
306                 return -1;
307         }
308
309         make_device_name(device, ppp_unit_number);
310
311         return ppp_unit_fd;
312 }
313
314 int tunemu_close(int ppp_sockfd) {
315         int ret = close(ppp_sockfd);
316
317         if(ret == 0) {
318                 close_pcap();
319         }
320
321         return ret;
322 }
323
324 ssize_t tunemu_read(int ppp_sockfd, uint8_t *buffer, size_t buflen) {
325         allocate_data_buffer(buflen + 2);
326
327         ssize_t length = read(ppp_sockfd, data_buffer, buflen + 2);
328
329         if(length < 0) {
330                 tun_error("reading packet: %s", strerror(errno));
331                 return length;
332         }
333
334         tun_noerror();
335
336         length -= 2;
337
338         if(length < 0) {
339                 return 0;
340         }
341
342         memcpy(buffer, data_buffer + 2, length);
343
344         return length;
345 }
346
347 ssize_t tunemu_write(uint8_t *buffer, size_t buflen) {
348         allocate_data_buffer(buflen + 4);
349
350         data_buffer[0] = 0x02;
351         data_buffer[1] = 0x00;
352         data_buffer[2] = 0x00;
353         data_buffer[3] = 0x00;
354
355         memcpy(data_buffer + 4, buffer, buflen);
356
357         if(pcap == NULL) {
358                 tun_error("pcap not open");
359                 return -1;
360         }
361
362         ssize_t length = pcap_inject(pcap, data_buffer, buflen + 4);
363
364         if(length < 0) {
365                 tun_error("injecting packet: %s", pcap_geterr(pcap));
366                 return length;
367         }
368
369         tun_noerror();
370
371         length -= 4;
372
373         if(length < 0) {
374                 return 0;
375         }
376
377         return length;
378 }