- 책 또는 웹사이트의 내용을 복제하여 다른 곳에 게시하는 것을 금지합니다.
- 책 또는 웹사이트의 내용을 발췌, 요약하여 강의 자료, 발표 자료, 블로그 포스팅 등으로 만드는 것을 금지합니다.
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 웹서버의 동작 과정
-
초기화
tapdev_init();
uip_init();
httpd_init();
-
송수신 루틴
uip_arp_arpin();
uip_arp_ipin();
uip_arp_out();
uip_input();
tapdev_read();
tapdev_send();
-
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
발표자료
저작권 안내
이 웹사이트에 게시된 모든 글의 무단 복제 및 도용을 금지합니다.- 블로그, 게시판 등에 퍼가는 것을 금지합니다.
- 비공개 포스트에 퍼가는 것을 금지합니다.
- 글 내용, 그림을 발췌 및 요약하는 것을 금지합니다.
- 링크 및 SNS 공유는 허용합니다.