얇지만얇지않은 TCP/IP 소켓프로그래밍 C 2 판 Chap 3. Of Names and Address Families
Chap. 3 Of Names and Address Families 3.1 도메인네임주소를숫자주소로매핑하기 3.2 IP 버전에무관한주소 - 범용코드의작성 3.3 숫자주소에서도메인네임주소획득하기
기존 IPv4 전용, IPv6 전용코드의취약성 전용주소코드 (IPv4 only, IPv6 only) 의의미 IPv4 전용코드는 IPv4 형식의 IPv4 만취하고 IPv6 전용코드는 IPv6 주소형식만취한다. 상대방의 IP 주소버전을모를경우, 두가지버전을모두준비해야함 IPv4, IPv6 범용코드 실행시간에주소버전을확인하여 IPv4, IPv6 주소타입에관계없이동작하게하는코드 내부적으로는이름 - 주소변환함수인 getaddrinfo() 함수를사용하여동작 getaddrinfo() 는 /etc/hosts, DNS 시스템에질의하여가능한모든 IPv4(A record), IPv6(AAAA record) 주소를리스트화하여반환 이름주소 -> IP 주소로변환하는네임시스템 API 하나의코드로 IPv4, IPv6 에모두대비
도메인네임시스템 도메인네임시스템 (Domain Name System) 이란? 인터넷에서호스트를구분하기위하여 IP 대신네임 ( 이름주소 ) 을사용할수있도록하는서비스 네임주소 - IP 주소를매핑하는 DB 를활용하여서비스 로컬 DB 활용 : /etc/hosts(linux) or windows/system32/dirvers/etc/hosts(windows) 분산 DB 활용 : DNS(domain Name System)
장점 도메인네임시스템 읽기, 쓰기, 기억의편의성 인터넷에서호스트는 IP 주소로구분이가능 숫자형태보다, 계층화된이름주소가더좋은사용편의성을제공 고정된주소값제공 IP 주소는특성상위치이동시변경되나네임주소는이를클라이언트에숨겨주어다른사람에게항상같은주소를제공한다 부하분배 (load balancing) 하나의네임주소에여러개의 IP 주소매핑이가능하며결과적으로서로다른물리적인서버가클라이언트의요청에대응하게할수있다 특징 DNS 가 TCP/IP 프로그래밍의필수요소는아님
IPv4, IPv6 통합네임서비스 API int getaddrinfo (const char *hoststr, const char *servicestr, const struct addrinfo *hints, struct addrinfo **results) 기능 : 프로토콜버전에상관없이네임주소 -> IP 주소해석을해주는함수 호스트주소 (IP 혹은도메인네임 ) 와서비스 ( 서비스이름혹은 port 번호 ) 을전달하면위정보에연결가능한주소정보 (addrinfo) 리스트를반환한다 호스트연결시도메인네임, IPv4 주소, IPv6 주소를모두사용가능 hoststr: 네임주소혹은 IP 주소 servicestr: 서비스이름혹은 port 번호 hints: 반환을원하는주소정보의형태 IPv4 및 IPv6 선택, 프로토콜종류등의선택이가능 results: 반환되는주소들의결과리스트
IPv4, IPv6 통합네임서비스 API struct addrinfo{ int ai_flags;// 제어정보해설을위한 flag int ai_family;//family:af_inet,af_inet6,af_unspec int ai_socktype;//socket type:sock_stream,sock_dgram int ai_protocol;//protocol:0(default)or IPPROTO_XXX socklen_t ai_addrlen;// 소켓주소인 ai_addr 의길이 struct sockaddr *ai_addr;// 소켓주소 char *ai_canonname;//canonical 네임 struct addrinfo *ai_next;// 연결리스트에서다음 addrinfo 의위치 };
네임 Resolve 예제 (1/2) GetAddrInfo.c 1 //... include files here.. 7 int main(int argc, char *argv[]) { 8 9 if (argc!= 3) // 인자의개수가알맞은지확인 10 DieWithUserMessage("Parameter(s)", "<Address/Name> <Port/Service>"); 11 12 char *addrstring = argv[1]; // 서버의 IP/ 도메인네임 13 char *portstring = argv[2]; // 서버의포트 / 서비스이름 14 15 // 반환받을주소의형태를지정 16 struct addrinfo addrcriteria; // 주소형태구조체 17 memset(&addrcriteria, 0, sizeof(addrcriteria)); // 0 으로초기화 18 addrcriteria.ai_family = AF_UNSPEC; // 임의의주소버전반환 (IPv4, IPv6 모두 ) 19 addrcriteria.ai_socktype = SOCK_STREAM; // 스트림프로토콜반환요청 20 addrcriteria.ai_protocol = IPPROTO_TCP; // TCP 프로토콜반환요청 21 22 // 주어진주소 / 서비스에대한주소반환을요청 23 struct addrinfo *addrlist; // 반환받을주소가저장될리스트 24
네임 Resolve 예제 (2/2) 25 int rtnval = getaddrinfo(addrstring, portstring, &addrcriteria, &addrlist); 26 if (rtnval!= 0) 27 DieWithUserMessage("getaddrinfo() failed", gai_strerror(rtnval)); 28 29 // 반환된주소정보를출력 30 for (struct addrinfo *addr = addrlist; addr!= NULL; addr = addr->ai_next) { 31 PrintSocketAddress(addr->ai_addr, stdout); 32 fputc('\n', stdout); 33 } 34 35 freeaddrinfo(addrlist); // getaddrinfo() 에의해할당된메모리를해제 36 37 exit(0); 38 }
네임 Resolve 실행예시 <= 로컬 DB resolve <= 분산 DB(DNS) resolve <= 서비스 resolve <= IPv6 resolve <= 분산 DB(DNS) resolve, 등록된모든 IP 반환
getaddrinfo() 를활용한주소범용 주소범용 (Generic) 코드란? (Generic) 코드 주소버전 (IPv4, IPv6) 에관계없이동작하는코드 기존코드의문제점 주소구조체를지정하여사용할경우, 사용자의각기다른주소버전의입력에유연하게대처하지못함 IPv4 코드는 IPv4 주소만처리하고 IPv6 코드는 IPv6 주소만처리 해결방안 이유 : 각주소버전에맞는구조체가코드에묶임 addrinfo 를처리하는 getaddrinfo 함수를사용하여 resolving 한결과를처리 사용자주소입력 (IPv4 or IPv6 or DNS) getaddrinfo 를수행하여가용한주소전부반환 각개별주소에대하여연결혹은연결대기시도
SetupTCPClientSocket(): 서버와연결을수행하고연결된소켓을반환 //SetupTCPClientSocket() // 서버호스트의주소 (IPv4, IPv6, DNS) 및서비스이름을입력하면서버와연결된소켓을반환 int SetupTCPClientSocket(const char *host, const char *service) { struct addrinfo addrcriteria; // 반환받을주소의형태를담을구조체 memset(&addrcriteria, 0, sizeof(addrcriteria)); // 0 으로초기화 addrcriteria.ai_family = AF_UNSPEC; // IPv4 와 IPv6 모두반환요청 addrcriteria.ai_socktype = SOCK_STREAM; // 스트리밍소켓만반환요청 addrcriteria.ai_protocol = IPPROTO_TCP; // TCP 프로토콜만반환요청 } struct addrinfo *servaddr; // 서버의주소를반환받을구조구조체 int rtnval = getaddrinfo(host, service, &addrcriteria, &servaddr); if (rtnval!= 0) DieWithUserMessage("getaddrinfo() failed", gai_strerror(rtnval)); int sock = -1; for (struct addrinfo *addr = servaddr; addr!= NULL; addr = addr->ai_next) { // TCP 를이용하여안정된소켓을생성 sock = socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol); if (sock < 0) continue; // 소켓생성실패, 다음주소로시도 // 에코서버에연결시도 if (connect(sock, addr->ai_addr, addr->ai_addrlen) == 0) break; // 소켓연결성공, 반복문을탈출하고소켓을반환 close(sock); // 소켓연결실패, 다음주소로다시시도 sock = -1; } freeaddrinfo(servaddr); // getaddrinfo() 의결과로반환된메모리를회수 return sock;
SetupTCPServerSocket(): 서버의주소를획득하고 bind 및 listen 수행 (1/2) SetupTCPServerSocket() 1 static const int MAXPENDING = 5; // 최대연결대기수 2 3 int SetupTCPServerSocket(const char *service) { 4 // 서버주소구조체의생성 5 struct addrinfo addrcriteria; 6 memset(&addrcriteria, 0, sizeof(addrcriteria)); 7 addrcriteria.ai_family = AF_UNSPEC; // IPv4, IPv6 주소모두받아들임 8 addrcriteria.ai_flags = AI_PASSIVE; 9 addrcriteria.ai_socktype = SOCK_STREAM; 10 addrcriteria.ai_protocol = IPPROTO_TCP; 11 12 struct addrinfo *servaddr; 13 int rtnval = getaddrinfo(null, service, &addrcriteria, &servaddr); 14 if (rtnval!= 0) 15 DieWithUserMessage("getaddrinfo() failed", gai_strerror(rtnval)); 16 17 int servsock = -1; 18 for (struct addrinfo *addr = servaddr; addr!= NULL; addr = addr->ai_next) { 19 // TCP 소켓생성 20 servsock = socket(servaddr->ai_family, servaddr->ai_socktype, 21 servaddr->ai_protocol); 22 if (servsock < 0) 23 continue; // 소켓생성실패, 다음주소로재시도
SetupTCPServerSocket(): 서버의주소를획득하고 bind 및 listen 수행 (2/2) SetupTCPServerSocket() 25 26 if ((bind(servsock, servaddr->ai_addr, servaddr->ai_addrlen) == 0) && 27 (listen(servsock, MAXPENDING) == 0)) { 28 // 소켓의지역주소를출력 29 struct sockaddr_storage localaddr; 30 socklen_t addrsize = sizeof(localaddr); 31 if (getsockname(servsock, (struct sockaddr *) &localaddr, &addrsize) < 0) 32 DieWithSystemMessage("getsockname() failed"); 33 fputs("binding to ", stdout); 34 PrintSocketAddress((struct sockaddr *) &localaddr, stdout); 35 fputc('\n', stdout); 36 break; 37 } 38 39 close(servsock); // 소켓을종료하고다시시도 40 servsock = -1; 41 } 42 43 44 freeaddrinfo(servaddr); 45 46 return servsock; 47 }
AcceptTCPConnection(): 클라이언트의연결을처리 // AcceptTCPConnection() // 클라이언트의연결을처리하고연결된소켓을반환 1 int AcceptTCPConnection(int servsock) { 2 struct sockaddr_storage clntaddr; // 클라이언트주소 3 // 클라이언트주소구조체길이설정 ( 입출력파라미터 ) 4 socklen_t clntaddrlen = sizeof(clntaddr); 5 6 // 클라이언트의연결을대기 7 int clntsock = accept(servsock, (struct sockaddr *) &clntaddr, &clntaddrlen); 8 if (clntsock < 0) 9 DieWithSystemMessage("accept() failed"); 10 11 // 이때 clntsock 는클라이언트에연결됨 12 13 fputs("handling client ", stdout); 14 PrintSocketAddress((struct sockaddr *) &clntaddr, stdout); 15 fputc('\n', stdout); 16 17 return clntsock; 18 }
IPv4-IPv6 상호연결 상호연결조건 IPv4 전용프로그램의경우 상호종단간 IPv4 지원 IPv6 전용프로그램의경우 상호종단간 IPv6 지원 IPv4, IPv6 범용프로그램의경우 상호종단모두 IPv4 를사용하거나상호종단모두 IPv6 를사용하는경우 단, 듀얼스택시스템의경우, IPv4 와 IPv6 간연결이가능 듀얼스택 (dual stack) 시스템 IPv4 및 IPv6 를동시에지원하는시스템
과제 getaddrinfo.c 작성후실행파일만들기 (100 점 ) 관련 API 정리 프로그램에주석달기 실행파일로주요서버 ip 주소와포트확인하기 [kgu@lily ch03]$./a.out iris.mmu.ac.kr ssh 203.232.252.109-22 [kgu@lily ch03]$./a.out www.ibm.com www 129.42.56.216-80 2 장과제개선 2 프로그램수정 범용주소이용이가능하도록수정 (200 점 ) echo_cli lily.mmu.ac.kr 6000 마감 : 5 월 9 일자정