리눅스 TUN/TAP 디바이스 프로그래밍
- 프로그래밍/리눅스 프로그래밍
- 2021. 1. 28.
반응형
개요
TUN 과 TAP(Terminal Access Point) 은 가상 네트워크 커널 인터페이스입니다.
위키피디아(https://en.wikipedia.org/wiki/TUN/TAP)에 따르면, 운영체제가 TUN/TAP 디바이스로 전송한 패킷들은 해당 TUN/TAP 디바이스에 연결된 사용자공간 프로그램으로 전달되며, 사용자공간 프로그램이 TUN/TAP 디바이스로 전송한 패킷들은 운영체제 네트워크 스택으로 전달됩니다.
TUN 디바이스는 IP 계층에서 이러한 역할을 수행하고, TAP 디바이스는 데이터링크 계층에서 이러한 역할을 수행합니다.
사용법
다음 명령으로 디바이스를 생성합니다.
mkdir /dev/net (없을 경우)
mknod /dev/net/tun c 10 200
다음 명령으로 디바이스 권한을 설정합니다.
chmod 0666 /dev/net/tun
드라이버 모듈을 로딩합니다.
modprobe tun
어플리케이션 프로그램 작성법
초기화 루틴
- IFF_TUN 플래그 : TUN 디바이스로 생성합니다. (IP 패킷이 교환됨)
- IFF_TAP 플래그 : TAP 디바이스로 생성합니다. (Ethernet 패킷이 교환됨)
- IFF_NO_PI : 설정할 경우, 패킷정보가 전달되지 않습니다.
- 패킷정보는 4바이트 길이를 가지며, 앞 2바이트는 Flags, 뒤 2바이트는 프로토콜 유형(예: IP, IPv6 - 즉, EtherType)을 나타냅니다.
#include <linux/if.h>
#include <linux/if_tun.h>
int tun_alloc(char *dev)
{
struct ifreq ifr;
int fd, err;
if( (fd = open("/dev/net/tun", O_RDWR)) < 0 )
return tun_alloc_old(dev);
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;
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;
}
read/write (패킷 송/수신) 를 동시에 처리하기 위해 여러 개의 큐(=file descriptor)를 생성할 수도 있습니다.
#include <linux/if.h>
#include <linux/if_tun.h>
int tun_alloc_mq(char *dev, int queues, int *fds)
{
struct ifreq ifr;
int fd, err, i;
if (!dev)
return -1;
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
* IFF_MULTI_QUEUE - Create a queue of multiqueue device
*/
ifr.ifr_flags = IFF_TAP | IFF_NO_PI | IFF_MULTI_QUEUE;
strcpy(ifr.ifr_name, dev);
for (i = 0; i < queues; i++) {
if ((fd = open("/dev/net/tun", O_RDWR)) < 0)
goto err;
err = ioctl(fd, TUNSETIFF, (void *)&ifr);
if (err) {
close(fd);
goto err;
}
fds[i] = fd;
}
return 0;
err:
for (--i; i >= 0; i--)
close(fds[i]);
return err;
}
큐는 기본적으로 생성과 동시에 활성화 상태가 되지만, 다음 코드를 통해 각 큐를 활성화, 비활성화 시킬 수도 있습니다.
#include <linux/if.h>
#include <linux/if_tun.h>
int tun_set_queue(int fd, int enable)
{
struct ifreq ifr;
memset(&ifr, 0, sizeof(ifr));
if (enable)
ifr.ifr_flags = IFF_ATTACH_QUEUE;
else
ifr.ifr_flags = IFF_DETACH_QUEUE;
return ioctl(fd, TUNSETQUEUE, (void *)&ifr);
}
패킷 수신 루틴
위에서 생성된 파일 디스크립터에 대한 read() 함수 호출을 통해 패킷을 수신할 수 있습니다.
위에서 TUN 디바이스로 생성했을 경우에는 IP 패킷이, TAP 디바이스로 생성했을 경우에는 Ethernet 패킷이 수신됩니다.
/* Now read data coming from the kernel */
while(1) {
/* Note that "buffer" should be at least the MTU size of the interface, eg 1500 bytes */
nread = read(tun_fd,buffer,sizeof(buffer));
if(nread < 0) {
perror("Reading from interface");
close(tun_fd);
exit(1);
}
/* Do whatever with the data */
printf("Read %d bytes from device %s\n", nread, tun_name);
for (int i = 0; i < nread; i++) {
if ((i!=0) && (i%16==0)) {
printf("\n");
}
printf("%02X ", buffer[i]);
}
printf("\n");
}
샘플 어플리케이션 작성 및 실행 (예제)
코드 작성
다음은 TUN/TAP 디바이스로부터 패킷을 수신하는 샘플 어플리케이션입니다.
#include <sys/socket.h> /// <linux/if.h> 보다 위에 선언되어야 한다.
#include <linux/if.h>
#include <linux/if_tun.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
int tun_alloc(char *dev, int flags)
{
struct ifreq ifr;
int fd, err;
char *clonedev = "/dev/net/tun";
printf("Allocating tun device\n");
/* Arguments taken by the function:
*
* char *dev: the name of an interface (or '\0'). MUST have enough space to hold the interface name if '\0' is passed
* int flags: interface flags (eg, IFF_TUN etc.)
*/
/* open the clone device */
if( (fd = open(clonedev, O_RDWR)) < 0 ) {
printf("Fail to open clone device %s - %m\n", clonedev);
return fd;
}
/* preparation of the struct ifr, of type "struct ifreq" */
memset(&ifr, 0, sizeof(ifr));
ifr.ifr_flags = flags; /* IFF_TUN or IFF_TAP, plus maybe IFF_NO_PI */
if (*dev) {
/* if a device name was specified, put it in the structure; otherwise,
* the kernel will try to allocate the "next" device of the
* specified type */
strncpy(ifr.ifr_name, dev, IFNAMSIZ);
printf("Create device - %s\n", dev);
}
/* try to create the device */
if( (err = ioctl(fd, TUNSETIFF, (void *) &ifr)) < 0 ) {
printf("Fail to create device - %m\n");
close(fd);
return err;
}
/* if the operation was successful, write back the name of the
* interface to the variable "dev", so the caller can know
* it. Note that the caller MUST reserve space in *dev (see calling
* code below) */
strcpy(dev, ifr.ifr_name);
printf("Success to allocate tun device %s\n", dev);
/* this is the special file descriptor that the caller will use to talk
* with the virtual interface */
return fd;
}
int main(int argc, char *argv[])
{
int nread;
char buffer[2000];
char tun_name[IFNAMSIZ];
char tap_name[IFNAMSIZ];
/* Connect to the device */
strcpy(tun_name, "tun77");
int tun_fd = tun_alloc(tun_name, IFF_TAP | IFF_NO_PI | IFF_MULTI_QUEUE); /* tun interface : IFF_TUN */
if(tun_fd < 0){
perror("Allocating interface");
exit(1);
}
/* Now read data coming from the kernel */
while(1) {
/* Note that "buffer" should be at least the MTU size of the interface, eg 1500 bytes */
nread = read(tun_fd,buffer,sizeof(buffer));
if(nread < 0) {
perror("Reading from interface");
close(tun_fd);
exit(1);
}
/* Do whatever with the data */
printf("Read %d bytes from device %s\n", nread, tun_name);
for (int i = 0; i < nread; i++) {
if ((i!=0) && (i%16==0)) {
printf("\n");
}
printf("%02X ", buffer[i]);
}
printf("\n");
}
}
어플리케이션 실행
# ./tun-test &
[1] 9664
Allocating tun device
Create device - tun77
Success to allocate tun device tun77
root@imx6qpdlsolox:~/20200214#
root@imx6qpdlsolox:~/20200214# ifconfig tun77 up
root@imx6qpdlsolox:~/20200214# Read 90 bytes from device tun77
33 33 00 00 00 16 02 DB 09 4C 6C D6 86 DD 60 00
00 00 00 24 00 01 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 FF 02 00 00 00 00 00 00 00 00
00 00 00 00 00 16 3A 00 05 02 00 00 01 00 8F 00
02 68 00 00 00 01 04 00 00 00 FF 02 00 00 00 00
00 00 00 00 00 01 FF 4C 6C D6
root@imx6qpdlsolox:~/20200214# Read 78 bytes from device tun77
33 33 FF 4C 6C D6 02 DB 09 4C 6C D6 86 DD 60 00
00 00 00 18 3A FF 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 FF 02 00 00 00 00 00 00 00 00
00 01 FF 4C 6C D6 87 00 99 07 00 00 00 00 FE 80
00 00 00 00 00 00 00 DB 09 FF FE 4C 6C D6
Read 90 bytes from device tun77
33 33 00 00 00 16 02 DB 09 4C 6C D6 86 DD 60 00
00 00 00 24 00 01 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 FF 02 00 00 00 00 00 00 00 00
00 00 00 00 00 16 3A 00 05 02 00 00 01 00 8F 00
02 68 00 00 00 01 04 00 00 00 FF 02 00 00 00 00
00 00 00 00 00 01 FF 4C 6C D6
# ifconfig
eth0 Link encap:Ethernet HWaddr fe:ff:ff:f4:35:76
inet addr:192.168.0.60 Bcast:192.168.0.255 Mask:255.255.255.0
inet6 addr: fe80::fcff:ffff:fef4:3576/64 Scope:Link
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:405239 errors:0 dropped:33801 overruns:0 frame:0
TX packets:3472 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:61232351 (58.3 MiB) TX bytes:304234 (297.1 KiB)
lo Link encap:Local Loopback
inet addr:127.0.0.1 Mask:255.0.0.0
inet6 addr: ::1/128 Scope:Host
UP LOOPBACK RUNNING MTU:65536 Metric:1
RX packets:97480 errors:0 dropped:0 overruns:0 frame:0
TX packets:97480 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1
RX bytes:7408704 (7.0 MiB) TX bytes:7408704 (7.0 MiB)
tun77 Link encap:Ethernet HWaddr 02:db:09:4c:6c:d6
inet6 addr: fe80::db:9ff:fe4c:6cd6/64 Scope:Link
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:0 errors:0 dropped:0 overruns:0 frame:0
TX packets:18 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:0 (0.0 B) TX bytes:3502 (3.4 KiB)
'프로그래밍 > 리눅스 프로그래밍' 카테고리의 다른 글
리눅스 어플리케이션 반복 실행 테스트 방법 (1) | 2022.09.25 |
---|---|
nanosleep() - 프로세스/쓰레드의 실행을 멈추는(재우는) 함수 (0) | 2022.04.20 |
Kernel PPS 신호 사용하기 (C source code) (0) | 2022.03.26 |
CURL 라이브러리 사용법 - 임베디드 플랫폼용으로 빌드하기(arm-cross-sysroot 활용) (0) | 2021.03.09 |
프로세스 간 통신(IPC) 프로그래밍 - 메시지큐 (0) | 2021.01.19 |
GPSd를 이용한 GNSS 프로그래밍 (0) | 2020.12.24 |
리눅스 프로그래밍 - 프로세스 종료 신호(SIGINT, SIGTERM) 후킹하기 (0) | 2020.08.21 |
리눅스 타이머 프로그래밍 (0) | 2020.07.31 |