Copy Link
Add to Bookmark
Report

TUN/TAP - Virtual Networks and their practical applications

Number 0x02: 15/02/2007

eZine's profile picture
Published in 
the bug magazine
 · 29 Dec 2022

[ --- The Bug! Magazine 

_____ _ ___ _
/__ \ |__ ___ / __\_ _ __ _ / \
/ /\/ '_ \ / _ \ /__\// | | |/ _` |/ /
/ / | | | | __/ / \/ \ |_| | (_| /\_/
\/ |_| |_|\___| \_____/\__,_|\__, \/
|___/

[ M . A . G . A . Z . I . N . E ]


[ Numero 0x02 <---> Edicao 0x01 <---> Artigo 0x04 ]



.> 14 de Fevereiro de 2007,
.> The Bug! Magazine < staff [at] thebugmagazine [dot] org >




+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
TUN/TAP - Redes virtuais e suas aplicacoes praticas
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+



.> 30 de Janeiro de 2007,
.> Paulo Matias a.k.a thotypous < matias [at] ursa.ifsc.usp.br >

"Seek, and ye shall find; knock, and it shall be opened to you."

-- Jesus Christ

Index

  1. Introduction
  2. Virtual network interfaces
    • 2.1. Types of virtual network interface
    • 2.2. Linux TUN/TAP device
    • 2.3. BSD TUN/TAP devices
    • 2.4. Using TUN/TAP in C language
    • 2.5. Using TUN/TAP in Python language

  3. MiniVPN: Implementation of a simple VPN
    • 3.1. Introduction
    • 3.2. Protocol Specification
    • 3.3. Implementation
    • 3.4. Configuration and use
    • 3.5. Tests and results

  4. 4. PPPoEforge: Forging PPPoE Packets
    • 4.1. Introduction
    • 4.2. Aspects of the PPPoE Protocol
    • 4.3. Implementation
    • 4.4. Taking the connection from another machine
    • 4.5. Disguising access to the internal network
    • 4.6. Use on Wi-Fi networks

  5. Conclusion
  6. References

1. Introduction

TUN/TAP is a universal driver for creating virtual networks, available for Linux, BSD, Solaris, MacOS X and Windows. This article is focused on Linux systems, but will point out the differences in the BSDs.

Virtual network interfaces are like virtual NICs. They appear to the rest of the operating system just as if they were regular hardware. However, they do not correspond to any kind of hardware. They are purely software controlled, and behave the way we want them to.

Traditionally, TUN/TAP is widely used in VPN applications such as OpenVPN and emulator applications such as qemu. We deal here with one of those traditional applications. A small point-to-point VPN application in Python language is presented.

A new type of application involving TUN/TAP is also discussed. When we want to do a security test on some network protocol, we often need to modify packets sent by some program before sending them to the external network.

A tool that uses TUN/TAP can do this very easily and very freely. Just configure the virtual interface to receive the desired packets, and they will be passed to our tool, where they can be processed and modified, and possibly redirected to another network interface.

A tool that uses this idea to forge PPPoE sessions is presented. The PPPoE protocol, although insecure to use, is found in many Internet providers via Wi-Fi as a way to authenticate clients.

2. Virtual Network Interfaces
2.1. Types of Virtual Network Interface

There are two basic types of virtual network interface. They are called TUN and TAP, hence the name "TUN/TAP" for the driver.

TUN interfaces are point-to-point, i.e. the network in which one of these interfaces participates resembles a PPP connection or a network made with a crossover cable, as only two members participate, one on each side of the interface. When using this type of interface, your program will work directly with IP packets. The virtual network interface is configured via ifconfig with the IP address of our machine and the remote machine.

  +---------------------+ 
| Sistema Operacional |
+---------------------+
|
+---------------+
| Interface TUN |
| 192.168.0.1 |
| pointopoint |
| 192.168.0.2 |
+---------------+
| +---------------------------+
+------------+ | Pacote IP de: 192.168.0.1 |
| | ---> | para: 192.168.0.2 |
| Aplicativo | +---------------------------+
| | <--- | Pacote IP de: 192.168.0.2 |
+------------+ | para: 192.168.0.1 |
+---------------------------+

TAP interfaces act like a network card plugged into a hub or switch, because they can participate in a network with multiple members, each identified by a MAC address. Therefore, this type of interface includes Ethernet encapsulation, under which packets for protocols such as IP and ARP will be carried. The virtual network interface is configured via ifconfig with a MAC address and if desired an IP address.

  +---------------------+ 
| Sistema Operacional |
+---------------------+
|
+-------------------+
| Interface TAP |
| 192.168.0.1 |
| hw ether |
| 4A:68:CD:E0:1D:4B |
+-------------------+
| +--------------------------------+
+------------+ | Ethernet de: 4A:68:CD:E0:1D:4B |
| | ---> | para: XX:XX:XX:XX:XX:XX |
| Aplicativo | +--------------------------------+
| | <--- | Ethernet de: YY:YY:YY:YY:YY:YY |
+------------+ | para: 4A:68:CD:E0:1D:4B |
+--------------------------------+


2.2. Linux TUN/TAP device

The TUN/TAP driver has been officially included in Linux since the 2.3.x series. It can be accessed via the /dev/net/tun device.

First the device is opened as a normal file for reading and writing and a file descriptor is obtained. Then you must send an ioctl to this descriptor in order to configure the device.

The ioctl code is TUNSETIFF (0x400454ca) and the argument to be passed to it is a pointer to a structure of the struct ifreq type, as in the figure below.

             struct ifreq 
+------------------+-------------+
| nome%d | flags |
+------------------+-------------+
<--- 16 bytes ---> <- 2 bytes ->
(C string) (short uint)

The beginning of the structure corresponds to the desired name for the network interface. At the end of the name, we put a "%d", which will be replaced by the next number available on the system. For example, if we put the name as "eth%d", and the network interfaces eth0 and eth1 already exist in the system, our virtual interface will acquire the name eth2. This part of the structure corresponds to a 16 byte string in standard C format, i.e., an ASCII string terminated by a null character.

This is followed by a 2 byte integer with the device initialization flags. This integer should result from the sum of one or more of the following codes:

  +-------------------+--------------------+ 
| Tipo de interface | Outros |
+-------------------+--------------------+
| IFF_TUN (0x0001) | |
| ou | IFF_NO_PI (0x1000) |
| IFF_TAP (0x0002) | |
+-------------------+--------------------+

The options IFF_TUN and IFF_TAP allow you to choose the type of virtual interface you want, as explained earlier. The IFF_NO_PI option will be explained further below.

If successful, the Linux TUN/TAP driver, after receiving this ioctl, will modify the name field of the structure passed to it, replacing it with the exact name of the network interface created, that is, replacing the "%d" with the numbering used.

Once this is done, the network interface will be available to the operating system. The network configuration can be done with the ifconfig command as usual.

  Interface do tipo TUN: 
ifconfig <interface> <ip-local> pointopoint <ip-remoto>

Interface do tipo TAP:
ifconfig <interface> hw ether <MAC> <ip-local>

The program will communicate with the virtual network interface through its file descriptor, reading and writing to it. However, there are some very important features that need to be observed.

To read a packet that has been sent to the network interface, the read() system call will be used. Each of these calls will always read a different packet. That is, you must read an entire packet at once. If you don't read the entire packet, the rest of the packet will be dropped automatically by the operating system.

Similarly, each write() system call will correspond to a different packet which will be received by the virtual network interface. That is, you cannot split a single packet into multiple write() calls, otherwise you are actually sending more than one packet.

The format of each data packet read or written to the file descriptor will depend on whether or not you used the IFF_NO_PI option in the device configuration ioctl. By default, that is, if you have NOT used this option, the format will be as follows:

  +------------+-------------+----------------------------------+ 
| Flags | Protocolo | Frame IP (TUN) ou Ethernet (TAP) |
+------------+-------------+----------------------------------+
<- 2 bytes -> <- 2 bytes -> <---------- [MTU] bytes --------->
(short uint) (short uint)

Currently, the "Flags" field is not used by the driver and will always be filled with zeros. The "Protocol" field indicates the packet protocol in the same format as used for Ethernet frames. Then follows the content of the packet, which will be an IP or IPv6 protocol frame if we are working with a TUN virtual interface, or an Ethernet frame if we are working with TAP.

The maximum frame size is given by the MTU setting of the virtual network interface. For TAP interfaces, the limit is 1500 bytes, which is the one imposed by the Ethernet standard. For TUN interfaces, the default MTU value is the same 1500 bytes, but can, however, be modified via ifconfig.

Note that the only secret to reading packets that have been sent to the virtual network interface is to pass the read() system call a buffer that is four bytes larger than the MTU.

Now you may be thinking "Why should I bother with these Flags and Protocol fields if Flags is always zero and I already know the Protocol? Actually, for most applications, we would not need to read these two fields, because when using TAP interfaces, we usually work with a single protocol (usually IP) and, when using TUN interfaces, the protocol can already be obtained inside the Ethernet frame.

This is why there is the IFF_NO_PI option, which took us so long to explain. It changes the format that should be used when reading or writing data packets to the TUN/TAP file descriptor, so that it is no longer necessary to work with these two fields. Only the pure frame of the network protocol used should be read or written, and the buffer size can only match the MTU value, without having to hold four additional bytes.

  +----------------------------------+ 
| Frame IP (TUN) ou Ethernet (TAP) |
+----------------------------------+
<---------- [MTU] bytes --------->

Information is also available in the official TUN/TAP documentation for Linux [1].

2.3. BSD TUN/TAP Devices

The way you boot a TUN/TAP device on a BSD is different from the way you do it on Linux. To begin with, there is no single device:

  root@netbsd [~]# ls -l /dev/tun[0-9]* /dev/tap[0-9]* /dev/tap 
crw------- 1 root wheel 169, 0x000fffff Jan 24 09:10 /dev/tap
crw------- 1 root wheel 169, 0 Jan 24 09:10 /dev/tap0
crw------- 1 root wheel 169, 1 Jan 24 09:10 /dev/tap1
crw------- 1 root wheel 169, 2 Jan 24 09:10 /dev/tap2
crw------- 1 root wheel 169, 3 Jan 24 09:10 /dev/tap3
crw------- 1 root wheel 40, 0 Jan 24 09:10 /dev/tun0
crw------- 1 root wheel 40, 1 Jan 24 09:10 /dev/tun1
crw------- 1 root wheel 40, 2 Jan 24 09:10 /dev/tun2
crw------- 1 root wheel 40, 3 Jan 24 09:10 /dev/tun3

Note that the device name itself indicates whether a TUN or TAP virtual network interface will be used. Another important aspect is that you can not choose any name for the network interface as in Linux. The name will always be the same as the device name. So, if we use the device /dev/tun0, the network interface will be named "tun0".

To use a TUN interface, just open one of the appropriate devices and the driver will take care of creating the virtual network interface if it does not exist. Note that this interface will NOT be automatically destroyed if the file descriptor is closed. That is, the virtual network interface will continue to appear to ifconfig and other system applications even after the program that created it has been closed.

Note that the procedure is very simple. It is not necessary to call a boot ioctl as in Linux. Just open the device and use it.

Another important difference with the TUN-like interfaces on BSD systems is that if you try to read packets from the file descriptor while the virtual network interface does not yet have an IP address assigned, the call will return an error instead of waiting for the packet to arrive, even if the descriptor is in blocking mode (the default). If you wish to wait for the next packet even though there is no IP address assigned to the TUN virtual interface, we recommend using the select() system call.

To use TAP type interfaces, there are two alternatives. The first is to create the desired interface in advance using the ifconfig command or the ioctl SIOCIFCREATE, and then open the corresponding device. For example, use the command "ifconfig tap0 create" and open /dev/tap0.

The second alternative is to directly open the /dev/tap device. This way, the driver takes care of automatically creating a TAP interface for the next available numbering, which will be automatically destroyed when closing the corresponding file descriptor. Note that in this case we do not know in advance what the network interface will be called. It is possible to get it by means of the ioctl TAPGIFNAME, which takes as argument a pointer to a structure of the struct ifreq type, similar to the procedure already explained for Linux.

Note that on NetBSD and OpenBSD by default it is not possible to change the MAC address of a network interface with the ifconfig command. However, if we wish to change the default MAC address of a TAP interface, chosen by the driver, and put any other we wish, it is possible to use a sysctl, for example:

 sysctl -w net.link.tap.tap0=f2:0b:a4:26:35:07

After initialization, the device behaves as it would behave in Linux when using the IFF_NO_PI option. That is, the default behavior in the BSDs is to use pure IP (for TUN) or Ethernet (for TAP) frames. Just read or write these frames to the file descriptor to control the virtual network interface, as done in Linux.

  +----------------------------------+ 
| Frame IP (TUN) ou Ethernet (TAP) |
+----------------------------------+
<---------- [MTU] bytes --------->

More detailed information can be found in the official TUN/TAP documentation for NetBSD [2] and FreeBSD [3]. This documentation is very good and well explained, unlike the official Linux documentation [1].

2.4. Using TUN/TAP in C language

Now that we have explained how the TUN/TAP device behaves, we will work on the implementation of a C program that uses it. The implementation for BSD systems is trivial, because in most cases we just need to open the device and use it, so we will focus on the Linux implementation.

First, let's see which headers we need to include in our C code.

  /* Para printf(). */ 
#include <stdio.h>

/* Para a chamada de sistema open(). */
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

/* Para os defines relativos ao TUN/TAP. */
#include <linux/if_tun.h>

/* Para a estrutura struct ifreq. */
#include <net/if.h>

/* Para a chamada de sistema ioctl(). */
#include <sys/ioctl.h>

We will now build a function that initializes the TUN/TAP device and returns the file descriptor on success, or -1 on failure.

  /* Argumentos: 
* - nome: Nome da interface a ser criada, opcionalmente
* contendo um "%d". Deve apontar para um local
* de memoria capaz de armazenar IFNAMSIZ
* caracteres.
* - tipo: IFF_TUN ou IFF_TAP.
*/
int abrir_tun_tap(char *nome, int tipo) {

Now let's check the variables we need for the initialization of the TUN/TAP virtual network interface.

  /* Descritor de arquivo para comunicacao com a interface 
* de rede virtual.
*/
int fd;

/* Estrutura struct ifreq para a ioctl de inicializacao
* do TUN/TAP.
*/
struct ifreq ifr;

As we saw earlier, the whole process starts by opening the Linux TUN/TAP device for reading and writing.

  if((fd = open("/dev/net/tun", O_RDWR)) < 0) 
return -1;

Next we need to clean up the ifr structure and fill it with the device initialization data. Notice that we use the IFF_NO_PI option by default, as this makes it easier to process and assemble the packets exchanged with the virtual interface, as explained earlier.

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

Finally we send the ioctl to the file descriptor, causing the Linux TUN/TAP driver to receive the desired settings.

  if(ioctl(fd, TUNSETIFF, (void *)&ifr) < 0) { 
close(fd);
return -1;
}

Now the device is initialized and we can read and write to it. The function can return the file descriptor to be used for this purpose. It is also useful that we copy the value chosen by the driver for the virtual network interface back into the area pointed to by "name", so that whoever uses the function can know it.

  strncpy(nome, ifr.ifr_name, IFNAMSIZ); 
return fd;
}

An example of using the open_tun_tap function we have just written is shown below.

  int main() { 
char nome[IFNAMSIZ] = "teste%d";
int fd = abrir_tun_tap(nome, IFF_TUN);

unsigned char buffer[1500];
int pacotes = 3;

if(fd < 0) {
printf("Erro ao iniciar o TUN/TAP!\n");
return 1;
}

printf("Iniciado na interface '%s'.\n", nome);

while(pacotes--) {
read(fd, buffer, sizeof(buffer));
printf("Pacote recebido!\n");
}

close(fd);
return 0;
}

As we can see, the above code is quite simple. It receives three packets on the virtual network interface and then closes it. Finally, let's test it:

  # gcc teste.c -o teste 
# ./teste &
[1] 13477
Iniciado na interface 'teste0'.
# ifconfig teste0 192.168.10.1 pointopoint 192.168.10.2
# ping -c 3 192.168.10.2
PING 192.168.10.2 (192.168.10.2) 56(84) bytes of data.
Pacote recebido!
Pacote recebido!
Pacote recebido!
--- 192.168.10.2 ping statistics ---
3 packets transmitted, 0 received, 100% packet loss, time 1999ms
[1]+ Done ./teste

It is important to realize that the read() portion of our example code would not work as expected on BSD systems, unless the virtual network interface was previously created and assigned an IP address. As explained earlier, this is because the read() call on BSD systems fails if an interface of type TUN does not have an IP address assigned at the time of the call. Thus the program would terminate before the three pings were sent.

2.4. Using TUN/TAP in Python language

The procedure for using TUN/TAP in Python is quite analogous to the C language. The main differences are in the way the structures exchanged via ioctl are assembled and read. To avoid lengthening the article with repetitions, I will be brief with a small commented example.

   import os, struct, fcntl 
# Valores das constantes utilizadas.
TUNSETIFF = 0x400454ca
IFF_TUN = 0x0001
IFF_NO_PI = 0x1000
# Primeiramente, abrimos o dispositivo.
fd = os.open('/dev/net/tun', os.O_RDWR)
# Configuracoes desejadas para o dispositivo.
nome = 'teste%d'
flags = IFF_TUN | IFF_NO_PI
# Montamos a estrutura struct ifreq.
estrutura = struct.pack('16sH', nome, flags)
# Chamamos a ioctl.
estrutura = fcntl.ioctl(fd, TUNSETIFF, estrutura)
# Retiramos o nome da estrutura resultante.
nome = estrutura[:16].strip('\x00')
# Recebemos tres pacotes.
print "Iniciado na interface '%s'." % nome
for i in range(3):
buffer = os.read(fd, 1500)
print 'Pacote recebido!'
# Fechamos o descritor de arquivo.
os.close(fd)

The above code performs exactly the same procedure as the C code presented earlier. It starts a TUN virtual network interface, waits for three packets to be received, and exits.

At the end of this article, you will find a compressed file coded in uuencoded format. Inside, there is a tun.py module, which is an implementation of access to the TUN/TAP interface similar to the above code, but object oriented. It is documented in the code itself and will be used from now on in our examples.

As an illustration, we end up repeating the above example reimplemented with the help of our tun module for Python.

  import tun 
iface = tun.TUN(name='teste')
print "Iniciado na interface '%s'." % iface.name
for i in range(3):
buffer = iface.read()
print 'Pacote recebido!'


3. MiniVPN: Implementing a simple VPN
3.1. Introduction

A VPN, or virtual private network, is a secure network within an insecure network. Its purpose is to ensure that data is not tampered with, and to ensure the authenticity of exchanged packets, even if there is interception on the insecure network.

In other words, two things are obviously essential in a VPN: a good encryption algorithm and a good digital signature algorithm. In the MiniVPN we will use OpenSSL for both tasks. By default we will use the 256-bit AES (Rijndael) for encryption and an HMAC-SHA256 for digital authentication. For simplicity, MiniVPN will only support symmetric encryption, with pre-shared keys.

There is also the need to protect the network against an attacker resending packets that have been intercepted, a very simple attack to perform [4] if the transport medium is a Wi-Fi network. The protection is done by adding a timestamp to the encrypted packet. The other side discards packets that do not have sequential timestamps. Moreover, packets are also rejected if the clocks of the two machines are too far out of sync.

For maximum performance even for applications like VoIP, packets are transported using the UDP protocol, because there is no need to guarantee their arrival. The loss of UDP packets is analogous to the loss that could occur due to defects in the physical transmission medium in a real network. If TCP packets are transported through the tunnel, the TCP protocol itself takes care of fixing these transmission failures.

The network is simulated for the operating system by means of a TUN virtual network interface. This allows the realization of point-to-point tunnels, which are quite simple to implement. The MTU value of the TUN interface is automatically optimized so that each packet exchanged with the TUN can fit into only one packet exchanged with the external network.

3.2. Protocol Specification

The protocol used by MiniVPN is quite simple. No permanent connection is established between the two ends, and no negotiation takes place. Each packet to be transmitted through the tunnel is authenticated and encrypted separately and then sent. This makes the packets extremely suitable for transmission over the UDP protocol.

Below is the format of a packet exchanged over the insecure network when using the algorithms that come by default in the example configuration.

  +------------+------------------------+----------------------+ 
| Tamanho | Vetor de Inicializacao | Dados Criptografados |
+------------+------------------------+----------------------+
<- 2 bytes -> <------ 16 bytes ------> <-- ~tamanho~ bytes -->
(short uint)

The Size field indicates how large the encrypted data will be after decryption, represented by a 2-byte unsigned integer in big-endian format.

The Initialization Vector (IV) field, which for the AES (Rijndael) encryption algorithm occupies 16 bytes, is a value used by the algorithm to initialize its internal calculations. A good protocol should always vary this value before encrypting the data, because this prevents the attacker from using certain cryptanalysis techniques. MiniVPN always creates the initialization vectors with the help of the OpenSSL random number generator.

If we gave the algorithm always the same initialization vector, two packets transmitted with the same content would be identical after encryption. By intercepting the communication and observing this, an attacker could discover valuable information about the traffic passing through the encrypted tunnel. [5]

After these two fields follows a set of data encrypted using the provided initialization vector and the configured secret key. The amount of data in this set is equal to the value in the Size field rounded down to the smallest number of encryption blocks that can contain it. The AES algorithm uses 16-byte encryption blocks.

After decrypting this set, we will have a data structure with the format shown below:

  +-------------------+-------------+-----------------------+ 
| Autenticacao HMAC | Timestamp | Frame de protocolo IP |
+-------------------+-------------+-----------------------+
<---- 32 bytes ----> <- 4 bytes -> <- [tamanho-36] bytes ->
(long uint)

The first field is a digital signature made with the HMAC algorithm. HMAC uses a secret key and an auxiliary digest algorithm to generate an authentication code. The authentication will be computed for the rest of the decrypted data set. The default digest helper algorithm is SHA256, which generates 32-byte authentication codes. Obviously we must discard packets whose HMAC does not match.

This is followed by a standard POSIX timestamp indicating the date and time at which the packet was generated by the MiniVPN, represented by a 4-byte integer in big-endian format. To prevent an attacker from resending previously intercepted packets, we should refuse to receive packets whose time is too far out of sync with our clock or that do not have a sequential time.

Finally, we have the IP protocol frame, exactly as received by the TUN virtual network interface. The other end of the tunnel will just need to write it to its TUN interface after performing the checks described above.

3.3. Implementation

The MiniVPN implementation was done in Python language. To interface with the OpenSSL library, we used M2Crypto, an easy to find and install extension for Python. The virtual network interface was built with the help of our tun module for Python, already presented.

MiniVPN loads a file with the VPN settings, also written in Python, passed on the command line. Then, after the virtual network interface is opened, an optimized value for its MTU is calculated from the configuration parameters, which is then automatically assigned by means of an ioctl call.

After that we use Python's standard socket library to listen on a UDP port. Packets received on this port are decoded according to the previous specification and written to the file descriptor of the TUN interface. Similarly, packets received on the virtual network interface are encrypted and sent via UDP to the remote machine.

  +--------+   +-------------------+   +---------------+ 
| Socket |-->| Descriptografador |-->| Checador HMAC |
| UDP | +-------------------+ +---------------+
+--------+ |
^ v
| +--------------------+
+----------------+ | Checador Timestamp |
| Criptografador | +--------------------+
+----------------+ |
^ v
| +--------------+ +-------------------+
|_________| Autenticador |<--| Interface Virtual |
| HMAC | | (vpn0) |
+--------------+ +-------------------+

For more details, please review the MiniVPN code that accompanies this article, noting the internal documentation and comments therein.

3.4. Configuration and use

The only prerequisites for using MiniVPN are having the TUN/TAP driver compiled into the Kernel, an installation of Python (MiniVPN has been successfully tested on versions 2.3 and 2.4) and the OpenSSL and M2Crypto libraries.

After installing the necessary software, you need to create a configuration file for the VPN. Use as base the example vpn-example.py that comes with MiniVPN.

The most important parts of the configuration file are the address of the remote computer with which the tunnel will be established and the two secret keys, the encryption and authentication keys.

To automatically generate two random 256 bit keys, used by both algorithms defined in the default setup, call Python from the command line:

  $ python 
>>> from M2Crypto import m2
>>> from base64 import encodestring
>>> encodestring(m2.rand_bytes(32))
'tL4sX8uqzXzKE4tCwU7SOoksPIYMaCQ5E29HzcOiDS4=\n'
>>> encodestring(m2.rand_bytes(32))
'rZVxL758wn/ReQuoNKkBgo/CLOUmTQj/wQLCqQq4V4w=\n'
>>>

These keys can be used as secret keys when setting up our VPN. After configuring the first machine, just copy the configuration file to the second machine by a secure means, for example via scp, and change the address configuration of the remote machine.

Now we can start tunneling between the two machines. For example, on the first machine we can execute the following commands:

  # python minivpn.py vpn-config-1.py >> vpn.log& 
[1] 19318
# ifconfig vpn0 192.168.10.1 pointopoint 192.168.10.2

And on the second machine:

  # python minivpn.py vpn-config-2.py >> vpn.log& 
[1] 18345
# ifconfig vpn0 192.168.10.2 pointopoint 192.168.10.1

Next, we test with pings. If the pings don't return, examine the log. Remember that the clocks of both machines have to be well synchronized. This can be done by means of NTP (ntpdate utility).

3.5. Testing and Results

For the test, we set up a VPN connecting two computers located in different cities via the Internet. Both were connected via ADSL with 128 kbps upload. Here is a table with the characteristics of the two machines used.

ProcessorRAMLinux DistributionIn use as
Celeron 1.1GHz128 MBGentoo LinuxDesktop + Servidor
Athlon XP 2.0+1 GBDebian EtchDesktop (KDE)

In the first test, we compared the ping from outside the VPN with the one from inside the VPN. The results are shown below.

  Por fora da VPN: 
--- 201.3.85.x ping statistics ---
300 packets transmitted, 299 received, 0% packet loss, time 300493ms
rtt min/avg/max/mdev = 53.555/58.007/96.911/5.905 ms

Por dentro da VPN:
--- 192.168.10.2 ping statistics ---
300 packets transmitted, 298 received, 0% packet loss, time 300629ms
rtt min/avg/max/mdev = 68.149/75.071/202.440/10.166 ms

Pico de CPU e memoria do Celeron:
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
4742 root 15 0 7908 5428 2488 S 0.3 4.9 0:00.52 python

We can see that the average response time of the pings has increased by about 17 milliseconds. An increase in jitter in this response time can also be observed. These factors hinder the use of real-time applications, such as VoIP. However, we can consider the increases insignificant, because it is possible to use VoIP even in satellite links. For comparison, we present below the statistics of pings directed to a Hispamar-Telemar satellite link.

  --- 201.59.21.x ping statistics --- 
100 packets transmitted, 100 received, 0% packet loss, time 99031ms
rtt min/avg/max/mdev = 624.458/815.531/1122.952/119.857 ms, pipe 2

The second test consisted of directing a relatively large amount of packets to the VPN using the -f ping option. At the same time, the CPU and memory usage of the Celeron was measured using the top utility.

  --- 192.168.10.2 ping statistics --- 
1035 packets transmitted, 1006 received, 2% packet loss, time 16780ms
rtt min/avg/max/mdev = 70.016/219.290/422.484/89.292 ms, pipe 25,
ipg/ewma 16.229/300.645 ms

PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
4742 root 15 0 7908 5080 2140 S 5.3 4.6 0:06.46 python

In the third test a file was transferred over the VPN via HTTP from the Celeron to the Athlon. The CPU and memory usage of the Celeron server and the average transfer speed were measured.

  Connecting to 192.168.10.1:80... connected. 
HTTP request sent, awaiting response... 200 OK
Length: 7,149,096 (6.8M) [application/pdf]
14:18:12 (13.21 KB/s) - `Arquivo.pdf' saved [7149096/7149096]

PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
5393 root 15 0 7912 4952 2212 S 2.3 4.5 0:02.27 python

We also performed tests with the MiniVPN on local networks. All Internet traffic from one computer was passed through a VPN connected to the same Celeron as in the previous tests. No significant performance losses were perceptible to the user, even with the use of applications that use large amounts of network resources, such as mldonkey.

4. PPPoEforge: Forging PPPoE Packages
4.1. Introduction

We will present here a practical example of how TUN/TAP is useful for the development of protocol security testing tools.

The PPPoE protocol is widely used by ADSL Internet providers and also by some Wi-Fi providers. We will analyze some aspects of this protocol and show how authentication can be easily forged when the network is interceptible.

We will then present a Python tool that can perform this task, explain how it can be used and show some practical examples.

4.2. Aspects of the PPPoE protocol

The PPPoE protocol performs point-to-point communication. We have two points in the network that connect, for example, the provider and the provider's user. We will only deal here with how the communication takes place after a connection has already been established between the two ends.

Basically, there are two types of packets that can travel over the network. The first type is IP packets, which carry out the normal communication of our applications with the network. The second type are LCP packets, which act as protocol control packets. An example of a packet passed through a PPPoE connection dissected by WireShark (formerly Ethereal) is shown below.

  [Ethernet] 
Destination: 00:90:1a:12:34:56
Source: 00:0a:e6:12:34:56
Type: PPPoE Session (0x8864)
[PPPoE]
0001 .... = Version: 1
.... 0001 = Type: 1
Code: Session Data (0x00)
Session ID: 0x024b
Payload Length: 54
[PPP]
Protocol: IP (0x0021)
[IP, Src: 201.42.106.123, Dst: 68.178.123.3]

Notice that the only values present in this packet that can uniquely identify a machine are its MAC address (Source field), its session code (Session ID field) and its IP address. With these three values in hand, we can forge an IP packet with a PPPoE header and send it to the machine whose values were intercepted.

Besides IP packets, as we have said, LCP packets can also be sent over the network. An example of an LCP packet is shown below.

  [Ethernet] 
Destination: 00:90:1a:12:34:56
Source: 00:0a:e6:12:34:56
Type: PPPoE Session (0x8864)
[PPPoE]
0001 .... = Version: 1
.... 0001 = Type: 1
Code: Session Data (0x00)
Session ID: 0x024b
Payload Length: 10
[PPP]
Protocol: Link Control Protocol (0xc021)
[LCP]
Code: Echo Request (0x09)
Identifier: 0x51
Length: 8
Magic number: 0x57ccee1c

This LCP packet is of the Echo Request type, and must be answered with an Echo Reply packet, otherwise the connection will be dropped. This type of packet is usually received when the connection is very inactive. The other end uses it to find out if the remote computer is still up or if it is just not carrying any traffic.

Note that we have a new field in this packet that uniquely identifies a machine. It is the Magic number. This number is assigned to one end by another through Configuration Request LCP packets. LCP Echo packets must always be accompanied by the Magic number of the machine that is sending them. If any discrepancy is identified by the other machine, the connection is severed. If a Magic number was not negotiated along the connection, through an LCP Configuration Request packet, the value zero must be used.

For more information about LCP packet types and their meanings, see the fifth section of RFC 1661 [6].

In summary, PPPoE does not use any digital signature algorithm to make it resistant to packet interception. An attacker can intercept any packet circulating on the network and use it to obtain the authentication data being used in the session - the source MAC address, the session code, and the machine's IP address.

If the attacker wants to take down the machine from which he intercepted this data, in order to use the connection only for himself, he must also intercept an LCP Echo Reply packet to find out the Magic number being used by the machine he is taking over from. In this way it can respond to LCP Echo Request packets it receives from the other end.

This makes the protocol extremely insecure for use on Wi-Fi networks, since the packets are easily intercepted.

4.3. Implementation

We will communicate with the Ethernet network interface that is connected to the target network via raw sockets. On Linux systems, we can read and write packets this way without any problems. On BSD systems, it would only be possible to write packets this way, requiring other methods for capturing and reading packets [7].

This real network interface should be directly accessed only by our tool. To allow other programs to use the "protected" network, we will create a TUN virtual network interface that will work in a similar way to the "ppp0" interface that would be created if we were connecting and authenticating normally (for example by rp-pppoe).

When receiving IP packets on the virtual interface, the tool should include forged PPPoE headers in them and send them to the real interface.

When receiving PPPoE packets on the real interface, the tool should check if they are IP or LCP packets. If they are IP packets, we can forward them to the virtual interface. Otherwise, we should process them and, if necessary, reply to them.

To help with this task, we built a small packet dissection system in Python. This dissector has several classes: Ethernet, PPPoE, PPP, IP, LCP, LCP_Configure and LCP_Echo. We pass the raw packet data to the initializer of one of these classes. The part of the packet corresponding to the protocol that names the class is dissected and placed in object members. If there is another protocol below the current frame, an object is created to process it and placed in the payload member. This is done until the entire packet is disassembled.

  Ethernet(          +-----------------+   +---------------+ 
"Pacote Bruto")-->| Classe Ethernet | | Classe PPPoE |
| - destination | | - version |
| - source | | - type |
| - type | | - code |
| - payload -------->| - session_id |
+-----------------+ | - payload |
+-----|---------+
v
+------------+
+-------------+ | Classe PPP |
"Frame IP | Classe IP | | - protocol |
Bruto" <---- packet_data |<--- - payload |
+-------------+ +------------+

To reconstruct dissected packages we call the construct method, which in turn calls the construct method of the current class payload, if one exists, and places it at the end of the reassembled package automatically. This way the dissected packages can be completely reassembled in a transparent way.

To implement this reassembly process in a more elegant way, we use Python's Decorators feature, which is only available since version 2.4 of the language.

                                                         +-------------+ 
| Classe IP |
| construct() |
+-------|-----+
+--------------|-----+
+--------------------+ | Classe PPP v |
+--------------------+ | Classe PPPoE | | @construct_payload |
| Classe Ethernet | | @construct_payload <-----construct() |
| @construct_payload <-----construct() | +--------------------+
| construct() | +--------------------+
+--------------------+

Both the dissector and the tool itself are capable of processing LCP packets only of the Configure and Echo types which, as we can see from sniffing, are the most commonly used by PPPoE implementations. LCP packets of other types are simply ignored, as if their transmission had failed. This should not pose any problems for the practical use of the tool.

Below is a simplified arrow diagram illustrating how the tool works.

  +-------------------+   +------------+ 
| Interface Real |-->| Dissecador |-->[IP?]
| (eth0) | +------------+ |
+-------------------+ [LCP?] |
^ | |
| v |
+-------------------+ +------------+ |
| Remontador |<--| Montar | |
+-------------------+ | Resposta | |
^ +------------+ v
| +-------------------+
| +--------+ | Interface Virtual |
|_________| Juntar |<--| (forge0) |
+--------+ +-------------------+
^
|
+--------------+
| Pacote PPPoE |
| Padrao |
+--------------+

For more details, please review the PPPoEforge code that accompanies this article, noting the internal documentation and comments therein.

4.4. Taking the connection from another machine

We will now exemplify the use of the tool. First, let's introduce the network structure used for the tests. We have a switch in which we connect two or more computers and a bridged ADSL modem. One of these computers is the network router.

  +------------+ +------------+ +------------+ 
| Modem ADSL | | Computador | | Nosso |
| (bridge) | | Roteador | | Computador |
+------------+ +------------+ +------------+
| ___________| |
| | __________________|
| | |
+---u-u-u-u-u-u-u-u---+
| Switch Encore N-Way |
+---------------------+

The Router Computer is normally connected to the Internet over a PPPoE connection. We wish to take over its connection.

First, we need to intercept some packets exchanged between the Router Computer and the Modem. To do this on a wired network like ours, we just have to camouflage our MAC with the Router's and send some packets to the switch. This way, it gets confused thinking that the network cable from the Router Computer has been switched to our network cable connector, and drops some packets to Our Computer. Meanwhile, WireShark (formerly Ethereal) is left open to collect these packets.

  # ifconfig eth0 down 
# ifconfig eth0 hw ether 00:0a:e6:12:34:56 192.168.0.1
# wireshark&
[1] 19374
# ping 192.168.0.2

After enough packets have been collected, the ping should be terminated. Ideally, if possible, you should collect at least one LCP Echo Reply packet to find out which Magic number is being used. In addition, the MAC addresses of each end, the IP of the machine on the Internet, and the session code should be noted. Let's take the following data as an example:

 MAC remoto 00:90:1a:12:34:56 
MAC local 00:0a:e6:12:34:56
Session ID 0x024b
IP externo 201.42.106.123
Magic num 0x57ccee1c

Now you must decide whether or not to bring down the machine whose connection will be taken. It is possible to let both machines use the connection at the same time, but the quality of the connection will suffer greatly if there is heavier network traffic. One of the reasons is that the switch only redirects packets to one network cable at a time, and makes the choice based on which one of them received the last packet.

If you choose to leave the other machine alone, you can include the -i option in the command line, so that PPPoEforge will only process IP packets. In this way, it will not respond to any LCP packets that are received. Depending on the packet loss that is happening on the network, this option may be appropriate, as the other machine using the connection may end up receiving LCP packets when they are forwarded from the other end and respond to them, sparing PPPoEforge the need to do so. Note that, using this option, it is not necessary to know the Magic number.

For this example, let's take down the other machine. This can be done very easily by unplugging the network cable from the switch. You must use the following command to start PPPoEforge:

  # python pppoeforge.py -n 0x57ccee1c eth0 00:90:1a:12:34:56 \ 
00:0a:e6:12:34:56 0x024b

Then you must assign the IP address to the virtual network interface forge0, which was created by PPPoEforge. Since this is a point-to-point network, it is also necessary to inform the IP address of the other end. However, this address cannot be obtained by intercepting packets traversing the network, because it does not appear in these packets. What can normally be done, then, is to draw (guess) any IP address, just for internal use by the operating system. There is no problem with this, since this address will not appear in any of the packets exchanged over the network.

Let us use for example the IP address 201.42.106.1 as if it were the other end of the network (it could be any other IP address we choose). This same address must also be set as the gateway.

  # ifconfig forge0 201.42.106.123 pointopoint 201.42.106.1 
# route add default gw 201.42.106.1

After that, the network is already up. To be able to use it comfortably, you must still configure the DNS servers that will be used. A practical tip is to always use the public OpenDNS servers.

  # echo "nameserver 208.67.222.222" >  /etc/resolv.conf 
# echo "nameserver 208.67.220.220" >> /etc/resolv.conf

After the network is up, the real IP address of the other end can be discovered using the traceroute utility. Note that this information is not necessary, it is just a curiosity.

  # traceroute -n google.com 
traceroute: Warning: google.com has multiple addresses; using 64.233.187.99
traceroute to google.com (64.233.187.99), 30 hops max, 40 byte packets
1 200.123.45.6 5.658 ms 5.588 ms 5.628 ms
^^^^^^^^^^^^

An interesting note is that the MAC of the actual network interface "eth0" does not have to be forged in order for PPPoEforge to work, as long as it is put into promiscuous mode. PPPoEforge itself does the filtering and only processes packets that have the appropriate MAC address.

In our tests, an ADSL Speedy connection, from Telefonica, was successfully transferred from one computer to another in the same network. This connection was left active for a whole day using PPPoEforge, presenting no problems. It was even possible to distribute it to three computers connected to the network, without the users noticing the difference.

4.5. Disguising access to the internal network

Another possible application for PPPoEforge is to disguise accesses from the internal network so that they appear to come from the external network. Let's take the same network as the previous usage example for testing. Suppose we want to access the Router computer via ssh from within the internal network, but pretending that access is coming from the Microsoft network. Let's use, for example, the IP address 207.46.19.30 as the access source.

The main difference from the previous example is that now PPPoEforge will not forge packets that appear to come from the Router Computer. Now it must forge packets that look like they are being transmitted from the ADSL modem and are directed to the Router Computer. To do this, you must switch the order of the MAC addresses on the command line from the order in the previous example.

  # ifconfig eth0 up promisc 
# python pppoeforge.py -i eth0 00:0a:e6:12:34:56 \
00:90:1a:12:34:56 0x024b

Next, the virtual network interface must be configured with the cloaking IP address on the local end and the target's IP address on the remote end. Once this is done, the target can now be accessed, for example via ssh.

  # ifconfig forge0 207.46.19.30 pointopoint 201.42.106.123 
# ssh 201.42.106.123
Password:
Last login: Mon Jan 29 13:48:06 2007 from 192.168.0.27

On a next machine access, the administrator will have the following as a record of the last access to his account:

 Last login: Mon Jan 29 23:01:40 2007 from 207.46.19.30

By checking the reverse DNS of that IP address, it will discover the origin of the strange access to your machine: wwwbaytest1.microsoft.com.

4.6. Use on Wi-Fi networks

The technique of taking access from a computer can also be applied to Wi-Fi networks. For this, it is necessary to put the card in Managed mode, associated with the AP with the MAC address of the machine from which the connection will be taken. After that, the procedure is the same as the one already explained, since Wi-Fi cards, when associated with an AP, behave practically the same way as Ethernet cards.

The tool's code can be modified, in a relatively simple way, to perform packet injection, for use with Wi-Fi cards that support this feature. Just put the card in Monitor mode and work with 802.11 frames instead of Ethernet frames in raw sockets [8]. This would make the internal network access cloaking attack simpler to perform on Wi-Fi networks.

The attack tests on Wi-Fi networks have unfortunately not yet been performed due to lack of adequate equipment, but everything should work as expected, since the tool was initially designed with Wi-Fi networks in mind.

5. Conclusion

The TUN/TAP virtual network driver proved to be a very useful accessory for the network security specialist's arsenal. Through its use, it was possible to build very functional applications in a simple way.

The Python language, which is becoming more and more popular in the area, fulfilled its role well. Its use allows the very fast creation of tools and increases the readability and reusability of the code.

The performance obtained in network traffic processing by utilities written in Python exceeded expectations, taking relatively few CPU resources and keeping memory consumption stable.

6. References

  1. [1] Documentacao oficial do TUN/TAP para Linux. /usr/src/linux/Documentation/networking/tuntap.txt
  2. Documentacao oficial do TUN/TAP para NetBSD. Manpages tun(4) e tap(4). Disponiveis online em: http://netbsd.gw.com/cgi-bin/man-cgi/man?tun+4 http://netbsd.gw.com/cgi-bin/man-cgi/man?tap+4
  3. Documentacao oficial do TUN/TAP para FreeBSD. Manpages tun(4) e tap(4). Disponiveis online em: http://www.freebsd.org/cgi/man.cgi?query=tun http://www.freebsd.org/cgi/man.cgi?query=tap
  4. Airpwn: Wireless Packet Injection Framework. http://airpwn.sourceforge.net
  5. Wikipedia: Initialization vector. http://en.wikipedia.org/wiki/Initialization_vector
  6. RFC 1661: The Point-to-Point Protocol: LCP Packet Formats. http://www.freesoft.org/CIE/RFC/1661/20.htm
  7. FreeBSD Mailing Lists: TCP Raw Socket Programming recvfrom(). http://lists.freebsd.org/pipermail/freebsd-hackers/2003-July/002011.html http://lists.freebsd.org/pipermail/freebsd-hackers/2003-July/002013.html http://lists.freebsd.org/pipermail/freebsd-hackers/2003-July/002016.html
  8. WiFi traffic injection based attacks, Cedric BLANCHER http://sid.rstack.org/pres/0511_Pacsec_WirelessInjection_en.pdf

begin 644 TUN-TAP-e-aplicacoes.tar.gz 
M'XL(`!8XOT4``^P]:W/:2+;YS*_H<2H7L0$"&'"&&J:N!^.8B1_$)C,[XW51
MLM2`)B"QDO!C:W_\GG.Z6VH],/;$Z\FMBRJQH1_GG#[O?J@]^GQ:&>T/*[QB

...

M=M?HTVPW_"'7DO""_.I7LUYL<_(+VL.^?=U05^.5S!=<89*\@9P;."LG:2]J
M/P$USJE;:>9XB2#X^O/EY-T&-R^.A,Z/^]>'@H%`(!`(!`*!0"`0"`0"@4`@
8$`@$`H%`(!`(!`*!_P:_`2\0(GH`H```
`
end

← previous
next →
loading
sending ...
New to Neperos ? Sign Up for free
download Neperos App from Google Play
install Neperos as PWA

Let's discover also

Recent Articles

Recent Comments

Neperos cookies
This website uses cookies to store your preferences and improve the service. Cookies authorization will allow me and / or my partners to process personal data such as browsing behaviour.

By pressing OK you agree to the Terms of Service and acknowledge the Privacy Policy

By pressing REJECT you will be able to continue to use Neperos (like read articles or write comments) but some important cookies will not be set. This may affect certain features and functions of the platform.
OK
REJECT