I am developing a VPN client on MacOS. My program creates a UTUN interface and an associated rule in the route table. Its purpose is reading packets sent to 44.10.0.0/16, doing stuff with them, then forge and reply packets to the sender application.
To do that, my program :
create a utun interface, and associate it the IP address 44.10.0.1.
create the routing rule 44.10.0.0/16 -\> utun device
attach itself to the utun interface
is currently able to read packets from the utun interface
seems to be able to write packets to the utun interface, as they appears in Wireshark. They seem to be well formatted. However, the kernel seems to ignore them, for an unknown reason.
What I have tried
To test my program, each time a TCP packet is read from the utun interface, it forges a RST TCP packet that is sent back to the sender. However, the sender never receives the packet:
Expected behavior
In this example, I manually execute "curl 44.10.0.240:8080" from a terminal. This sends the TCP SYN packets visible in the capture. As soon as a packet is read, my program replies with a RST packet. The RST packets are also visibles in the capture. However, by receiving a RST packet, the curl command should stop trying to connect, but it continues to send SYN packets, as if he did not receive the RST packet.
Complementary information
Configuration
Here is the interface configuration while my program runs:
ifconfig utun3
utun3: flags=8051<UP,POINTOPOINT,RUNNING,MULTICAST> mtu 1500
inet 44.10.0.1 --> 44.10.0.1 netmask 0xff000000
Note that the interface is configured as "Point-to-Point": I had to specify source and destination IP addresses during setup. I can't figure out another way of configuring a utun interface in MacOS.
Here is the routing rule:
Destination Gateway Flags Netif Expire
44 44.10.0.1 UGSc utun3
44.10.0.1 44.10.0.1 UH utun3
Code
Here is the part of my code I use to achieve the configuration of the utun interface:
/* Create interface */
int fd = socket(PF_SYSTEM, SOCK_DGRAM, SYSPROTO_CONTROL);
struct ctl_info ctlInfo = {0};
strlcpy(ctlInfo.ctl_name, "com.apple.net.utun_control", sizeof(ctlInfo.ctl_name));
if (ioctl(fd, CTLIOCGINFO, &ctlInfo) == -1) {
perror("ioctl");
return -1;
}
/* Connect to interface */
struct sockaddr_ctl sc = {0};
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 = 4;/* On my machine, this is the available sc unit for the previously created interface */
if (connect(fd, (struct sockaddr *)&sc, sizeof(sc)) < 0) {
perror("Connect");
return -1;
}
/* Configure IP addresses */
char interface_name[] = "utun3";
struct sockaddr_in *sin;
struct ifaliasreq ifra;
int = socket(AF_INET, SOCK_DGRAM, 0);
if (s < 0) {
perror("socket(AF_INET, SOCK_DGRAM)");
return -1;
}
memset(&ifra, 0, sizeof(ifra));
strncpy(ifra.ifra_name, interface_name, sizeof(ifra.ifra_name));
// Setup source address
sin = (struct sockaddr_in *)&ifra.ifra_addr;
sin->sin_family = AF_INET;
inet_pton(AF_INET, "44.10.0.1", &sin->sin_addr);
sin->sin_len = sizeof(*sin);
// Setup destination address
sin = (struct sockaddr_in *)&ifra.ifra_broadaddr;
sin->sin_family = AF_INET;
inet_pton(AF_INET, "44.10.0.1", &sin->sin_addr);
sin->sin_len = sizeof(*sin);
if (ioctl(s, SIOCAIFADDR, &ifra) == -1) {
perror("ioctl (SIOCAIFADDR)");
close(s);
return -1;
}
close(s);
After that, I easily use read() and write() on fd to read or write packets from/to the interface.
Other strange behavior that could help
I noticed that I am not able to receive any packet if I start a server listening on the configured utun interface.
For example, if I execute:
nc -l 44.10.0.1 7777
Netcat correctly listens for incoming connections.
However, in the same time, If I try to execute "curl 44.10.0.1:7777", SYN packets are never received even though they appears in Wireshark. This behavior may be the same cause of my problem.
Note that the MacOS firewall is disabled. Pf is also disabled. I also verified the TCP sequence/ack numbers of the forged packets and they seem correct.
Do I miss something in my network configuration ?
Why am I able to see packets written in Wireshark attached to the interface, while these are not delivered to the target application ?
How can I configure my utun interface/routing table or adapt my code to make the kernel interpret and deliver my written packets to the target applications ?