4a5d42178c
This is a slightly modified patch from Grzegorz Dymarek that allows tinc to use the tunemu device, which allows tinc to be compiled for iPhones and recent iPods. To enable support for tunemu, the --enable-tunemu option has to be used when running the configure script.
386 lines
6.9 KiB
C
386 lines
6.9 KiB
C
/*
|
|
* 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;
|
|
}
|