uIP TCP/IP Stack

이재홍 http://www.pyrasis.com 2003.11.1 ~ 2004.1.29

목차

uIP 0.9

uIP의 공식 홈페이지 : http://www.dunkels.com/adam/uip

공개 TCP/IP 스택으로 Swedish Institute of Computer Science의 Adam Dunkels이라는 사람이 제작하였습니다.

uIP 0.9의 디렉터리 구성

uip-0.9
  apps: uIP TCP/IP 스택을 사용한 응용프로그램들
    httpd
    resolv
    smtp
    telnet
    telnetd
    webclient
  do :  uIP의 문서들
    html
  uip: TCP/IP 스택 소스
  unix: Unix, Linux에서 실행 가능한 예제 프로그램

uIP 0.9의 파일

uip/
  uip_arp.h
  uip_arp.c    : ARP 프로토콜 처리 부분
  uip_arch.h   : 아키텍쳐 의존적 부분 설정 헤더파일
  uip.h
  uip.c        : uip의 소스코드
  slipdev.h
  slipdev.c    : slip 드라이버 소스코드

unix/
  main.c      : 예제 프로그램 메인
  tapdev.c    : Tun/Tap 디바이스 드라이버 소스코드
  tapdev.h
  uip_arch.c  : 아키텍쳐 의존적인 부분
  uipopt.h    : 변수 타입, IP설정, 각종 설정 부분

예제를 실행하기 위한 준비 과정

예제를 동작시키기 위해서 리눅스 커널 컴파일에서 아래의 옵션을 체크하고 커널을 다시 컴파일 해야 합니다.

이 장치 드라이버는 가상 장치 드라이버입니다.

FreeBSD에서는 커널 컴파일 다시 할 필요 없이 바로 실행 할 수 있습니다.

리눅스 커널 옵션
    Network device support  --->
      <M> Universal TUN/TAP device driver support

다음 명령을 입력하여 장치를 생성한다.

# mkdir /dev/net
# mknod /dev/net/tun c 10 200

예제 프로그램 컴파일 및 실행

uip/unix 디렉토리에서 make로 컴파일하고 실행합니다.

# make
# ./uip

다른 터미널에서 ifconfig를 실행해서 tap0 장치가 만들어졌는지 확인합니다. 새로 만들어진 tap0 장치에 192.168.0.1이라는 IP주소가 할당 되어 있습니다. 이 주소는 uIP의 게이트웨이로 설정되기 때문에 실제 웹 서버가 작동 되는 IP는 192.168.0.2입니다. 이 IP는 ifconfig로 나오지 않습니다.

웹 브라우저에서 http://192.168.0.2로 접속하면 예제 웹 서버에 접속할 수 있습니다. 리눅스의 모질라, 모질라 파이어폭스, 컨커러, Lynx, w3m등을 사용하면 됩니다.

uIP TCP/IP Stack 소스 분석

uIP의 소스를 분석하도록 하겠습니다. 앞서 예제를 컴파일 해서 작동을 시켜 보면서 분석을 해보면 쉽게 할 수 있습니다.

초기화 과정 분석

예제 프로그램으로 분석을 하겠습니다. 예제 프로그램을 완벽히 파악해야 uIP를 이해 할 수 있습니다. 나중에 MicroC/OS-II에 포팅을 할 때에도 예제 프로그램의 실행 루틴을 그대로 따라서 할 것입니다. uIP를 만든 사람은 이 예제 프로그램을 통해서 uIP가 작동되는 방식을 쉽게 설명하려 하고 있습니다.

unix 디렉토리의 main.c의 main() 함수의 앞부분 일부 소스 입니다.

int
main(void)
{
  u8_t i, arptimer;

  /* Initialize the device driver. */
  /* Tap/Tun 드라이버 초기화 */
  tapdev_init();

  /* Initialize the uIP TCP/IP stack. */
  uip_init();

  /* Initialize the HTTP server. */
  /* HTTP 서버 초기화 */
  httpd_init();

tapdev_init() : 네트웍 드라이버 초기화 tap0 네트웍 장치에 192.168.0.1의 IP 설정

unix 디렉토리의 tapdev.c의 tapdev_init() 함수 입니다.

/* 
 * void tapdev_init(void)
 * Tun/Tap 장치 드라이버 초기화 함수
 */
void
tapdev_init(void)
{
  char buf[1024];

  /* /dev/net/tun 장치파일이 존재 하는지 체크 한다 */
  fd = open(DEVTAP, O_RDWR);
  if(fd == -1) {
    perror("tapdev: tapdev_init: open");
    exit(1);
  }

#ifdef linux
  /* ifconfig 했을때 나오는 tap0 디바이스를 만든다. */
  {
    struct ifreq ifr;
    memset(&ifr, 0, sizeof(ifr));
    ifr.ifr_flags = IFF_TAP|IFF_NO_PI;
    if (ioctl(fd, TUNSETIFF, (void *) &ifr) < 0) {
      perror(buf);
      exit(1);
    }
  }
#endif /* Linux */

  /* tap0 디바이스에 192.168.0.1의 IP를 준다.
   * UIP_DRIPADDR0~3
   * uipopt.h 159~168 line
   */
  snprintf(buf, sizeof(buf), "ifconfig tap0 inet %d.%d.%d.%d",
       UIP_DRIPADDR0, UIP_DRIPADDR1, UIP_DRIPADDR2, UIP_DRIPADDR3);
  system(buf);

  lasttime = 0;
}

uip_init(): uip 초기화 uip_hostaddr에 192.168.0.2의 IP 설정

uip 디렉토리의 uip.c의 uip_init() 함수 입니다.

void
uip_init(void)
{
  for(c = 0; c < UIP_LISTENPORTS; ++c) {
    uip_listenports[c] = 0;
  }
  for(c = 0; c < UIP_CONNS; ++c) {
    uip_conns[c].tcpstateflags = CLOSED;
  }
#if UIP_ACTIVE_OPEN
  lastport = 1024;
#endif /* UIP_ACTIVE_OPEN */

#if UIP_UDP
  for(c = 0; c < UIP_UDP_CONNS; ++c) {
    uip_udp_conns[c].lport = 0;
  }
#endif /* UIP_UDP */


  /* IPv4 initialization. */
  /* 192.168.0.2 IP 설정 */
#if UIP_FIXEDADDR == 0
  uip_hostaddr[0] = uip_hostaddr[1] = 0;
#endif /* UIP_FIXEDADDR */

}

httpd_init(): http 서버 초기화 fs_init()으로 파일 시스템 초기화 uip_listen(HTONS(80));으로 80번 포트 listen 상태로 만듦

app/httpd의 httpd.c 파일의 httpd_init() 함수입니다.

void
httpd_init(void)
{
  fs_init();

  /* Listen to port 80. */
  uip_listen(HTONS(80));
}

송신 과정 분석

while(1) { }로 무한 루프, 폴링 방식입니다. 폴링 방식인 이유는 uIP의 예제 프로그램이 리눅스/유닉스의 응용프로그램으로 실행 되고 있기 때문입니다.

예제 프로그램은 단순히 uIP를 최소한으로 작동시키게만 할 뿐입니다.

실시간 OS같은데 포팅을 하기 위해서는 그 OS와 네트워크 카드(칩셋)에 맞게 인터럽트 방식을 사용하면 됩니다.

unix 디렉토리의 main.c main() 함수 전체 입니다.
int
main(void)
{
  u8_t i, arptimer;

  /* Initialize the device driver. */
  /* Tap/Tun 드라이버 초기화 */
  tapdev_init();

  /* Initialize the uIP TCP/IP stack. */
  uip_init();

  /* Initialize the HTTP server. */
  /* HTTP 서버 초기화 */
  httpd_init();

  arptimer = 0;

  while(1) {
    /* Let the tapdev network device driver read an entire IP packet
       into the uip_buf. If it must wait for more than 0.5 seconds, it
       will return with the return value 0. If so, we know that it is
       time to call upon the uip_periodic(). Otherwise, the tapdev has
       received an IP packet that is to be processed by uIP. */
    /*
       tapdev 네트워크 디바이스 드라이버를 읽어 전체의 IP 패킷을 uip_buf
       에 넣는다. 만약 0.5초를 대기 한다면 tapdev는 return값 0을 반환
       한다. 그렇다면 우리는 uip_periodic()을 호출할 시간이라는 것을 알
       수 있다. 그렇지 않으면 tapdev는 uIP에 의해 처리된 패킷을 받는다.*/
    /* 
       extern volatile u16_t uip_len, uip_slen; 
       tapdev_read()에서 리턴한 받은 패킷의 길이를 uip_len에 저장한다. */
    uip_len = tapdev_read();
    if(uip_len == 0) {
      for(i = 0; i < UIP_CONNS; i++) {
        /* #define uip_periodic(conn) do { uip_conn = &uip_conns[conn]; \
                                           uip_process(UIP_TIMER); } while (0)
           uip.h 
           
           UIP_CONNS 10 
           uipopt.h */
        /* 받은 패킷의 길이가 0이라면, TCP연결 루틴 처리를 10번 한다. 
           이 함수는 꼭 호출 되어야 한다. 
           어플리케이션과 TCP/IP 스택이 연결되는 가장 중요한 함수이다. */
        uip_periodic(i);
        /* If the above function invocation resulted in data that
           should be sent out on the network, the global variable
           uip_len is set to a value > 0. */
        if(uip_len > 0) {
          /* 패킷의 길이가 0보다 크다면 uip_arp_out()으로 arp 패킷을 만들고
             tapdev_send()로 패킷을 보낸다 */
          uip_arp_out();
          tapdev_send();
        }
      }

#if UIP_UDP
      for(i = 0; i < UIP_UDP_CONNS; i++) {
        uip_udp_periodic(i);
        /* If the above function invocation resulted in data that
           should be sent out on the network, the global variable
           uip_len is set to a value > 0. */
        if(uip_len > 0) {
          uip_arp_out();
          tapdev_send();
        }
      }
#endif /* UIP_UDP */

      /* Call the ARP timer function every 10 seconds. */
      /* 10초 마다 ARP 타이머 함수를 호출 */
      if(++arptimer == 20) {
        uip_arp_timer();
        arptimer = 0;
      }

    } else {
      /* 패킷의 타입이 IP이면 uip_arp_ipin()함수로 IP프로토콜의 ARP처리
         uip_input()으로 패킷을 받는다. */
      if(BUF->type == htons(UIP_ETHTYPE_IP)) {
        uip_arp_ipin();
        uip_input();
        /* If the above function invocation resulted in data that
           should be sent out on the network, the global variable
           uip_len is set to a value > 0. */
        /* 패킷의 길이가 0보다 크면 ARP패킷을 보낸다 */
        if(uip_len > 0) {
          uip_arp_out();
          tapdev_send();
        }
      /* 패킷의 타입이 ARP이면 uip_arp_arpin()으로 ARP프로토콜의 ARP처리 */
      } else if(BUF->type == htons(UIP_ETHTYPE_ARP)) {
        uip_arp_arpin();
        /* If the above function invocation resulted in data that
           should be sent out on the network, the global variable
           uip_len is set to a value > 0. */
        if(uip_len > 0) {
          tapdev_send();
        }
      }
    }
  }
  return 0;
}

main() 함수에서 while(1) {}안에서 처음 부분 입니다.

    uip_len = tapdev_read();
    if(uip_len == 0) {
      for(i = 0; i < UIP_CONNS; i++) {
        /* #define uip_periodic(conn) do { uip_conn = &uip_conns[conn]; \
                                           uip_process(UIP_TIMER); } while (0)
           uip.h 
           
           UIP_CONNS 10 
           uipopt.h */
        /* 받은 패킷의 길이가 0이라면, TCP연결 루틴 처리를 10번 한다. 
           이 함수는 꼭 호출 되어야 한다. 
           어플리케이션과 TCP/IP 스택이 연결되는 가장 중요한 함수이다. */
        uip_periodic(i);
                if(uip_len > 0) {
          /* 패킷의 길이가 0보다 크다면 uip_arp_out()으로 arp 패킷을 만들고
             tapdev_send()로 패킷을 보낸다 */
          uip_arp_out();
          tapdev_send();
        }
      }

uip_len = tapdev_read();: 받은 패킷의 길이를 리턴합니다.

for(i = 0; i < UIP_UDP_CONNS; i++) {

uip_periodic(i); 패킷의 길이가 0이면 TCP연결 처리 루틴을 10번 수행합니다.

uip_arp_out(); ARP패킷을 생성합니다.

tapdev_send(); 패킷을 보냅니다.

}

수신 과정 분석

main() 함수에서 while(1) {}의 뒷 부분입니다.

    } else {
      /* 패킷의 타입이 IP이면 uip_arp_ipin()함수로 IP프로토콜의 ARP처리
         uip_input()으로 패킷을 받는다. */
      if(BUF->type == htons(UIP_ETHTYPE_IP)) {
        uip_arp_ipin();
        uip_input();
        /* 패킷의 길이가 0보다 크면 ARP패킷을 보낸다 */
        if(uip_len > 0) {
          uip_arp_out();
          tapdev_send();
        }
      /* 패킷의 타입이 ARP이면 uip_arp_arpin()으로 ARP프로토콜의 ARP처리 */
      } else if(BUF->type == htons(UIP_ETHTYPE_ARP)) {
        uip_arp_arpin();

        if(uip_len > 0) {
          tapdev_send();
        }

if(BUF->type == htons(UIP_ETHTYPE_IP)) {

uip_arp_ipin(); uip_input();

패킷의 타입이 IP이면 uip_arp_ipin()함수로 IP프로토콜을 ARP처리합니다. uip_input()으로 패킷을 받습니다.

else if(BUF->type == htons(UIP_ETHTYPE_ARP)) { uip_arp_arpin();

패킷의 타입이 ARP이면 uip_arp_arpin()으로 ARP프로토콜을 ARP처리합니다.

httpd 웹서버의 동작 과정

  1. 초기화
    tapdev_init();
    uip_init();
    httpd_init();

  2. 송수신 루틴
    uip_arp_arpin();
    uip_arp_ipin();
    uip_arp_out();
    uip_input();
    tapdev_read();
    tapdev_send();

  3. TCP/IP 처리 루틴
    uip_periodic(); -> uip_process();
    uip_process(); -> UIP_APPCALL();
    UIP_APPCALL(); -> httpd_appcall();

#define uip_periodic(conn) do { uip_conn = &uip_conns[conn]; \
                                uip_process(UIP_TIMER); } while (0)

#define UIP_APPCALL     httpd_appcall

httpd_appcall을 UIP_APPCALL로 정의했습니다. 이것은 httpd.h에 정의 되어 있습니다. httpd가 아닌 다른 어플리케이션을 연결 하고 싶다면 telnet_app등을 UIP_APPCALL로 정의합니다.

범용적인 스택이 아니기 때문에 리눅스등의 스택과는 다르게 많이 축소된 모습을 볼 수가 있습니다. 이렇게 매우 축소 되어 있기 때문에 할 수 있는 일이 제한적입니다.

MicroC/OS-II + uIP TCP/IP Stack

MicroC/OS-IIuIPAdition

발표자료

uIP_TCPIP_Stack_20031101.ppt


저작권 안내

이 웹사이트에 게시된 모든 글의 무단 복제 및 도용을 금지합니다.
  • 블로그, 게시판 등에 퍼가는 것을 금지합니다.
  • 비공개 포스트에 퍼가는 것을 금지합니다.
  • 글 내용, 그림을 발췌 및 요약하는 것을 금지합니다.
  • 링크 및 SNS 공유는 허용합니다.

Published

01 November 2003