http://bbs.kldp.org/viewtopic.php?t=768
여기에 썼던글이다. (drupal로 이관하면서 이관된 문서를 찾으려 했으나 역부족.)
본 글은 IP 주소를 얻는 방법에 대해 쓴 것이 아니라, 그 이면에 있는 드라이버와의 통신에 대해 다룬다.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stropts.h>

#if defined(sun)
#include <sys/sockio.h>
#endif

#include <net/if.h>
#if defined(linux)
#include <linux/sockios.h>
#endif

#define BUFFERSIZE 1024
const char * localip = "0.0.0.0";

const char * myip()
{
        const int MAX_NIC = 10;
        struct ifconf ifc;
        struct ifreq ifr[MAX_NIC];

        int s;
        int nNumIFs;
        int i;
        int count;
        int max=2;
        static char ip[BUFFERSIZE];
        int cmd = SIOCGIFCONF;

        max++;

        ifc.ifc_len = sizeof ifr;
        ifc.ifc_ifcu.ifcu_req = ifr;

        if( (s=socket(AF_INET,SOCK_STREAM,0)) < 0)
        {
                perror("socket");
                exit(1);
        }

#if defined(_AIX)
        cmd = CSIOCGIFCONF;
#endif

        if( ioctl(s, cmd, &ifc) < 0)
        {
                perror("ioctl");
                exit(1);
        }
        close(s);

        nNumIFs = ifc.ifc_len / sizeof ( struct ifreq );
        count = 0;
        strcpy( ip, localip );
        for( i=0; i<nNumIFs; i++ )
        {
                struct in_addr addr;
                if( ifc.ifc_ifcu.ifcu_req[i].ifr_ifru.ifru_addr.sa_family != AF_INET)
                {
                        continue;
                }

                addr = ((struct sockaddr_in *) & ifc.ifc_ifcu.ifcu_req[i].ifr_ifru.ifru_addr)->sin_addr;
                if( addr.s_addr == htonl( 0x7f000001 ) )
                {
                        continue;
                }
                strcpy( ip, inet_ntoa( addr ) );
                printf( "IP: %s\n", ip );
        }
        return ip;
}

int main()
{
        printf("One of my IP is %s\n", myip() );
        return 0;
}

이 함수를 인용하는 이유는, 원리가 특이하다 생각할 수 있기 때문이다. 자세히 들여다보면, myip 함수에서 socket 함수를 사용하여 소켓하나를 만들고 정작 우리가 흔히 이용하는 대로 bind하거나 connect하지 않는다.
위 함수가 Windows에서 돌아가는지조차 테스트 해보지 않아서 더더욱 이식성이라도 있는지 모르겠다.

Windows 같으면 Registry를 뒤져가며, 네트웍 인터페이스 카드(NIC)에 할당되어 있는 DHCP IP건 Auto IP건 말그대로 Static IP건 찾는대로 보여줄텐데, 이 녀석은 그렇지 않다.

IP 주소는 무엇인가? 커널의 어떤 녀석이 그 주소 정보를 가지고 있는것인가? SIOCGIFCONF 라는 옵션은 인터페이스 구성에 대한 것을 되돌려 받는 것 같은데, 그것이 커널내의 어떤 녀석에게 물어 본다는 것인가? 상상을 하자면 끝이 없다.

모든 I/O 관련 시스템콜이 그렇지만, 간과해서는 안 될 것이, I/O 관련 시스템콜은 사실 파일 기술자 혹은 소켓이 어디 소속인지를 보고 소속 드라이버의 실제 시스템콜을 호출하는 것에 불과하다는 것이다. 드라이버를 제작하다보면, 그 드라이버와 통신하기 위해 몇가지 방법이 존재하는데 그 중 한 방법이 ioctl이다. (디바이스 드라이버나 가상 파일 시스템을 만들수도 있고, 시스템 콜을 추가할 수도 있다.)

위의 예제는 네트웍 인터페이스 카드의 주소를 설정하기 위해, 해당 드라이버와 통신하기 위한 소켓을 만들었을 뿐, 그 이상도 이하도 아니다. 그 소켓은 실제 통신용이 아니라는 얘기이다. 만일, 실제 통신용이 존재한다면, 그것을 그대로 이용해도 무방하다. 즉 따로 IP를 구하기 위해 소켓을 만들지 말라는 얘기이다.

아니 정말, 소켓을 만든이유가 단지 그 드라이버와의 통신을 위한 것이란 말이오? 라고 반문할 수 있겠지만, 요즈음에 사용되는 /proc 파일 시스템같은 것들이 소개되기 훨씬이전부터 있어 왔던 방식이다라고 말할 수 밖에 없다.

좀더 생각해 볼 것은, read가 recv와 같은 역할을 하는 이유가 read, recv 들은 사실 wrapper일 뿐 실제적인 녀석은 따로 있기 때문이다. 둘 다 소켓을 보고 해당 드라이버의 recv 용 함수를 부르게된다.
신고

+ Recent posts