본문 바로가기

Linux

[Linux] ioctl

출처 : http://blog.naver.com/jmlee402/120005131921


3.1절. ioctl에 대해서


ioctl()은 입출력(Input/Oupt)장치의 제어(Contol)을 위해서 사용되는 함수로 장치에 접근해서 장치 정보를 얻어오거나 장치의 값을 변경하기 위한 용도로 사용된다. 여기에서는 네트워크 장치중 일부분의 정보를 가져오는 것만 다루고 있지만, 터미널, 소켓, cdrom, floppy, 프린터, 사운드카드.. 등 모든 입출력과 관련된 장치를 제어할 수 있다. 예를 들어 여러분이 오디오 CD를 재생하는 애플리케이션을 제작하길 원한다면 ioctl()을 이용해서 장치를 제어하고 CD에 있는 데이터를 읽어와야 한다. 


다음은 ioctl()함수 선언이다. #include <sys/ioctl.h> 


int ioctl(int d, int request, ...); 



첫번째 아규먼트 d는 입출력 지정자로써 일반 파일, 소켓, 장치등을 가리킨다. 이 문서는 네트워크 정보관련된 내용에 대해서 다루고 있으므로 d는 소켓지정자가 될 것이다. request는 ioctl()로 요청할 정보를 지정하기 위해서 사용한다. ...는 request를 통해서 얻은 정보를 저장하기 위한 버퍼이다. 


...부분은 어떤 입출력장치에 대한 제어를 하느냐에 따라 달라지는데, 그러므로 ioctl()을 제대로 사용하기 위해서는 필요한 정보를 얻어오기 위한 request와 request에 대해서 넘겨주는 데이터(대부분 구조체)에 대해서 알고 있어야 한다(혹은 필요할 경우 쉽게 찾아낼 수 있어야 한다). 


request가능한 목록은 man ioctl_list로 확인해 볼 수 있다. 여기에서는 주로 네트워크 인터페이스에 관련된 정보를 얻어오게 될 것이므로 SIOC로 시작되는 request들을 사용하게 될 것이다. 이들 네트워크 정보관련 정보들은 대부분 ifreq구조를 통해서 넘어오게 된다. 지금까지 내용을 바탕으로 네트워크 인터페이스 정보를 얻기 위해서 대략 다음과 같은 방법으로 ioctl()을 사용할 수 있다는걸 감 잡을수 있을 것이다. // 인터페이스에 대한 인터넷 주소를 얻어오는 일반적인 방법 

ioctl(sockfd, SIOCGIFDSTADDR, (char *)&ifreq); 





참고로 request의 목록이 정의된 헤더파일은 /usr/include/bits/ioctls.h이다. 



-------------------------------------------------------------------------------- 


3.2절. 네트워크 정비와 관련된 구조체 

3.2.1절. struct ifconf


네트워크 인터페이스의 정보를 얻어오기 위한 가장 기본이 되는 구조체로 네트워크 인터페이스 정보 얻기원한다면 ioctl()을 이용해서 ifconf 구조체의 값을 얻어오는 것부터 시작한다. 


코드:

struct ifconf 

    int ifc_len;            /* Size of buffer.  */ 

    union 

      { 

    __caddr_t ifcu_buf; 

    struct ifreq *ifcu_req; 

      } ifc_ifcu; 

}; 

# define ifc_buf    ifc_ifcu.ifcu_buf   /* Buffer address.  */ 

# define ifc_req    ifc_ifcu.ifcu_req   /* Array of structures.  */ 

# define _IOT_ifconf _IOT(_IOTS(struct ifconf),1,0,0,0,0) /* not right */ 

            


이 구조체에 저장되는 핵심 정보는 네트워크 인터페이스의 정보가 저장된 ifreq구조체로써, 이구조체를 이용해서 인터페이스의 핵심적인 정보를 얻을 수 있다. 


다음과 같이 ioctl()을 이용하므로써 ifconf정보를 얻어 올 수 있다. ioctl(fd, SIOCGIFCONF, (char *)&ifcfg); 



그러면 모든 (루프백을 포함한)네트워크 인터페이스에 대한 정보를 얻어오게 된다. 가져온 정보는 ifcu_buf버퍼에 쌓이게 된다. ifcu_buf에는 인터페이스의 갯수만큼 크기가 늘어나야 하므로 (인터페이스 갯수)X(sizeof(ifreq)) 만큼의 공간을 필요로 하게 된다. ifc_len은 바로 이 버퍼의 크기를 가지고 있다. 



-------------------------------------------------------------------------------- 


3.2.2절. struct ifreq


위에서 설명했듯이 ifreq는 네트워크 인터페이스 정보들을 담고 있다.

코드:

struct ifreq 

  { 

# define IFHWADDRLEN    6 

# define IFNAMSIZ   IF_NAMESIZE 

    union 

      { 

    char ifrn_name[IFNAMSIZ];   /* Interface name, e.g. "en0".  */ 

      } ifr_ifrn; 


    union 

      { 

    struct sockaddr ifru_addr; 

    struct sockaddr ifru_dstaddr; 

    struct sockaddr ifru_broadaddr; 

    struct sockaddr ifru_netmask; 

    struct sockaddr ifru_hwaddr; 

    short int ifru_flags; 

    int ifru_ivalue; 

    int ifru_mtu; 

    struct ifmap ifru_map; 

    char ifru_slave[IFNAMSIZ];  /* Just fits the size */ 

    char ifru_newname[IFNAMSIZ]; 

    __caddr_t ifru_data; 

      } ifr_ifru; 

  }; 

             

 

멤버들은 이름만 봐도 어떤 데이터의 저장을 위해 사용되는지 알 수 있을 것이다. ifru_addr, ifru_broadaddr, ifru_netmask는 인터페이스의 주소, 브로드케스팅주소, 네트마스크 주소가 저장된다. ifru_mtu는 MTU(maximum Transmission Unit)값이 저장된다. 


참고: MTU는 TCP/IP 네트 워크 상에서 한번에 전송할 수 있는 패킷의 최대크기를 나타낸다. 이보다 큰 데이터를 보내고자 할때는 MTU사이즈에 맞도록 여러개로 나뉘어서 보내게 된다. 하나의 큰 데이터를 보내는 것보다는 적당한 크기로 나누어서 보내는게 네트워크 환경의 효율을 높일 수 있기 때문이다. MTU사이즈는 네트워크 환경에 따라 달라 질수 있다. 일반적 으로 ethernet은 1500의 크기를 가진다. 




-------------------------------------------------------------------------------- 


3.3절. 예제


지금까지 내용을 종합해서 네트워크 인터페이스 정보를 얻어오는 간단한 프로그램을 만들어 보도록 하겠다. 프로그램이 하는일은 pcap설명시 나왔던 예제와 별차이가 없으나 좀더 상세한 정보들을 보여주도록 작성되었다. 


코드:

예제 : ioctl_net.c #include <sys/ioctl.h> 

#include <sys/types.h> 

#include <sys/stat.h> 

#include <sys/socket.h> 

#include <unistd.h> 

#include <netinet/in.h> 

#include <arpa/inet.h> 

#include <stdio.h> 

#include <stdlib.h> 

#include <string.h> 

#include <net/if.h> 

#include <arpa/inet.h> 


int main() 

    // 이더넷 데이터 구조체 

    struct ifreq *ifr; 

    struct sockaddr_in *sin; 

    struct sockaddr *sa; 


    // 이더넷 설정 구조체 

    struct ifconf ifcfg; 

    int fd; 

    int n; 

    int numreqs = 30; 

    fd = socket(AF_INET, SOCK_DGRAM, 0); 


    // 이더넷 설정정보를 가지고오기 위해서 

    // 설정 구조체를 초기화하고  

    // ifreq데이터는 ifc_buf에 저장되며, 

    // 네트워크 장치가 여러개 있을 수 있으므로 크기를 충분히 잡아주어야 한다.  

    // 보통은 루프백주소와 하나의 이더넷카드, 2개의 장치를 가진다. 

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

    ifcfg.ifc_buf = NULL; 

    ifcfg.ifc_len = sizeof(struct ifreq) * numreqs; 

    ifcfg.ifc_buf = malloc(ifcfg.ifc_len); 


    for(;;) 

    { 

        ifcfg.ifc_len = sizeof(struct ifreq) * numreqs; 

        ifcfg.ifc_buf = realloc(ifcfg.ifc_buf, ifcfg.ifc_len); 

        if (ioctl(fd, SIOCGIFCONF, (char *)&ifcfg) < 0) 

        { 

            perror("SIOCGIFCONF "); 

            exit; 

        } 

        // 디버깅 메시지 ifcfg.ifc_len/sizeof(struct ifreq)로 네트워크 

        // 장치의 수를 계산할 수 있다.  

        // 물론 ioctl을 통해서도 구할 수 있는데 그건 각자 해보기 바란다. 

        printf("%d : %d \n", ifcfg.ifc_len, sizeof(struct ifreq)); 

        break; 

    } 


    // 주소를 비교해 보자.. ifcfg.if_req는 ifcfg.ifc_buf를 가리키고 있음을 

    // 알 수 있다. 

    printf("address %d\n", &ifcfg.ifc_req); 

    printf("address %d\n", &ifcfg.ifc_buf); 


    // 네트워크 장치의 정보를 얻어온다.  

    // 보통 루프백과 하나의 이더넷 카드를 가지고 있을 것이므로 

    // 2개의 정보를 출력할 것이다. 

    ifr = ifcfg.ifc_req; 

    for (n = 0; n < ifcfg.ifc_len; n+= sizeof(struct ifreq)) 

    { 

        // 주소값을 출력하고 루프백 주소인지 확인한다. 

        printf("[%s]\n", ifr->ifr_name); 

        sin = (struct sockaddr_in *)&ifr->ifr_addr; 

        printf("IP    %s %d\n", inet_ntoa(sin->sin_addr), sin->sin_addr.s_addr); 

        if ( ntohl(sin->sin_addr.s_addr) == INADDR_LOOPBACK) 

        { 

            printf("Loop Back\n"); 

        } 

        else 

        { 

            // 루프백장치가 아니라면 MAC을 출력한다. 

            ioctl(fd, SIOCGIFHWADDR, (char *)ifr); 

            sa = &ifr->ifr_hwaddr; 

            printf("%s\n", ether_ntoa((struct ether_addr *)sa->sa_data)); 

        } 

        // 브로드 캐스팅 주소 

        ioctl(fd,  SIOCGIFBRDADDR, (char *)ifr); 

        sin = (struct sockaddr_in *)&ifr->ifr_broadaddr; 

        printf("BROD  %s\n", inet_ntoa(sin->sin_addr)); 

        // 네트워크 마스팅 주소 

        ioctl(fd, SIOCGIFNETMASK, (char *)ifr); 

        sin = (struct sockaddr_in *)&ifr->ifr_addr; 

        printf("MASK  %s\n", inet_ntoa(sin->sin_addr)); 

        // MTU값 

        ioctl(fd, SIOCGIFMTU, (char *)ifr); 

        printf("MTU   %d\n", ifr->ifr_mtu); 

        printf("\n"); 

        ifr++; 

    } 

          

 

필자의 리눅스 박스에서 실행시킨 결과이다.

코드:

# ./ioctl_net 

64 : 32 

address -1073743948 

address -1073743948 

[lo] 

IP    127.0.0.1 16777343 

Loop Back 

BROD  127.255.255.255 

MASK  255.0.0.0 

MTU   16436 


[eth0] 

IP    61.254.131.50 847511101 

52:54:5:ff:0:73 

BROD  255.255.255.255 

MASK  255.255.255.0 

MTU   1500 

         


IP, MAC주소, MTU, 브로드캐스팅/네트워킹 주소 정보를 가져오는걸 확인할 수 있을 것이다. 이들정보는 낮은 수준에서의 네트워크 프로그래밍을 하는데 중요하게 사용할 수 있을 것이다.