Donnerstag, Juli 21, 2016

Spass mit tun/tap unter Linux

Heute hab ich ein bisschen Spass mit dem tun/tap-Treiber von Linux gehabt. 

Hier die einfache tun_alloc() abgeschrieben. Mein Code liest nun endlos (die meisten Beispiele benutzen select(), aber ich blockiere einfach) Pakete, vertauscht Source- und Destination-Address, und schreibt es wieder zurück auf das Interface. Das ist insgesamt nicht besonders sinnvoll, 
... aber ...
obwohl es eine Checksumme (crc32) gibt, die den Header sichern soll, kann man trotzdem, ohne die neu zu berechnen, einfach Source- und Dest-Adresse tauschen, weil die crc32 trotzdem korrekt bleibt. Eine merkwürdige Eigenheit dieses Checksummenalgorithmus.

Trotzdem nicht besonders sinnvoll, aber es gibt ein Protokoll, das genauso funktioniert: Ping! Und siehe da, ping geht auch.

Wichtig, was mir etwas Kopfzerbrechen gemacht hat: Das tuntap-Interface liefert einem einen File-Deskriptor, dem man mit read() und write() bearbeiten kann. Allerdings liefert read() immer ein Paket von Anfang an. Man kann nicht "testweise" in ein Paket reinlesen und dann im Header nachschauen, wie lang das Paket noch ist, und dann ein passendes zweites read() aufrufen, wie man das vielleicht bei einem tcp-Stream tun würde oder bei einer Datei.

So, jetzt mal Butter bei die Fische:

Mein Code:
tuntaptest.c
--------------------

// Tun/Tap Test

#include <sys/socket.h>    //Mo: or else doesn't compile. (errors in if.h)
#include <linux/if.h>
#include <linux/if_tun.h>

#include <sys/select.h>

#include <netinet/ip.h>
#include <unistd.h>

#include <stdio.h>
#include <string.h>

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <stdlib.h>

#include <arpa/inet.h>

/////////////////////////////////////////////////////////
// copied from https://www.kernel.org/doc/Documentation/networking/tuntap.txt

int tun_alloc(char *dev)
{
  struct ifreq ifr;
  int fd, err;
  
  if( (fd = open("/dev/net/tun", O_RDWR)) < 0 )
    perror("open(/dev/net/tun) < 0");
  
  memset(&ifr, 0, sizeof(ifr));
  
  /* Flags: IFF_TUN   - TUN device (no Ethernet headers) 
   *        IFF_TAP   - TAP device  
   *
   *        IFF_NO_PI - Do not provide packet information  
   */ 
  ifr.ifr_flags = IFF_TUN | IFF_NO_PI;   //Mo: ohne IFF_NO_PI hatte ich ein padding-Problem.
  //ifr.ifr_mtu   = 1500;      // Mo: Hinzugefuegt.
  
  if( *dev )
    strncpy(ifr.ifr_name, dev, IFNAMSIZ);
  
  if( (err = ioctl(fd, TUNSETIFF, (void *) &ifr)) < 0 ){
    close(fd);
    return err;
  }
  strcpy(dev, ifr.ifr_name);
  return fd;
}

//////////////////////////////////////////////////////////////////////

void printip(u_int32_t addr)
{
  printf("%i.%i.%i.%i", addr&0xff, (addr>>8)&0xff, (addr>>16)&0xff, (addr>>24)&0xff);
}

union {
  unsigned char ip_packet[65536];
  struct iphdr iphdr;
} packet;

int main(int argc, char **argv)
{
  char devname[IFNAMSIZ] = "mirror%d";
  int fd, n;
  u_int32_t swaptmp;

  fd = tun_alloc(devname);
  printf("devname: %s\nfd: %i\n", devname, fd);
  printf("sizeof iphdr = %lu\n", sizeof packet.iphdr);
  fflush(stdout);

  while (1) {
    // Genau ein read() pro Paket.
    // Wenn man nicht das ganze liest (bspw. erstmal nur den Header)
    // bekommt man den Rest nicht mehr.
    n = read(fd, &packet, sizeof packet);

    printf("Source IP: ");
    printip(packet.iphdr.saddr);
    printf(" Dest IP: ");
    printip(packet.iphdr.daddr);
    printf(" headerlength=%i", packet.iphdr.ihl);
    printf(" length: %i\n\n", n);
    
    ///// Wir swappen nun daddr und saddr und
    // schreiben das paket zurueck auf die
    // leitung.
    // WENN das ein ping war, muesste es korrekt
    // beantwortet sein. Wenn nicht, passieren halt
    // komische dinge, aber wen interessierts. ;)

    swaptmp = packet.iphdr.saddr;
    packet.iphdr.saddr = packet.iphdr.daddr;
    packet.iphdr.daddr = swaptmp;

    write(fd, &packet, sizeof packet);
  }
}





Dazu gibt es dann ein kleines Configscript:

configtun.sh
------------
#!/bin/bash

ip addr add 192.168.123.1/24 dev mirror0
ip link set up mirror0


Und nun führen wir das ganze aus:

$ gcc tuntaptest.c -o tuntaptest
$ sudo su # ich bin grad auf einem ubuntu...
$ modprobe tun
$ ./tuntaptest

# ... neue shell ...
$ sudo su
$ bash configtun.sh
$ ip addr
...
mirror0   Link encap:UNSPEC  Hardware Adresse 00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00 
          inet Adresse:192.168.123.1  P-z-P:192.168.123.1  Maske:255.255.255.0
          UP PUNKTZUPUNKT RUNNING NOARP MULTICAST  MTU:1500  Metrik:1
          RX-Pakete:6 Fehler:0 Verloren:0 Überläufe:0 Fenster:0
          TX-Pakete:6 Fehler:0 Verloren:0 Überläufe:0 Träger:0
          Kollisionen:0 Sendewarteschlangenlänge:500
          RX-Bytes:393216 (393.2 KB)  TX-Bytes:264 (264.0 B)


schonmal ganz cool...

und nun ...
# ping 192.168.123.42
PING 192.168.123.42 (192.168.123.42) 56(84) bytes of data.
64 bytes from 192.168.123.42: icmp_seq=1 ttl=64 time=1.08 ms
64 bytes from 192.168.123.42: icmp_seq=2 ttl=64 time=0.668 ms
64 bytes from 192.168.123.42: icmp_seq=3 ttl=64 time=0.335 ms
^C
--- 192.168.123.42 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2001ms
rtt min/avg/max/mdev = 0.335/0.696/1.085/0.306 ms


# ping 192.168.123.23
PING 192.168.123.23 (192.168.123.23) 56(84) bytes of data.
64 bytes from 192.168.123.23: icmp_seq=1 ttl=64 time=0.340 ms
64 bytes from 192.168.123.23: icmp_seq=2 ttl=64 time=0.433 ms
64 bytes from 192.168.123.23: icmp_seq=3 ttl=64 time=0.575 ms
^C
--- 192.168.123.23 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 1998ms
rtt min/avg/max/mdev = 0.340/0.449/0.575/0.098 ms


antwortet einfach jede Adresse in dem Subnetz :)