TCP/IP Stack ARP

General Plan

This section is influenced by this post of https://www.saminiir.com. I will try to explain what is happening in his posts step by step. It was easy for me to follow his general ideas but hard for me to follow them up with running code on macOS. In his post on ARP, Sami describes how to set up a TAP interface and bind to that interface using a C userspace application that implements a subset of the ARP protocol. The C application will contain just enough ARP to answer an arping call to that TAP interface. The C application will return the MAC-Address of the TAP interface as part of the ARP answer.

I will explain TUN/TAP, ARP, MAC-Addresses and user space applications in the rest of this post.

General Thoughts

During my tests on macOS, I made a bunch of experiences and I learned a lot of things. I try to sum up my findings in this section. Please take the points in this section with a grain of salt because I am a beginner with low level network programming and I basically do not understand most of what is happening with the technology at hand. Be warned!

  • Applications that open() tuntap interfaces should be run with administrator privileges (sudo)!
  • You should install wireshark! Wireshark allows you to select from a list of interfaces. After selecting one of the interfaces by double clicking, it will then monitor all traffic (ethernet frames) coming in and going out on that interface. You even get a hex dump of the frames with detailed explanation and a digest of the nested protocols inside the ethernet frame. As we will use virtual TAP interfaces, wireshark can bind to the interface and you can check what data is send to your application.
  • If you have a hexdump of a frame (e.g. copied from wireshark), you can past that hex dump into the Hex Packet Decoder (HPD) on gasmi net https://hpd.gasmi.net/. The Hex Packet Decoder will digest the dump and show you a nicely colored view of which bytes pertain to which of the OSI-Models protocols. You can use your mouse pointer to hover over bytes to get more detailed information. Within each protocol layer, HPD will show you what type of information the bytes carry. HPD can also validate if checksums and parity codes are correct or faulty and which values would be correct. This can help in debugging your check algorithms.
  • TAP interfaces can be written to and read from by a user space application. If there is no user space application reading from the device, the ethernet frames sent to the device are not forwareded to the internet and are just lost. It is not possible to send ethernet frames for a ICMP echo command to google and expect an answer without a user space application that receives the packages and does the communication tasks.
  • Sending a ethernet frame to a TAP interface requires the name of the interface (e.g. tap0). The tools wireshark, arping and nping all take the name of the interface to send data to as a command line parameter or from the GUI. A ethernet packet has to be sent to the ethernet interface. Only then can it be retrieved by the user space application that has the TAP interface opened. In contrast to TCP/IP where an application has to know a hostname or IP and a port to send data, for raw ethernet, the interface name and MAC address are needed.
  • It is not possible to call open() on a TAP interface in more than one process! The second and all following processes that try to open() will get a “Device is busy” error code.
  • If you want to send ethernet frames to a user space application that has a TAP interface opened, you can use raw sockets on macOS as described in this post: https://www.vankuik.nl/2012-02-09_Writing_ethernet_packets_on_OS_X_and_BSD. The example application outlined in the post is able to send data to the interface without a “Device is busy” error!
  • If you want to bind to a tcp/ip socket via hostname/IP and a portnumber using a tuntap interface, you have to???

TUN/TAP interfaces

Network traffic usually starts in an application in user space such as a web browser or a email client. That application uses sockets for example to talk to the operating system for sending packages into the network. The socket will send the packages to a network interface.

Usually network interfaces forward packages that are send to them from an application to a driver that drives a hardware network card.

TUN/TAP devices are simulated, virtual network interfaces that forward all packages to a software application in user space instead of to a driver.

The user space software, that gets the packages forwarded, can answer directly or send messages into the network using raw sockets or do something else.

The first of Sami’s ideas is to use a TAP device and let the user space application, that he describes and writes in his blog posts, answer ARP requests that are sent using the arping utility. In this case arping is the application in user space that starts the network traffic.

A TUN device is a OSI-Layer 3 element and works with IP-Packets. A TAP device is a OSI-Layer 2 element and works with ethernet frames.

TUN/TAP interfaces on macOS

Linux provides TUN/TAP devices without installing further software. Unlike Linux, macOS does not have TUN/TAP devices. The free software tuntaposx is a way to install TUN/TAP on macOS.

Download the application from http://tuntaposx.sourceforge.net/download.xhtml. Inside the .tar.gz file, there is .pkg file that starts an installer after a double click. You have to allow the installation of this application using the macOS security dialogs that popup during the installation procedure.

The TAP device will not be created by the installer! Instead, after the installation, you can create a TAP device manually from the command line or write an application that creates a TAP device in code.

This post describes the situation very well. tuntaposx provides /dev/tunX and /dev/tapX where X is a numerical value starting from 0 and range to a maximum parameter (default 16) that is set at compile time.

The devices are created automatically when they are used for the first time by an open() call from a application or by a command from the terminal such as:

exec 5<>/dev/tap0

The command above will create a device tap0.

At this point, the device has no IP address and a call to read() will currently fail in this state! To assign an IP and bring the device up, use ifconfig or write code in an application to achieve the same effect programatically.

Using ifconfig you can now configure this device and assign a IP:

sudo ifconfig tap0 10.1.2.3 up

The device tap0 is now ready to receive packages.

Another script that opens and closes a device is:

exec 4<>/dev/tap0  # opens device, creates interface tap0
ifconfig tap0 10.10.10.1 10.10.10.255
ifconfig tap0 up
hexdump -c <&4 # reads from device - a cheap etherdump
(...here, the tap0 interface is working, try ping 10.10.10.255 ...)
exec 4>&-  # closes device, destroys interface tap0

Working with ARP

The Address Resolution Protocol (ARP) is used to retrieve the Media-Access-Control-Address (MAC-Address) of a network interface given it’s IP address. (Reverse-ARP converts a MAC-Address into an IP-Address.)

A MAC-Address is required to send messages using the ethernet protocol. The ethernet protocol does not use IP-Addresses.

In order to transmit a ethernet frame between two interfaces, you have to write the source MAC-Address and destination MAC-Address into the ethernet frames you want to send. In order to determine those two MAC-Addresses, you can use arping. arping is an open source utility that implements ARP. Use arping on the remote IP-Address and use arping on the IP-Address that is assigned to the local network interface to retrieve both their MAC-Addresses. Now use those values as source and destination addresses in the ethernet frame.

How can ARP send a package to the destination interface if the sender does not know it’s MAC-Address and the IP-Address is not used in the link layer? ARP will broadcast a request by using the MAC-Address ff:ff:ff:ff:ff:ff which is received by all interfaces. Every interface will compare it’s own IP to the IP in the request. The particular interface on which the IPs match, will answer the request with its MAC-Address, not by broadcasting but by sending a direct message to the sender.

Write the User Space Application

Now it is finally time to show some code. The code is supposed to receive all packages send to the tap0 interface, filter out and answer ARP requests.

Because the interface can get any type of packages, the application will actively look for ARP packages and filter out all other packages (e.g. ipv4 packages).

TODO

  • Once it detects an incoming ARP package, the app has to answer.
  • Why does the application receive packages from the start? The answer can be found using wireshark and binding to the TAP interface. A service called airport keeps sending MDNS packages to the interface.
  • How can I make the app wait for input? A call to select() causes the application to wait until there is data ready for reading on the interface. select() can be called using a timeout object for timeouts or a NULL parameter which causes the application to wait for input indefinitely. If select() returns without timeouting (data is ready) you still have to consume the data via a call to read().

Excuse the messy code, I have to clean it up! If you are a beginner programmer, this code is not a good example right now, you should come back when I did clean up the code!

The code basically calls open(), on the tun/tap device tap0. In this state, it is not possible for the application to read from the device! To read, first a IP-Address has to be assigned to the device and it has to be brought up. The device has a MAC address as MAC addresses are burned or assigned by the hardware vendor when the hardware is produced. The device does not have an IP yet!

To assign a IP address, ioctl() calls are used. This is the reason why you have to run this app with admin rights (sudo) as only the admin is allowed to change the system state with ioctl(). This first part of the application is similar to the command

sudo ifconfig tap0 10.10.10.1 10.10.10.255 up

You could theoretically put a sleep() into the application after opening the tap device and enter the ifconfig command to assign it an ip and a netmask and bringing it up manually in another console. Then after the sleep() the application can continue to read from the device as the device now has a IP address. It is more convenient if the application assigns an IP programatically.

Instead of using ifconfig or setting a harcoded IP in code, the correct way would be to implement the DHCP protocol and ask the router to lease a free IP address in the network. Maybe we implement a subset of DHCP in a later post.

After the device has an IP, the app starts to read from the device. The part I currently fail to understand is, why the app immediately receives Ethernet frames! I do not know who sends those frames! It is not the arping utility because I do not call the arping utility! Maybe it is the operating system or some firewall.

The application will now read from the device. It will first check the device using a call to select(). select() allows to check if a device is in a desired state for a specific operation.

From the manpages:

select() and pselect() allow a program to monitor multiple file descriptors, waiting until one or more of the file descriptors become “ready” for some class of I/O operation (e.g., input possible).

So if select() returns a success() code, the application calls read() to read bytes from the network into a buffer in memory. The bytes are a ethernet frame which in turn contains an ARP packet in its payload section.

The ethernet header struct is packed, so we can just cast the byte buffer pointer to the ethernet header structure. An alternative to packing is to parse the byte buffer manually and copy values into the non-packed header field by field. That way the compiler can insert padding bytes to align the elements in the struct as he sees fit. I do actually not know what disadvantages packed structs have but I imagine that the performance can get pretty bad if the compiler cannot apply it’s magic. However, the manual work is not worth the compiler’s freedom for this example so packing is used.

Once a ethernet header pointer is available, the application can use the ethernet header’s ethertype field to check which protocol the ethernet frames payload belongs to. If the ethertype is a code that denotes ARP, the package is further investigated. If the ethertype is something else, such as a code for ipV4, the application skips the ethernet frame for now. We only want to answer an ARP request for now.

Also as a side node, the ethernet header contains the target MAC. The target MAC is set to ff:ff:ff:ff:ff:ff which you can see from the applications console output. The ff:ff:ff:ff:ff:ff MAC address is the broadcast address. You can see that the communication partner first broadcasts into the network to resolve an IP to a MAC address, so he can talk to the MAC address using ethernet (which will carry ipv4 as a payload). Our task in the future is to send the MAC address of the tun/tap device as an answer to the broadcaster using an answer solely targeted at the communcaction parter (no broadcast).

Just like with the ethernet header, the payload is cast to a packed struct of the ARP header. For now, the application just outputs the ARP header’s fields. The ARP header contains a lot of information that we can use to finally answer the ARP request.

TODO: answer the request

Something nobody ever tells you: You have to run this application with admin rights (sudo)! Otherwise the application will not be able to alter the tun/tap interface (calling ioctl(), assign an IP, bring it up into the up state, …)

Major parts of this code example are copied from https://github.com/LaKabane/libtuntap which is an excellent library that performs most of the setup code for you in a portable fashion! You should check it out!

#include <net/if.h>
#include <net/if_arp.h>
#include <net/if_utun.h>

#include <netinet/if_ether.h>
#include <netinet/in_systm.h>
#include <netinet/in.h>
#include <netinet/ip.h>

#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <signal.h>
#include <errno.h>
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <ifaddrs.h>
#include <errno.h>

#include <arpa/inet.h>

//#if defined(__APPLE__) && defined(HAVE_NET_UTUN_H)
#include <sys/kern_control.h>
#include <sys/sys_domain.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
//#endif

// from if_tun.h because it does not exist on mac. TUNSETIFF ifr flags
#define IFF_TUN 0x0001
#define IFF_TAP 0x0002
#define IFF_NO_PI 0x1000
#define IFF_ONE_QUEUE 0x2000
#define IFF_VNET_HDR 0x4000
#define IFF_TUN_EXCL 0x8000

static char *device_ptr;

#define BUFFER_LEN 2048

//#define ARP_ETHERNET_FRAME_TYPE 0x0806            // 1544, ARP, Address resolution protocol ethernet frame type
#define ETHERTYPE_ARP_ENDIANNESS 0x0608 // endianess changed

#define ETHERTYPE_IP_ENDIANNESS 0x0008 // IPv4 endianess changed

#define ARP_802DOT2_FRAME_TYPE 0x0004 // 1024 is in fact 0x0004 = 802.2 frames

struct eth_hdr
{
    uint8_t dmac[6];
    uint8_t smac[6];
    uint16_t ethertype;
    uint8_t payload[];
} __attribute__((packed));

struct arp_hdr
{
    uint16_t hwtype;
    uint16_t protype;
    unsigned char hwsize;
    unsigned char prosize;
    uint16_t opcode;
    unsigned char data[];
} __attribute__((packed));

#if defined Windows
typedef IN_ADDR t_tun_in_addr;
typedef IN6_ADDR t_tun_in6_addr;
#else // Unix
typedef struct in_addr t_tun_in_addr;
typedef struct in6_addr t_tun_in6_addr;
#endif

typedef int t_tun;

struct device
{
    t_tun tun_fd;
    int ctrl_sock;
    int flags; // ifr.ifr_flags on Unix
    unsigned char hwaddr[ETHER_ADDR_LEN];
    char if_name[IF_NAMESIZE + 1];
};

int tuntap_sys_set_ipv4(struct device *dev, t_tun_in_addr *s4, uint32_t bits)
{
    struct ifaliasreq ifa;
    struct ifreq ifr;
    struct sockaddr_in addr;
    struct sockaddr_in mask;

    memset(&ifa, '\0', sizeof ifa);
    strlcpy(ifa.ifra_name, dev->if_name, sizeof(ifa.ifra_name));

    printf("A) %s\n", ifa.ifra_name);

    memset(&ifr, '\0', sizeof ifr);
    strlcpy(ifr.ifr_name, dev->if_name, sizeof(ifr.ifr_name));

    printf("B) %s\n", ifr.ifr_name);

    // Delete previously assigned address
    ioctl(dev->ctrl_sock, SIOCDIFADDR, &ifr);

    // Fill-in the destination address and netmask,
    // but don't care of the broadcast address
    (void)memset(&addr, '\0', sizeof addr);
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = s4->s_addr;
    addr.sin_len = sizeof(addr);
    (void)memcpy(&ifa.ifra_addr, &addr, sizeof addr);

    (void)memset(&mask, '\0', sizeof mask);
    mask.sin_family = AF_INET;
    mask.sin_addr.s_addr = bits;
    mask.sin_len = sizeof(mask);
    (void)memcpy(&ifa.ifra_mask, &mask, sizeof ifa.ifra_mask);

    // Simpler than calling SIOCSIFADDR and/or SIOCSIFBRDADDR
    if (ioctl(dev->ctrl_sock, SIOCSIFADDR, &ifa) == -1)
    {
        //tuntap_log(TUNTAP_LOG_ERR, "Can't set IP/netmask");
        printf("Can't set IP/netmask\n");
        printf("ERRNO: (%d) %s\n", errno, strerror(errno));
        printf("If the error is 'operation not permitted' make sure you have to run this app with administrator rights (sudo)!\n");

        return -1;
    }

    return 0;
}

/*
http://tuntaposx.sourceforge.net/faq.xhtml

I'm a developer and I try to read() and write() to the character devices. However, 
all it gives me is an "Input/Output error". 
Why is that?

You can only read and write packets from and to the kernel while the corresponding network interface is up. 
The setup sequence is as follows (using tap0 as an example):

    open() the character device /dev/tap0.
    Configure the network interface tap0 and bring it up. 
    Typically, you'll also want to assign an IP address. 
    Here is an example using ifconfig (but you can also configure the device programatically using the usual IOCTLs):

    ifconfig tap0 10.1.2.3 up
    							
    Once the interface has been brought up, you can use the read() and write() functions on the character device's 
    file descriptor to receive or send a packet at a time.
    When you're done, close() the character device. This will remove the network interface from the system. 
     */

void print_hex_memory(void *mem, const int len)
{
    int i;
    unsigned char *p = (unsigned char *)mem;
    for (i = 0; i < len; i++)
    {

        // after 16 bytes, insert a newline
        if ((i % 16 == 0) && i > 0)
        {
            printf("\n");
        }

        printf("0x%02x ", p[i]);
    }
    printf("\n");
}

/*
 * Taken from Kernel Documentation/networking/tuntap.txt
 */
static int tun_alloc(char *dev)
{
    //struct ifreq ifr;
    struct ifaliasreq ifr;
    int fd, err;

    //if ((fd = open("/dev/net/tap", O_RDWR)) < 0)
    if ((fd = open("/dev/tap0", O_RDWR)) < 0)
    {
        perror("Cannot open TUN/TAP dev\n"
               "Make sure one exists with "
               "'$ mknod /dev/tap0 c 10 200'");

        return 1;
    }

    printf("device is opened %d!\n", fd);

    // before this timesout, type
    // sudo ifconfig tap0 10.10.10.1 10.10.10.255
    // sudo ifconfig tap0 up
    //sleep(10);

    memset(&ifr, 0, sizeof(ifr));

    printf("device is cleared!\n");

    // Flags: IFF_TUN   - TUN device (no Ethernet headers)
    //        IFF_TAP   - TAP device
    //
    //        IFF_NO_PI - Do not provide packet information
    //
    //ifr.ifr_flags = IFF_TAP | IFF_NO_PI;
    if (*dev)
    {
        strncpy(ifr.ifra_name, dev, IFNAMSIZ);
    }
    printf("device name '%s'\n", ifr.ifra_name);

    printf("Creating socket ...\n");
    int sock = -1;
    if ((sock = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
    {
        printf("ERRNO: (%d) %s\n", errno, strerror(errno));
        return -3;
    }
    printf("Creating socket done %d!\n", sock);

    printf("Setting ip ...\n");

    in_addr_t in_addr = inet_addr("10.10.10.1");

    struct sockaddr_in addr;
    memset(&addr, 0, sizeof(addr));
    addr.sin_addr.s_addr = in_addr;

    struct device libDevice;
    memset(&libDevice, 0, sizeof(struct device));
    strncpy(libDevice.if_name, "tap0\0", IFNAMSIZ);
    libDevice.ctrl_sock = sock;
    libDevice.flags &= IFF_LINK0;

    printf("tuntap_sys_set_ipv4 ...!\n");

    uint32_t bits;
    inet_pton(AF_INET, "10.10.10.255", &bits);

    if (tuntap_sys_set_ipv4(&libDevice, &(addr.sin_addr), bits) != 0)
    {
        printf("tuntap_sys_set_ipv4 failed!\n");
        return -1;
    }
    printf("tuntap_sys_set_ipv4 done.!\n");

    //sleep(10);

    printf("Setting ip done.\n");

    //
    // read and output mac address
    //

    struct ifaddrs *ifa = 0;
    if (getifaddrs(&ifa) != 0)
    {
        printf("Could not retrieve if addresses!\n");
        goto cleanup;
    }
    if (ifa == NULL)
    {
        printf("Can't get link-layer address\n");
    }

    struct ether_addr eth_addr;

    struct ifaddrs *pifa = 0;
    for (pifa = ifa; pifa != NULL; pifa = pifa->ifa_next)
    {
        // only output the addresses of the tun/tap interface
        if (strcmp(pifa->ifa_name, ifr.ifra_name) != 0)
        {
            continue;
        }

        printf("addresses found for ifc!\n");

        // The MAC address is from 10 to 15.
        //
        // And yes, I know, the buffer is supposed
        // to have a size of 14 bytes.
        //(void)memcpy(dev->hwaddr,
        //             pifa->ifa_addr->sa_data + 10,
        //            ETHER_ADDR_LEN);

        // initialize with zeroes
        (void)memset(&eth_addr.ether_addr_octet, 0, ETHER_ADDR_LEN);

        // copy data in
        (void)memcpy(&eth_addr.ether_addr_octet, pifa->ifa_addr->sa_data + 10, ETHER_ADDR_LEN);
        break;
    }

    printf("MAC: %s\n", ether_ntoa(&eth_addr));

    freeifaddrs(ifa);
    ifa = 0;

    /*
     * ioctl() ==  input/output control == system call
     * 
     * http://man7.org/linux/man-pages/man2/ioctl.2.html
     * 
     * Sends request codes to drivers. The reaction to the code is up to the driver implementation.
     * 
     * Parameters:
     * int fd -  file descriptor
     * unsigend long request - request code
     * ... - variadic parameter list
     */

    char buffer[BUFFER_LEN];

    printf("Trying to read ...\n");

    struct timeval timeout;
    timeout.tv_sec = 0;
    timeout.tv_usec = 10000;

    fd_set set;
    FD_ZERO(&set);    // clear the set
    FD_SET(fd, &set); // add our file descriptor to the set

    for (int i = 0; i < 10; i++)
    {
        memset(buffer, 0, BUFFER_LEN);

        printf("\n");
        printf("Selecting...\n");

        // select() and pselect() allow a program to monitor multiple file
        // descriptors, waiting until one or more of the file descriptors become
        // "ready" for some class of I/O operation (e.g., input possible).
        int rv = select(fd + 1, &set, NULL, NULL, &timeout);
        printf("rv: %d\n", rv);

        if (rv == -1)
        {
            // an error accured
            perror("select\n");
            printf("ERRNO: (%d) %s\n", errno, strerror(errno));
        }
        else if (rv == 0)
        {
            // a timeout occured
            printf("timeout\n");
            printf("ERRNO: (%d) %s\n", errno, strerror(errno));
        }
        else
        {

            printf("Something was read!\n");

            int read_result = read(fd, buffer, BUFFER_LEN);
            if (read_result != 0)
            {
                printf("ERRNO: (%d) %s\n", errno, strerror(errno));
            }
            else
            {
                printf("Something was read!\n");
            }

            print_hex_memory(buffer, BUFFER_LEN);

            struct eth_hdr *ethHeader = (struct eth_hdr *)buffer;

            // 6 byte destination MAC
            printf("Destination MAC: ");
            print_hex_memory(ethHeader->dmac, 6);

            // 6 byte source MAC:
            printf("Source MAC:      ");
            print_hex_memory(ethHeader->smac, 6);

            // 2 byte ethernet frame type
            // 1544 = 0x0806 = ARP
            if (ethHeader->ethertype == ETHERTYPE_ARP_ENDIANNESS)
            {
                printf("Ethertype: %d ARP\n", ethHeader->ethertype);

                struct arp_hdr
                {
                    uint16_t hwtype;
                    uint16_t protype;
                    unsigned char hwsize;
                    unsigned char prosize;
                    uint16_t opcode;
                    unsigned char data[];
                } __attribute__((packed));

                // payload is ARP
                struct arp_hdr *arpHeader = (struct arp_hdr *)ethHeader->payload;

                // https://de.wikipedia.org/wiki/Address_Resolution_Protocol

                // https://www.iana.org/assignments/arp-parameters/arp-parameters.xhtml
                // 256 - HW_EXP2
                printf("ARP hardware address type: %d \n", arpHeader->hwtype);
                printf("ARP protocol address type: %d ", arpHeader->protype);
                if (arpHeader->protype == ETHERTYPE_IP_ENDIANNESS)
                {
                    printf("ipv4");
                }
                else
                {
                    printf("unknown");
                }
                printf("\n");
                printf("ARP hardware address size: %d \n", arpHeader->hwsize);
                printf("ARP protocol address size: %d \n", arpHeader->prosize);
                printf("ARP opcode: %d \n", arpHeader->opcode);

                unsigned char *tempPtr = arpHeader->data;

                printf("Source MAC: ");
                print_hex_memory(tempPtr, 6);
                tempPtr += 6;

                printf("Source IP:  ");
                print_hex_memory(tempPtr, 4);
                tempPtr += 4;

                printf("Dest MAC:   ");
                print_hex_memory(tempPtr, 6);
                tempPtr += 6;

                printf("Dest IP:    ");
                print_hex_memory(tempPtr, 4);
            }
            else if (ethHeader->ethertype == ETHERTYPE_IP_ENDIANNESS)
            {
                printf("Ethertype: %d IPv4\n", ethHeader->ethertype);
            }
            else
            {
                printf("UNKNOWN Ethertype: %d ???\n", ethHeader->ethertype);
            }
        }

        printf("Selecting done.\n");
    }

cleanup:
    printf("Closeing device ...\n");
    close(fd);
    fd = 0;
    printf("Closeing device done.\n");

    return fd;
}

int main(int argc, char **argv)
{
    printf("You have to run this app with administrator rights (sudo)!\n");
    printf("You have to run this app with administrator rights (sudo)!\n");
    printf("You have to run this app with administrator rights (sudo)!\n");

    device_ptr = calloc(16, 1);
    strncpy(device_ptr, "tap0", strlen("tap0"));

    if (tun_alloc(device_ptr) != 0)
    {
        printf("There was an error allocating the tun/tap device!\n");
    }

    free(device_ptr);
    device_ptr = 0;

    return 0;
}

Installing arping on macOS

arping is an opensource application that allows to send Address Resolution Protocol (ARP) messages from the command line. It is not a standard utility and it is not installed on macOS by default. It can be installed using brew.

brew install arping
brew link arping

On my machine arping was not available after linking. It was installed to /usr/local/Cellar/arping/2.19/sbin/ and it can be used from there.

Send the arping to the tap0 interface using this command:

arping -I tap0 10.0.0.4
sudo /usr/local/Cellar/arping/2.19/sbin/arping -I tap0 10.10.10.1

An nping command that also outputs the frame in hex is:

sudo ./nping -vvv --dest-mac ff:ff:ff:ff:ff:ff --ether-type 0x0800 -e tap0 --send-eth --data ffffffff 10.10.10.1

 

 

 

Leave a Reply