still quite broken but a good starting point

This commit is contained in:
Morgan 'ARR\!' Allen 2024-01-05 13:12:09 -08:00
commit cbf4ff2ad5
8 changed files with 661 additions and 0 deletions

9
CMakeLists.txt Normal file
View file

@ -0,0 +1,9 @@
cmake_minimum_required(VERSION 3.18)
project('ifname')
add_executable(ifnow src/main.c src/tun.c src/now.c)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g -ggdb")
target_include_directories(ifnow PUBLIC ./include/)
set_target_properties(ifnow PROPERTIES LINK_LIBRARIES -pthread)

33
README.md Normal file
View file

@ -0,0 +1,33 @@
# ifnow
A Linux network interface device for communication over the ESP-Now wireless broadcast protocol.
NOTE: This is EXTREMELY work in progress and as it requires root permissions is not recommended to be run unmonitored.
# Build
```
git clone https://git.oit.cloud/morgan/ifnow.git
cd ifnow
mkdir build
cd build
cmake ../
make
```
This should result in the `ifnow` binary in the build directory.
# Usage
This is two steps, first setting up monitoring mode (using aircrack-ng in this method). You will need to determine
your wireless interface name and replace if different from `wlp2s0` in the example. You will also want to set the
channel to match the ESP32 configuration.
```
sudo airmon-ng start wlp2s0
sudo iw wlp2s0 set channel 1
./ifnow wlp2s0mon now1 10.10.0.13
```
The interface is now up as `now1` and you should be able to write to this interface. In theory `pcap` can
be used to intercept raw packets but if used with LwIP on the ESP32 end, which can be accomplished with
[esp_netif_now](https://git.oit.cloud/morgan/esp_netif_now), any IP stack should work. Has been testing
with UDP and some [CoAP](https://libcoap.net). There are definitely still issues with parsing some incoming
packets.

113
include/ifnow.h Normal file
View file

@ -0,0 +1,113 @@
#ifndef __IFNOW_H__
#define __IFNOW_H__
#define IFNOW_MAX_RECEIVE (512)
#define DATARATE_1Mbps 0x02
#define DATARATE_2Mbps 0x04
#define DATARATE_6Mbps 0x0c
#define DATARATE_9Mbps 0x12
#define DATARATE_12Mbps 0x18
#define DATARATE_18Mbps 0x24
#define DATARATE_24Mbps 0x30
#define DATARATE_36Mbps 0x48
#define DATARATE_48Mbps 0x60
#define DATARATE_54Mbps 0x6c
#define CHANNEL_freq_1 2412
#define CHANNEL_freq_2 2417
#define CHANNEL_freq_3 2422
#define CHANNEL_freq_4 2427
#define CHANNEL_freq_5 2432
#define CHANNEL_freq_6 2437
#define CHANNEL_freq_7 2442
#define CHANNEL_freq_8 2447
#define CHANNEL_freq_9 2452
#define CHANNEL_freq_10 2457
#define CHANNEL_freq_11 2462
#define CHANNEL_freq_12 2467
#define CHANNEL_freq_13 2472
#define WLAN_LEN 24
#define ACTIONFRAME_HEADER_LEN 8
#define VENDORSPECIFIC_CONTENT_LEN 7
struct IEEE80211_radiotap {
uint8_t version;
uint8_t pad;
uint16_t length;
uint32_t present;
uint8_t flags;
uint8_t datarate;
uint16_t channel_freq;
uint16_t channel_flags_quarter;
};
#define IFNOW_RADIOTAP_DEFAULT() {\
.version = 0,\
.pad = 0,\
.length = sizeof(struct IEEE80211_radiotap),\
.present = 0x0000000e,\
.flags = 0x10,\
.datarate = DATARATE_6Mbps,\
.channel_freq = CHANNEL_freq_1,\
.channel_flags_quarter = 0x00c0,\
}
struct IEEE80211_vendorspecific {
uint8_t elementID;
uint8_t length;
uint8_t OUI[3];
uint8_t type;
uint8_t version;
uint8_t payload[250];
} __attribute__((__packed__));
#define IFNOW_VENDORSPECIFIC_DEFAULT() {\
.elementID = 0xdd,\
.OUI[0] = 0x18,\
.OUI[1] = 0xfe,\
.OUI[2] = 0x34,\
.type = 0x04,\
.version = 0x01,\
}
struct IEEE80211_actionframe {
uint8_t category_code;
uint8_t OUI[3];
uint8_t unknown_bytes[4];
struct IEEE80211_vendorspecific content;
} __attribute__((__packed__));
#define IFNOW_ACTIONFRAME_DEFAULT() {\
.category_code = 0x7f,\
.OUI[0] = 0x18,\
.OUI[1] = 0xfe,\
.OUI[2] = 0x34,\
}
struct IEEE80211_wlan {
uint8_t type;
uint8_t flags;
uint16_t duration;
uint8_t da[6];
uint8_t sa[6];
uint8_t bssid[6];
uint16_t seq;
struct IEEE80211_actionframe actionframe;
uint32_t fcs;
};
#define IFNOW_WLAN_DEFAULT() {\
.type = 0xd0,\
.flags = 0x00,\
.duration = 0x0000,\
.seq = 0x0280,\
.fcs = 0x00000000,\
}
typedef struct {
struct IEEE80211_radiotap radiotap;
struct IEEE80211_wlan wlan;
} ifnow_packet_t;
#endif // __IFNOW_H__

28
include/ifnow_now.h Normal file
View file

@ -0,0 +1,28 @@
#ifndef __IFNOW_WLAN_H__
#define __IFNOW_WLAN_H__
#include <linux/filter.h>
#include <pthread.h>
#include <stdint.h>
#include "ifnow.h"
#define ERR_SOCKET_FAILED (-1)
#define ERR_IOCTL_FAILED (-2)
#define ERR_BIND_FAILED (-3)
#define ERR_FILTER_FAILED (-4)
typedef int (*ifnow_now_receive_cb)(void *now_cfg, uint8_t *buf, uint32_t len);
typedef struct {
int fd_if_socket;
char *if_name;
struct sock_fprog filter;
pthread_t thread_id;
ifnow_now_receive_cb receive;
} ifnow_now_cfg_t;
int ifnow_now_init(ifnow_now_cfg_t *now_cfg);
int ifnow_now_send(ifnow_now_cfg_t *now_cfg, uint8_t *buf, uint16_t len);
#endif

26
include/ifnow_tun.h Normal file
View file

@ -0,0 +1,26 @@
#ifndef __IFNOW_TUN_H__
#define __IFNOW_TUN_H__
#include <arpa/inet.h>
#include <linux/filter.h>
#include <pthread.h>
#include <stdint.h>
#include "ifnow.h"
typedef int (*ifnow_tun_receive_cb)(void *tun_cfg, uint8_t *buf, uint32_t len);
typedef struct {
int fd_tuntap;
int fd_netlink;
struct in_addr addr;
char *addr_s;
char *if_name;
pthread_t thread_id;
ifnow_tun_receive_cb receive;
} ifnow_tun_cfg_t;
int ifnow_tun_init(ifnow_tun_cfg_t *tun_cfg);
int ifnow_tun_send(ifnow_tun_cfg_t *tun_cfg, uint8_t *buf, uint8_t len);
#endif

89
src/main.c Normal file
View file

@ -0,0 +1,89 @@
#include <stdio.h>
#include <strings.h>
#include <stdint.h>
#include "ifnow.h"
#include "ifnow_now.h"
#include "ifnow_tun.h"
ifnow_now_cfg_t now_cfg;
ifnow_tun_cfg_t tun_cfg;
#define ANSI_FONT_COL_RESET "\x1b[0m"
#define FONT_COL_CUSTOM_RED "\e[38;2;200;0;0m"
#define FONT_COL_CUSTOM_GREEN "\e[38;2;0;200;0m"
#define FONT_COL_CUSTOM_BLUE "\e[38;2;0;0;200m"
#define BCKGRD_COL_CUSTOM_RED "\e[48;2;200;0;0m"
#define BCKGRD_COL_CUSTOM_GREEN "\e[48;2;0;200;0m"
#define BCKGRD_COL_CUSTOM_BLUE "\e[48;2;0;0;200m"
char *color_chart[] = {
// NOW header
BCKGRD_COL_CUSTOM_GREEN, // category version
FONT_COL_CUSTOM_BLUE, FONT_COL_CUSTOM_BLUE, FONT_COL_CUSTOM_BLUE, // org identifier
FONT_COL_CUSTOM_RED, FONT_COL_CUSTOM_RED, FONT_COL_CUSTOM_RED, FONT_COL_CUSTOM_RED, // random
// Vendor Specific Header
BCKGRD_COL_CUSTOM_GREEN, // Element ID
BCKGRD_COL_CUSTOM_BLUE, // Length
FONT_COL_CUSTOM_BLUE, FONT_COL_CUSTOM_BLUE, FONT_COL_CUSTOM_BLUE, // org identifier
BCKGRD_COL_CUSTOM_BLUE, // Type
BCKGRD_COL_CUSTOM_GREEN, // Version
// Body
NULL
};
uint8_t color_size = 15;
int ifnow_now_receive_callback(void *cfg, uint8_t *buf, uint32_t len) {
ifnow_now_cfg_t *now_cfg = cfg;
printf("NOW receive_count: %d\n", len);
for(uint16_t i = 0; i < len; i++) {
printf("%s%02X"ANSI_FONT_COL_RESET" ", (i < color_size ? color_chart[i] : (buf[i] == 0x11 ? BCKGRD_COL_CUSTOM_RED : "")), buf[i]);
if(i % 16 == 15) printf("\n");
}
printf("\n");
// TODO apply buf to actual structs of headers
uint8_t bp = 57;
int err = ifnow_tun_send(&tun_cfg, buf + bp, len - bp);
if(err < 0) {
perror("Failed to send over tunnel");
}
}
int ifnow_tun_receive_callback(void *cfg, uint8_t *buf, uint32_t len) {
printf("TUN receive_count: %d\n", len);
for(uint32_t i = 0; i < len; i++) {
printf("%02X ", buf[i]);
if(i % 16 == 15) printf("\n");
}
printf("\n");
int err = ifnow_now_send(&now_cfg, buf, len);
if(err < 0) {
perror("Failed to send over now");
}
}
uint8_t main(int argc, char **argv) {
// TODO handle arguments
// * <now_if> wifi interface to listen for ESP-Now packets on
// Interface should be in monitor mode
// * <tun_if> name of new TUN interface
// * <ip_addr> IP address for `now` interface
now_cfg.if_name = argv[1];
now_cfg.receive = &ifnow_now_receive_callback;
tun_cfg.if_name = argv[2];
tun_cfg.addr_s = argv[3];
tun_cfg.receive = &ifnow_tun_receive_callback;
ifnow_now_init(&now_cfg);
ifnow_tun_init(&tun_cfg);
while(1) {};
return 0;
}

189
src/now.c Normal file
View file

@ -0,0 +1,189 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
#include <linux/if_packet.h>
#include <net/ethernet.h>
#include <net/if.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include "ifnow.h"
#include "ifnow_now.h"
ifnow_packet_t packet;
int ifnow_now_send(ifnow_now_cfg_t *now_cfg, uint8_t *buf, uint16_t len) {
struct IEEE80211_radiotap radiotap = IFNOW_RADIOTAP_DEFAULT();
struct IEEE80211_actionframe action = IFNOW_ACTIONFRAME_DEFAULT();
struct IEEE80211_vendorspecific vendor = IFNOW_VENDORSPECIFIC_DEFAULT();
struct IEEE80211_wlan wlan = IFNOW_WLAN_DEFAULT();
memcpy(&packet.radiotap, &radiotap, sizeof(struct IEEE80211_radiotap));
memcpy(&packet.wlan.actionframe, &action, sizeof(struct IEEE80211_actionframe));
memcpy(&packet.wlan.actionframe.content, &vendor, sizeof(struct IEEE80211_vendorspecific));
memcpy(&packet.wlan.actionframe.content.payload, buf, len < 250 ? len : 250);
packet.wlan.actionframe.content.length = len + 5;
memcpy(&packet.wlan.da, buf, 6);
memcpy(&packet.wlan.sa, buf + 6, 6);
memcpy(&packet.wlan.bssid, buf, 6);
printf("NOW: sending %d bytes\n", sizeof(packet));
for(uint16_t i = 0; i < sizeof(packet); i++) {
printf("%02X ", ((uint8_t*)&packet)[i]);
if(i % 16 == 15) printf("\n");
}
printf("\n");
// TODO verify sendto vs send vs write
int ret = sendto(now_cfg->fd_if_socket, &packet, sizeof(packet), 0, NULL, 0);
return ret;
}
void *ifnow_thread_handle(void *arg) {
ifnow_now_cfg_t *now_cfg = arg;
printf("starting ifnow_now thread.\n");
ssize_t receive_count = 0;
uint8_t receive_buffer[IFNOW_MAX_RECEIVE];
while(1) {
receive_count = recv(now_cfg->fd_if_socket, receive_buffer, IFNOW_MAX_RECEIVE, MSG_TRUNC);
if(receive_count > 0 && now_cfg->receive != NULL) {
now_cfg->receive(now_cfg, receive_buffer, receive_count);
}
}
}
int ifnow_now_init(ifnow_now_cfg_t *now_cfg) {
printf("Using interface: %s\n", now_cfg->if_name);
struct sockaddr_ll if_bind_addr;
struct ifreq ifr;
now_cfg->fd_if_socket = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
if(now_cfg->fd_if_socket == -1) {
perror("Failed to initialize IF socket.");
return ERR_SOCKET_FAILED;
}
strncpy((char*)ifr.ifr_name, now_cfg->if_name, IFNAMSIZ);
int ioctrl_err = ioctl(now_cfg->fd_if_socket, SIOCGIFINDEX, &ifr);
if(ioctrl_err < 0) {
perror("Failed to set io ctl on IF socket.");
return ERR_IOCTL_FAILED;
}
if_bind_addr.sll_family = PF_PACKET;
if_bind_addr.sll_protocol = htons(ETH_P_ALL);
if_bind_addr.sll_ifindex = ifr.ifr_ifindex;
int bind_err = bind(now_cfg->fd_if_socket, (struct sockaddr *)&if_bind_addr, sizeof(if_bind_addr));
if(bind_err < 0) {
perror("Failed bind raw socket.");
return ERR_BIND_FAILED;
}
now_cfg->filter.len = 53;
uint32_t MSB_dst = 0;
uint32_t LSB_dst = 0;
uint32_t MSB_src = 0;
uint32_t LSB_src = 0;
uint8_t jeq_dst = 0x30;
uint8_t jeq_src = 0x30;
struct sock_filter temp_code[53] = {
{ 0x30, 0, 0, 0x00000003 },
{ 0x64, 0, 0, 0x00000008 },
{ 0x7, 0, 0, 0x00000000 },
{ 0x30, 0, 0, 0x00000002 },
{ 0x4c, 0, 0, 0x00000000 },
{ 0x2, 0, 0, 0x00000000 },
{ 0x7, 0, 0, 0x00000000 },
{ 0x50, 0, 0, 0x00000000 },
{ 0x54, 0, 0, 0x000000fc },
{ 0x15, 0, 42, 0x000000d0 },
{ 0x40, 0, 0, 0x00000018 },
{ 0x15, 0, 40, 0x7f18fe34 },
{ 0x50, 0, 0, 0x00000020 },
{ 0x15, 0, 38, 0x000000dd },
{ 0x40, 0, 0, 0x00000021 },
{ 0x54, 0, 0, 0x00ffffff },
{ 0x15, 0, 35, 0x0018fe34 },
{ 0x50, 0, 0, 0x00000025 },
{ 0x15, 0, 33, 0x00000004 },
{ 0x50, 0, 0, 0x00000000 },
{ 0x45, 31, 0, 0x00000004 },
{ 0x45, 0, 21, 0x00000008 },
{ 0x50, 0, 0, 0x00000001 },
{ 0x45, 0, 4, 0x00000001 },
{ 0x40, 0, 0, 0x00000012 },
{ jeq_dst, 0, 26, LSB_dst },
{ 0x48, 0, 0, 0x00000010 },
{ jeq_dst, 4, 24, MSB_dst },
{ 0x40, 0, 0, 0x00000006 },
{ jeq_dst, 0, 22, LSB_dst },
{ 0x48, 0, 0, 0x00000004 },
{ jeq_dst, 0, 20, MSB_dst },
{ 0x50, 0, 0, 0x00000001 },
{ 0x45, 0, 13, 0x00000002 },
{ 0x45, 0, 4, 0x00000001 },
{ 0x40, 0, 0, 0x0000001a },
{ jeq_src, 0, 15, LSB_src },
{ 0x48, 0, 0, 0x00000018 },
{ jeq_src, 12, 13, MSB_src },
{ 0x40, 0, 0, 0x00000012 },
{ jeq_src, 0, 11, LSB_src },
{ 0x48, 0, 0, 0x00000010 },
{ jeq_src, 8, 9, MSB_src },
{ 0x40, 0, 0, 0x00000006 },
{ jeq_dst, 0, 7, LSB_dst },
{ 0x48, 0, 0, 0x00000004 },
{ jeq_dst, 0, 5, MSB_dst },
{ 0x40, 0, 0, 0x0000000c },
{ jeq_src, 0, 3, LSB_src },
{ 0x48, 0, 0, 0x0000000a },
{ jeq_src, 0, 1, MSB_src },
{ 0x6, 0, 0, 0x00040000 },
{ 0x6, 0, 0, 0x00000000 }
};
now_cfg->filter.filter = (struct sock_filter*) malloc(sizeof(struct sock_filter)*now_cfg->filter.len);
memcpy(now_cfg->filter.filter, temp_code, sizeof(struct sock_filter) * now_cfg->filter.len);
int sockopt_err = setsockopt(now_cfg->fd_if_socket, SOL_SOCKET, SO_ATTACH_FILTER, &now_cfg->filter, sizeof(now_cfg->filter));
if(sockopt_err < 0) {
perror("Failed to set BPF socket options.");
return ERR_FILTER_FAILED;
}
struct IEEE80211_radiotap radiotap = IFNOW_RADIOTAP_DEFAULT();
struct IEEE80211_wlan wlan = IFNOW_WLAN_DEFAULT();
struct IEEE80211_actionframe actionframe = IFNOW_ACTIONFRAME_DEFAULT();
struct IEEE80211_vendorspecific content = IFNOW_VENDORSPECIFIC_DEFAULT();
memcpy(&packet.radiotap, &radiotap, sizeof(struct IEEE80211_radiotap));
memcpy(&packet.wlan, &wlan, sizeof(struct IEEE80211_wlan));
memcpy(&packet.wlan.actionframe, &actionframe, sizeof(struct IEEE80211_actionframe));
memcpy(&packet.wlan.actionframe.content, &content, sizeof(struct IEEE80211_vendorspecific));
printf("radiotap.channel_freq: %d\n", packet.radiotap.channel_freq);
pthread_create(&now_cfg->thread_id, NULL, &ifnow_thread_handle, now_cfg);
}

174
src/tun.c Normal file
View file

@ -0,0 +1,174 @@
#include <fcntl.h>
#include <pthread.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <linux/if_tun.h>
#include <linux/netlink.h>
#include <linux/rtnetlink.h>
#include <net/if.h>
#include <sys/ioctl.h>
#include "ifnow_tun.h"
// this is called from the ESP Now receive callback
// it passes the contents received from the wifi interface
int ifnow_tun_send(ifnow_tun_cfg_t *tun_cfg, uint8_t *buf, uint8_t len) {
printf("tun: sending %d bytes to fd: %d\n", len, tun_cfg->fd_tuntap);
for(uint16_t i = 0; i < len; i++) {
printf("%02X ", buf[i]);
if(i % 16 == 15) printf("\n");
}
printf("\n");
return write(tun_cfg->fd_tuntap, buf, len);
}
void *ifnow_tun_thread_handle(void *arg) {
ifnow_tun_cfg_t *tun_cfg = arg;
printf("starting ifnow_tun thread.\n");
ssize_t receive_count = 0;
uint8_t receive_buffer[IFNOW_MAX_RECEIVE];
while(1) {
receive_count = read(tun_cfg->fd_tuntap, &receive_buffer, IFNOW_MAX_RECEIVE);
if(receive_count > 0 && tun_cfg->receive != NULL) {
tun_cfg->receive(tun_cfg, (uint8_t*)&receive_buffer, receive_count);
}
};
}
int ifnow_tun_netlink_connect(ifnow_tun_cfg_t *tun_cfg) {
struct sockaddr_nl netlink_socket;
tun_cfg->fd_netlink = socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, NETLINK_ROUTE);
if(tun_cfg->fd_netlink < 0) {
perror("Failed to get netlink socket");
return -4;
}
memset(&netlink_socket, 0, sizeof(struct sockaddr_nl));
netlink_socket.nl_family = AF_NETLINK;
int err = bind(tun_cfg->fd_netlink, (struct sockaddr*) &netlink_socket, sizeof(struct sockaddr_nl));
if(err < 0) {
perror("Failed to bind netlink socket");
return -5;
}
}
int ifnow_tun_netlink_set_ip(ifnow_tun_cfg_t *tun_cfg) {
struct {
struct nlmsghdr header;
struct ifaddrmsg content;
char attributes_buf[64];
} request;
struct rtattr *request_attr;
size_t attributes_buf_avail = sizeof request.attributes_buf;
memset(&request, 0, sizeof request);
request.header.nlmsg_type = RTM_NEWADDR;
request.header.nlmsg_len = NLMSG_LENGTH(sizeof request.content);
request.header.nlmsg_flags = NLM_F_REQUEST | NLM_F_EXCL | NLM_F_CREATE;
request.content.ifa_index = if_nametoindex(tun_cfg->if_name);
request.content.ifa_family = AF_INET;
request.content.ifa_prefixlen = 32;
/* request.attributes[IFA_LOCAL] = address */
request_attr = IFA_RTA(&request.content);
request_attr->rta_type = IFA_LOCAL;
request_attr->rta_len = RTA_LENGTH(sizeof (struct in_addr));
request.header.nlmsg_len += request_attr->rta_len;
inet_pton(AF_INET, tun_cfg->addr_s, RTA_DATA(request_attr));
/* request.attributes[IFA_ADDRESS] = address */
request_attr = RTA_NEXT(request_attr, attributes_buf_avail);
request_attr->rta_type = IFA_ADDRESS;
request_attr->rta_len = RTA_LENGTH(sizeof (struct in_addr));
request.header.nlmsg_len += request_attr->rta_len;
inet_pton(AF_INET, tun_cfg->addr_s, RTA_DATA(request_attr));
if (send(tun_cfg->fd_netlink, &request, request.header.nlmsg_len, 0) == -1) {
perror("Failed to connect netlink");
return -6;
}
}
int ifnow_tun_netlink_up(ifnow_tun_cfg_t *tun_cfg) {
struct {
struct nlmsghdr header;
struct ifinfomsg content;
} request;
printf("ifnow_tun_netlink_up: %s\n", tun_cfg->if_name);
memset(&request, 0, sizeof request);
request.header.nlmsg_len = NLMSG_LENGTH(sizeof request.content);
request.header.nlmsg_flags = NLM_F_REQUEST;
request.header.nlmsg_type = RTM_NEWLINK;
request.content.ifi_index = if_nametoindex(tun_cfg->if_name);
request.content.ifi_flags = IFF_UP;
request.content.ifi_change = 1;
if (send(tun_cfg->fd_netlink, &request, request.header.nlmsg_len, 0) == -1) {
perror("Failed to send netlink up");
return -1;
}
return 0;
}
int ifnow_tun_tuntap_connect(ifnow_tun_cfg_t *tun_cfg) {
struct ifreq ifr;
tun_cfg->fd_tuntap = open("/dev/net/tun", O_RDWR | O_CLOEXEC);
if(tun_cfg->fd_tuntap < 0) {
perror("Failed to open /dev/net/tun");
return -2;
}
memset(&ifr, 0, sizeof(struct ifreq));
ifr.ifr_flags = IFF_TAP | IFF_NO_PI;
if(tun_cfg->if_name != NULL) {
memcpy(ifr.ifr_name, tun_cfg->if_name, strlen(tun_cfg->if_name) + 1);
}
int err = ioctl(tun_cfg->fd_tuntap, TUNSETIFF, &ifr);
if(err < 0) {
perror("Failed to set interface name");
return -3;
}
}
int ifnow_tun_init(ifnow_tun_cfg_t *tun_cfg) {
printf("tun_cfg->addr_s: %s\n", tun_cfg->addr_s);
int err = inet_pton(AF_INET, tun_cfg->addr_s, &tun_cfg->addr);
if(err < 0) {
perror("Failed to parse IP address");
return -1;
}
ifnow_tun_tuntap_connect(tun_cfg);
ifnow_tun_netlink_connect(tun_cfg);
ifnow_tun_netlink_set_ip(tun_cfg);
// TODO send netlink route request
ifnow_tun_netlink_up(tun_cfg);
pthread_create(&tun_cfg->thread_id, NULL, &ifnow_tun_thread_handle, tun_cfg);
return 0;
}