就不用去看别人乱写一通的文章了,我在这里直接说重点,这东西不是什么NB玩意儿,整那么多花里胡哨没得意义。
有需要的童鞋可以自己在MAC虚拟机上、或者MAC真机电脑上面写C/C++程序整整。
在MACOS上苹果在很早之前的版本就内置了 utun 驱动,这个驱动有几个限制,简单明了的说一下大家就懂了。
1、utun 驱动不可以设置为特定的网卡名字,跟LINUX 二点几版本内核集成的 tun 驱动不同。
2、utun 驱动只支持P2P点对点模式,所以设置不了MAC、整不了ARP这些L2的东西。
3、utun 驱动需要明确说明,IP协议类型是AF_INET、还是AF_INET6,就是读入的IP包、写入utun的IP包头前有四个字节来表示。
虽然我是整不懂,烂苹果(Apple)为啥要整四个字节,也许是为了CPU读写更快那么一丝丝(真就一丝丝,笑~)?实际上就它就只用了一个字节,其它三个字节都是0、别问撒、自己看AF_INET、AF_INET6 宏定义的数值,跟IP协议头上,PROTO占多少位就晓得。
所以它的结构就是(注意:苹果是小端机,低位在前,高位在后,但是网络字节序是大端,就编程高位在前,低位在后)
所以就变成这样了:
0 0 0 0 IP头 IP载荷
4、utun 驱动通过 utunnum(就是 sockaddr_ctl::sc_unit)来管理使用那个 utun,没有就创建,这个不可以是0,最少就1。
所以:utun0 就等于 utunnum = 0 + 1
如果写1出来就是 utun0、如果写2就是 utun1,需要说明一点在比较有历史遗留感的 MAC OSX系统上面,这个最大就是255,所以你只能在 0~255 之间重试,后头可以整到42亿了,但是最好就0~255。
5、在 macos 上面操作 utun 的IP、MASK、GW这块,用命令行来整,ifconfig 可以办到,或者用 networksetup 命令,因为OS X API C/C++接口不是撒系统都能用,动不动就失败,用命令行去配置兼容性要好的多,除了有点慢,但是执行成功与否都是可以取得的,成功返回0,否则返回非0。
废话不多说,直接上代码:
cpp
#include <sys/socket.h>
#include <sys/sysctl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <net/if.h>
#include <net/route.h>
#include <netinet/in.h>
#include <sys/ioctl.h> // ioctl
#include <sys/kern_control.h> // struct socketaddr_ctl
#include <net/if_utun.h> // UTUN_CONTROL_NAME
#include <sys/sys_domain.h>
#include <arpa/inet.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <ifaddrs.h>
#include <cstring>
#include <iostream>
bool utun_set_cloexec(int fd) noexcept
{
int flags = fcntl(fd, F_GETFD, 0);
if (flags == -1)
{
return false;
}
flags |= FD_CLOEXEC;
if (fcntl(fd, F_SETFD, flags) < 0)
{
return false;
}
return true;
}
/* Helper functions that tries to open utun device
* return -2 on early initialization failures (utun not supported
* at all (old OS X) and -1 on initlization failure of utun
* device (utun works but utunX is already used */
int utun_open(int utunnum) noexcept
{
if (utunnum < 0 || utunnum > UINT8_MAX)
{
return -1;
}
struct ctl_info ctlInfo;
memset(&ctlInfo, 0, sizeof(ctlInfo));
strlcpy(ctlInfo.ctl_name, UTUN_CONTROL_NAME, sizeof(ctlInfo.ctl_name));
int fd = socket(PF_SYSTEM, SOCK_DGRAM, SYSPROTO_CONTROL);
if (fd == -1)
{
return fd;
}
if (ioctl(fd, CTLIOCGINFO, &ctlInfo) < 0)
{
close(fd);
return -1;
}
struct sockaddr_ctl sc;
memset(&sc, 0, sizeof(sc));
sc.sc_id = ctlInfo.ctl_id;
sc.sc_len = sizeof(sc);
sc.sc_family = AF_SYSTEM;
sc.ss_sysaddr = AF_SYS_CONTROL;
sc.sc_unit = utunnum + 1;
if (connect(fd, (struct sockaddr*)&sc, sizeof(sc)) < 0)
{
close(fd);
return -1;
}
int flags = fcntl(fd, F_GETFL, 0);
if (flags == -1)
{
close(fd);
return -1;
}
if (fcntl(fd, F_SETFL, O_NONBLOCK) < 0)
{
close(fd);
return -1;
}
utun_set_cloexec(fd);
return fd;
}
bool utun_get_if_name(int tun, ppp::string& ifrName) noexcept
{
ifrName.clear();
if (tun == -1)
{
return false;
}
/* Retrieve the assigned interface name. */
char utunname[1000];
socklen_t utunname_len = sizeof(utunname);
if (getsockopt(tun, SYSPROTO_CONTROL, UTUN_OPT_IFNAME, utunname, &utunname_len) < 0)
{
return false;
}
ifrName = utunname;
return true;
}
bool utun_set_if_ip_gw_and_mask(int tun, const ppp::string& ip, const ppp::string& gw, const ppp::string& mask) noexcept
{
if (tun == -1 || ip.empty() || mask.empty())
{
return false;
}
ppp::string name;
if (!utun_get_if_name(tun, name))
{
return false;
}
char cmd[1000];
snprintf(cmd, sizeof(cmd), "ifconfig %s inet %s %s netmask %s up", name.data(), ip.data(), gw.data(), mask.data());
int status = system(cmd);
return status == 0;
}
bool utun_set_mtu(int tun, int mtu) noexcept
{
if (tun == -1)
{
return false;
}
// MTU: 68 ~ 65535 RANGE.
if (mtu < 68)
{
mtu = ITap::Mtu;
}
ppp::string name;
if (!utun_get_if_name(tun, name))
{
return false;
}
char buf[1000];
snprintf(buf, sizeof(buf), "ifconfig %s mtu %d > /dev/null 2>&1", name.data(), mtu);
int status = system(buf);
return status == 0;
}
int utun_utunnum(const ppp::string& dev) noexcept
{
int v = 0;
if (dev.empty())
{
return v;
}
ppp::string s;
for (char ch : dev)
{
if (ch >= '0' && ch <= '9')
{
s.append(1, ch);
continue;
}
}
v = atoi(s.data());
if (v < 0)
{
v = 0;
}
elif(v > UINT8_MAX)
{
v = UINT8_MAX;
}
return v;
}
参考资料,可以看这:
// https://bugzilla.mozilla.org/show_bug.cgi?id=1579424
// https://gist.github.com/etodd/d8184b91c02306b889c13eb03f81fb6d
// https://github.com/songgao/water/issues/3#issuecomment-158704536
// https://build.openvpn.net/doxygen/route_8c_source.html
// https://github.com/OpenVPN/openvpn/blob/master/src/openvpn/tun.c#L3250
读入的包;
cpp
// According to the low-level interface documentation of the operating system (obscure location),
// OSX utun packets need to add four bytes to the frame header to indicate the AF_INET and AF_INET6 protocol types.
int packet_length = e.PacketLength;
if (packet_length > sizeof(uint32_t))
{
Byte* packet = (Byte*)e.Packet;
Byte protocol = (Byte)ntohl(*(uint32_t*)packet);
if (protocol == AF_INET)
{
e.Packet = packet + sizeof(uint32_t);
e.PacketLength = packet_length - sizeof(uint32_t);
// 你的代码。
}
}
写入的包:
cpp
// Check whether the IP packet protocol output by the VPN protocol stack is AF_INET.
struct ip_hdr* iphdr = (struct ip_hdr*)packet;
if (ip_hdr::IPH_V(iphdr) != ip_hdr::IP_VER) // IP_VER = 4
{
return false;
}
// Copy to the current thread or coroutine stack, reduce memory fragmentation and write to the kernel,
// Discarding the report if it exceeds the MTU size.
if (packet_size > 1500) // MTU=1500
{
return false;
}
else
{
Byte chunk[1500 + sizeof(uint32_t)];
size_t chunk_size = packet_size + sizeof(uint32_t);
*(uint32_t*)chunk = htonl(AF_INET);
memcpy(chunk + sizeof(uint32_t), packet, packet_size);
ssize_t bytes_transferred = ::write(tun, chunk, chunk_size);
return bytes_transferred > -1;
}