Add support for iPhones and recent iPods.
[tinc] / src / bsd / tunemu.c
diff --git a/src/bsd/tunemu.c b/src/bsd/tunemu.c
new file mode 100644 (file)
index 0000000..f532b04
--- /dev/null
@@ -0,0 +1,386 @@
+/*
+ *  tunemu - Tun device emulation for Darwin
+ *  Copyright (C) 2009 Friedrich Schöller <friedrich.schoeller@gmail.com>
+ *  
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *  
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *  
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *  
+ */
+
+#include "tunemu.h"
+
+#include <sys/socket.h>
+#include <unistd.h>
+#include <sys/ioctl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <memory.h>
+#include <util.h>
+#include <pcap.h>
+#include <stdarg.h>
+#include <errno.h>
+#include <stdint.h>
+#include <stdint.h>
+#include <ctype.h>
+#include <fcntl.h>
+
+#define PPPPROTO_CTL 1
+
+#define PPP_IP         0x21
+#define PPP_IPV6       0x57
+
+#define SC_LOOP_TRAFFIC 0x00000200
+
+#define PPPIOCNEWUNIT  _IOWR('t', 62, int)
+#define        PPPIOCSFLAGS    _IOW('t', 89, int)
+#define PPPIOCSNPMODE  _IOW('t', 75, struct npioctl)
+#define PPPIOCATTCHAN  _IOW('t', 56, int)
+#define PPPIOCGCHAN            _IOR('t', 55, int)
+#define PPPIOCCONNECT  _IOW('t', 58, int)
+#define        PPPIOCGUNIT             _IOR('t', 86, int)
+
+struct sockaddr_ppp
+{
+       u_int8_t ppp_len;
+       u_int8_t ppp_family;
+       u_int16_t ppp_proto;
+       u_int32_t ppp_cookie;
+};
+
+enum NPmode
+{
+       NPMODE_PASS,
+    NPMODE_DROP,
+    NPMODE_ERROR,
+    NPMODE_QUEUE
+};
+
+struct npioctl
+{
+       int protocol;
+       enum NPmode mode;
+};
+
+#define PPP_KEXT_PATH "/System/Library/Extensions/PPP.kext"
+
+#define ERROR_BUFFER_SIZE 1024
+
+char tunemu_error[ERROR_BUFFER_SIZE];
+
+static int pcap_use_count = 0;
+static pcap_t *pcap = NULL;
+
+static int data_buffer_length = 0;
+static char *data_buffer = NULL;
+
+static void tun_error(char *format, ...)
+{
+       va_list vl;
+       va_start(vl, format);
+       vsnprintf(tunemu_error, ERROR_BUFFER_SIZE, format, vl);
+       va_end(vl);
+}
+
+static void tun_noerror()
+{
+       *tunemu_error = 0;
+}
+
+static void closeall()
+{
+    int fd = getdtablesize();
+       while (fd--)
+               close(fd);
+
+    open("/dev/null", O_RDWR, 0);
+    dup(0);
+    dup(0);
+}
+
+static int ppp_load_kext()
+{
+       int pid = fork();
+       if (pid < 0)
+       {
+               tun_error("fork for ppp kext: %s", strerror(errno));
+               return -1;
+       }
+
+       if (pid == 0)
+       {
+               closeall();
+               execle("/sbin/kextload", "kextload", PPP_KEXT_PATH, NULL, NULL);
+               exit(1);
+       }
+
+       int status;
+       while (waitpid(pid, &status, 0) < 0)
+       {
+               if (errno == EINTR)
+                       continue;
+
+               tun_error("waitpid for ppp kext: %s", strerror(errno));
+               return -1;
+       }
+
+       if (WEXITSTATUS(status) != 0)
+       {
+               tun_error("could not load ppp kext \"%s\"", PPP_KEXT_PATH);
+               return -1;
+       }
+
+       tun_noerror();
+       return 0;
+}
+
+static int ppp_new_instance()
+{
+       // create ppp socket
+    int ppp_sockfd = socket(PF_PPP, SOCK_RAW, PPPPROTO_CTL);
+    if (ppp_sockfd < 0)
+       {
+               if (ppp_load_kext() < 0)
+                       return -1;
+
+               ppp_sockfd = socket(PF_PPP, SOCK_RAW, PPPPROTO_CTL);
+               if (ppp_sockfd < 0)
+               {
+                       tun_error("creating ppp socket: %s", strerror(errno));
+                       return -1;
+               }
+       }
+
+       // connect to ppp procotol
+    struct sockaddr_ppp pppaddr;
+    pppaddr.ppp_len = sizeof(struct sockaddr_ppp);
+    pppaddr.ppp_family = AF_PPP;
+    pppaddr.ppp_proto = PPPPROTO_CTL;
+    pppaddr.ppp_cookie = 0;
+    if (connect(ppp_sockfd, (struct sockaddr *)&pppaddr, sizeof(struct sockaddr_ppp)) < 0)
+       {
+               tun_error("connecting ppp socket: %s", strerror(errno));
+               close(ppp_sockfd);
+               return -1;
+    }
+
+       tun_noerror();
+       return ppp_sockfd;
+}
+
+static int ppp_new_unit(int *unit_number)
+{
+       int fd = ppp_new_instance();
+       if (fd < 0)
+               return -1;
+
+       // create ppp unit
+       if (ioctl(fd, PPPIOCNEWUNIT, unit_number) < 0)
+       {
+               tun_error("creating ppp unit: %s", strerror(errno));
+               close(fd);
+               return -1;
+    }
+
+       tun_noerror();
+       return fd;
+}
+
+static int ppp_setup_unit(int unit_fd)
+{
+       // send traffic to program
+       int flags = SC_LOOP_TRAFFIC;
+       if (ioctl(unit_fd, PPPIOCSFLAGS, &flags) < 0)
+       {
+               tun_error("setting ppp loopback mode: %s", strerror(errno));
+               return -1;
+    }
+
+       // allow packets
+       struct npioctl npi;
+       npi.protocol = PPP_IP;
+       npi.mode = NPMODE_PASS;
+       if (ioctl(unit_fd, PPPIOCSNPMODE, &npi) < 0)
+       {
+               tun_error("starting ppp unit: %s", strerror(errno));
+               return -1;
+       }
+
+       tun_noerror();
+       return 0;
+}
+
+static int open_pcap()
+{
+       if (pcap != NULL)
+       {
+               pcap_use_count++;
+               return 0;
+       }
+
+       char errbuf[PCAP_ERRBUF_SIZE];
+       pcap = pcap_open_live("lo0", BUFSIZ, 0, 1, errbuf);
+       pcap_use_count = 1;
+
+       if (pcap == NULL)
+       {
+               tun_error("opening pcap: %s", errbuf);
+               return -1;
+       }
+
+       tun_noerror();
+       return 0;
+}
+
+static void close_pcap()
+{
+       if (pcap == NULL)
+               return;
+
+       pcap_use_count--;
+       if (pcap_use_count == 0)
+       {
+               pcap_close(pcap);
+               pcap = NULL;
+       }
+}
+
+static void allocate_data_buffer(int size)
+{
+       if (data_buffer_length < size)
+       {
+               free(data_buffer);
+               data_buffer_length = size;
+               data_buffer = malloc(data_buffer_length);
+       }
+}
+
+static void make_device_name(tunemu_device device, int unit_number)
+{
+       snprintf(device, sizeof(tunemu_device), "ppp%d", unit_number);
+}
+
+static int check_device_name(tunemu_device device)
+{
+       if (strlen(device) < 4)
+               return -1;
+
+       int unit_number = atoi(device + 3);
+       if (unit_number < 0 || unit_number > 999)
+               return -1;
+
+       tunemu_device compare;
+       make_device_name(compare, unit_number);
+
+       if (strcmp(device, compare) != 0)
+               return -1;
+
+       return 0;
+}
+
+int tunemu_open(tunemu_device device)
+{
+       int ppp_unit_number = -1;
+       if (device[0] != 0)
+       {
+               if (check_device_name(device) < 0)
+               {
+                       tun_error("invalid device name \"%s\"", device);
+                       return -1;
+               }
+
+               ppp_unit_number = atoi(device + 3);
+       }
+
+       int ppp_unit_fd = ppp_new_unit(&ppp_unit_number);
+       if (ppp_unit_fd < 0)
+               return -1;
+
+       if (ppp_setup_unit(ppp_unit_fd) < 0)
+       {
+               close(ppp_unit_fd);
+               return -1;
+       }
+
+       if (open_pcap() < 0)
+       {
+               close(ppp_unit_fd);
+               return -1;
+       }
+
+       make_device_name(device, ppp_unit_number);
+
+       return ppp_unit_fd;
+}
+
+int tunemu_close(int ppp_sockfd)
+{
+       int ret = close(ppp_sockfd);
+
+       if (ret == 0)
+               close_pcap();
+
+       return ret;
+}
+
+int tunemu_read(int ppp_sockfd, char *buffer, int length)
+{
+       allocate_data_buffer(length + 2);
+
+       length = read(ppp_sockfd, data_buffer, length + 2);
+       if (length < 0)
+       {
+               tun_error("reading packet: %s", strerror(errno));
+               return length;
+       }
+       tun_noerror();
+
+       length -= 2;
+       if (length < 0)
+               return 0;
+
+       memcpy(buffer, data_buffer + 2, length);
+
+       return length;
+}
+
+int tunemu_write(int ppp_sockfd, char *buffer, int length)
+{
+       allocate_data_buffer(length + 4);
+
+       data_buffer[0] = 0x02;
+       data_buffer[1] = 0x00;
+       data_buffer[2] = 0x00;
+       data_buffer[3] = 0x00;
+
+       memcpy(data_buffer + 4, buffer, length);
+
+       if (pcap == NULL)
+       {
+               tun_error("pcap not open");
+               return -1;
+       }
+
+       length = pcap_inject(pcap, data_buffer, length + 4);
+       if (length < 0)
+       {
+               tun_error("injecting packet: %s", pcap_geterr(pcap));
+               return length;
+       }
+       tun_noerror();
+
+       length -= 4;
+       if (length < 0)
+               return 0;
+
+       return length;
+}