도메인 네임 시스템과 주소 패밀리 (IPv4-IPv6 서비스)
인터넷 프로토콜 03장
목차
제 3장 도메인 네임시스템과 주소 패밀리
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
기능 : 프로토콜 버전에 상관없이 네임 주소 -> IP 주소 해석을 해주는 함수
호스트 주소(IP 혹은 도메인 네임)와 서비스(서비스 이름 혹은 port 번호)을 전달하면 위 정보에 연결가능한 주소 정보(addrinfo)리스트를 반환한다
호스트 연결 시 도메인 네임, IPv4 주소, IPv6 주소를 모두 사용가능
hostStr: 네임 주소 혹은 IP 주소
serviceStr: 서비스 이름 혹은 port 번호
hints: 반환을 원하는 주소 정보의 형태 IPv4 및 IPv6 선택, 프로토콜 종류 등의 선택이 가능
results: 반환되는 주소들의 결과 리스트int getaddrinfo (const char *hostStr, const char *serviceStr,
const struct addrinfo *hints, struct addrinfo **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; // 반환 받을 주소가 저장될 리스트
네임 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반환
상세분석
ai_flags
AI_PASSIVE
AI_CANONNAME
AI_NUMERICHOST
AI_ADDRCONFIG
AI_V4MAPPED
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; // 소켓 연결 성공, 반복문을 탈출하고 소켓을 반환
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);
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
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
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