Skip to content

Instantly share code, notes, and snippets.

@soypat
Created March 3, 2024 21:54
Show Gist options
  • Save soypat/e2323e3bc87dd36bec7e2ebf3a329f23 to your computer and use it in GitHub Desktop.
Save soypat/e2323e3bc87dd36bec7e2ebf3a329f23 to your computer and use it in GitHub Desktop.
Unix TAP example using /x/sys library
package main
import (
"errors"
"log"
"net/netip"
"os"
"unsafe"
"golang.org/x/sys/unix"
)
func main() {
// Open TAP interface
tap, err := OpenRawTap("tap0")
if err != nil {
log.Fatal("Error opening TAP interface:", err)
}
mtu, err := tap.GetMTU()
if err != nil {
log.Println("Error getting MTU:", err)
} else {
log.Println("TAP interface MTU:", mtu)
}
addr, err := tap.Addr()
if err != nil {
log.Println("Error getting address:", err)
} else {
log.Println("TAP interface address:", addr)
}
_, err = tap.Write([]byte("Hello, TAP interface!"))
if err != nil {
log.Fatal("Error writing to TAP interface:", err)
}
log.Println("Data sent over TAP interface successfully.")
}
// Before using a Tap one must create a Tap interface via linux command line.
//
// # Create the tap interface, name it tap0.
// sudo ip tuntap add tap0 mode tap
// # add a bridge to link TAP to the interface with internet access.
// sudo ip link add br0 type bridge
// # link the tap0 to the bridge.
// sudo ip link set tap0 master br0
// # set the tap0 up.
// sudo ip link set tap0 up
// # set the bridge to use the interface with internet access.
// # CAUTION: This command may disconnect the host from the internet briefly. To undo: sudo ip link set enp44s0 nomaster; sudo ip addr add <original_IP>/<subnet_mask> dev enp44s0; sudo ip link set enp44s0 up
// sudo ip link set enp44s0 master br0
// # set the bridge up.
// sudo ip link set br0 up
type Tap struct {
fd int
// ifr is used to configure the TAP interface.
ifr unix.Ifreq
}
func OpenRawTap(name string) (_ *Tap, err error) {
var tap Tap
ifr, err := unix.NewIfreq(name)
if err != nil {
return nil, err
}
tap.ifr = *ifr
tap.fd, err = unix.Open("/dev/net/tun", os.O_RDWR, 0)
if err != nil {
return nil, err
}
const tunFlags = unix.IFF_TAP | // TAP device: operate at layer 2 of the OSI model, so raw ethernet frames.
unix.IFF_NO_PI | // Prevents the kernel from including a protocol information (PI) header in the packet data.
unix.IFF_ONE_QUEUE | // Enables a single packet queue for the interface.
// unix.IFF_TUN_EXCL | // Request exclusive access to the interface.
unix.IFF_NOARP // Prevents the interface from participating in ARP exchanges.
// unix.IFF_PROMISC // Enable promiscuous mode. In promiscuous mode, the interface receives all packets on the network segment, regardless of the destination MAC address.
// IFF_BROADCAST is inherently set with IFF_TAP.
// unix.IFF_BROADCAST // When set, the interface can send and receive broadcast packets.
err = tap.tunsetFlags(tunFlags)
if err != nil {
tap.Close()
return nil, err
}
return &tap, nil
}
func (t *Tap) SetAddr(addr netip.Addr) error {
if !addr.Is4() {
return errors.New("invalid addr or not ipv4")
}
t.clearIfreq()
t.ifr.SetInet4Addr(addr.AsSlice())
return unix.IoctlIfreq(t.fd, unix.SIOCSIFADDR, &t.ifr)
}
func (t *Tap) Addr() (netip.Addr, error) {
t.clearIfreq()
err := unix.IoctlIfreq(t.fd, unix.SIOCGIFADDR, &t.ifr)
if err != nil {
return netip.Addr{}, err
}
addr, err := t.ifr.Inet4Addr()
if err != nil {
return netip.Addr{}, err
}
return netip.AddrFrom4([4]byte(addr)), nil
}
func (t *Tap) tunsetFlags(flags uint16) error {
t.clearIfreq()
t.ifr.SetUint16(flags)
return unix.IoctlIfreq(t.fd, unix.TUNSETIFF, &t.ifr)
}
func (t *Tap) GetMTU() (uint32, error) {
t.clearIfreq()
err := unix.IoctlIfreq(t.fd, unix.SIOCGIFMTU, &t.ifr)
if err != nil {
return 0, err
}
return t.ifr.Uint32(), nil
}
func (t *Tap) SetMTU(mtu uint32) error {
t.clearIfreq()
t.ifr.SetUint32(mtu)
return unix.IoctlIfreq(t.fd, unix.SIOCSIFMTU, &t.ifr)
}
func (t *Tap) Write(data []byte) (int, error) {
return unix.Write(t.fd, data)
}
func (t *Tap) Read(data []byte) (int, error) {
return unix.Read(t.fd, data)
}
func (t *Tap) Close() error {
return unix.Close(t.fd)
}
func (t *Tap) clearIfreq() {
// Skip over initial 16 bytes which are Name and must not change.
ifruStart := unsafe.Pointer(uintptr(unsafe.Pointer(&t.ifr)) + 16)
// Zero next 24 bytes which correspond to ioctl value.
ifru := (*[24]byte)(ifruStart)
for i := range ifru {
ifru[i] = 0
}
}
@soypat
Copy link
Author

soypat commented Mar 3, 2024

C version:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <net/if.h>
#include <linux/if_tun.h>

#define TAP_INTERFACE_NAME "tap0"
#define BUFFER_SIZE 1024

int main() {
    int tap_fd;
    struct ifreq ifr;
    char buffer[BUFFER_SIZE];

    // Open TAP interface
    if ((tap_fd = open("/dev/net/tun", O_RDWR)) < 0) {
        perror("Error opening TAP interface");
        exit(1);
    }

    memset(&ifr, 0, sizeof(ifr));
    ifr.ifr_flags = IFF_TAP | IFF_NO_PI;
    strncpy(ifr.ifr_name, TAP_INTERFACE_NAME, IFNAMSIZ);

    if (ioctl(tap_fd, TUNSETIFF, (void *)&ifr) < 0) {
        perror("Error configuring TAP interface");
        close(tap_fd);
        exit(1);
    }

    // Get interface MTU
    // if (ioctl(tap_fd, SIOCGIFMTU, &ifr) < 0) {
    //     perror("Error getting interface MTU");
    //     close(tap_fd);
    //     exit(1);
    // }

    // Print interface MTU
    printf("MTU of tap0 interface: %d\n", ifr.ifr_mtu);

    // Write arbitrary data to the TAP interface
    strcpy(buffer, "Hello, TAP interface!");
    if (write(tap_fd, buffer, strlen(buffer)) < 0) {
        perror("Error writing to TAP interface");
        close(tap_fd);
        exit(1);
    }

    printf("Data sent over TAP interface successfully.\n");

    // Close TAP interface
    close(tap_fd);

    return 0;
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment