본문 바로가기

Programming Language/C&C++

소켓 통신 #3 - ICMP 클라이언트 구현하기(3)

336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.

소켓 통신 #3 - ICMP 클라이언트 구현하기(3)

- by Tapito


 2편에서 ICMP 패킷을 sendto로 보낼 때는 IP 헤더를 소켓에서 알아서 작성하도록 지정했습니다. 이번에는 IP 헤더를 직접 구성하여 패킷을 보내보도록 하겠습니다. IP 헤더를 직접 작성하기 위해서는 setsockopt 함수에서 IP_HDRINCL 속성을 지정해야 합니다.

/* 예제 */
#pragma comment(lib, "ws2_32.lib")

#include <winsock2.h>
#include <ws2tcpip.h>
#include <windows.h>

#include <stdio.h>
#include <stdlib.h>
#include <conio.h>

#pragma pack(push, 1)
typedef struct _tagIPHDR4
{
	BYTE bVersionIHL; // Version and IHL
	BYTE bTypeOfService; // Type Of Service
	WORD wLength; // Total Packet Length
	WORD wIdentification; // Fragment Identification
	WORD wFlagOffset; // Flags and Fragment Offset
	BYTE bTimeToLive; // Time To Live
	BYTE bProtocol; // Protocol
	WORD wChecksum;		// Checksum
	struct in_addr addressSource; // Internet Address - Source
	struct in_addr addressDestination; // Internet Address - Destination
} IPHDR4, NEAR * PIPHDR4, FAR * LPIPHDR4;

typedef struct _tagICMPHDR4
{
	BYTE bType; // Type
	BYTE bCode; // Code
	WORD wChecksum; // Checksum
	WORD wIdentification; // Identification
	WORD wSequence; // Sequence
} ICMPHDR4, NEAR * PICMPHDR4, FAR * LPICMPHDR4;
#pragma pack(pop)

WORD GetChecksumWord(LPBYTE lpByte, SIZE_T nByte)
{
	SIZE_T tByte;
	DWORD dwTemp = 0;
	WORD wReturn;

	switch (nByte % 2)
	{
	case 0: // 짝수 바이트면
		for (tByte = 0; tByte < nByte; tByte += 2)
		{
			dwTemp += MAKEWORD(lpByte[tByte], lpByte[tByte + 1]);
		}
		dwTemp = LOWORD(dwTemp) + HIWORD(dwTemp);
		dwTemp = LOWORD(dwTemp) + HIWORD(dwTemp);
		wReturn = (WORD)((~dwTemp) & 0xFFFF);
		break;
	case 1: // 홀수 바이트면
		for (tByte = 0; tByte < nByte - 1; tByte += 2)
		{
			dwTemp += MAKEWORD(lpByte[tByte], lpByte[tByte + 1]);
		}
		dwTemp = lpByte[nByte - 1];
		dwTemp = LOWORD(dwTemp) + HIWORD(dwTemp);
		dwTemp = LOWORD(dwTemp) + HIWORD(dwTemp);
		wReturn = (WORD)((~dwTemp) & 0xFFFF);
		break;
	default:
		wReturn = 0;
	}

	return wReturn;
}

int main(int argc, char * argv[])
{
	/* WinSock */
	WSADATA wsaData;
	SOCKET ipSocket;

	/* 출발지와 목적지 주소 */
	SOCKADDR_IN ipSourceAddress;
	SOCKADDR_IN ipDestinationAddress;

	/* select 함수를 사용하기 위한 소켓 목록과 제한 시간 */
	fd_set fdset;
	TIMEVAL timeValue;

	/* 보내는 패킷와 받은 패킷이 저장될 구조체 */
	struct { IPHDR4 ipHeader; ICMPHDR4 icmpHeader; } icmpRequest;
	struct { IPHDR4 ipHeader; ICMPHDR4 icmpHeader; } icmpResponse;
	
	/* 소켓 함수의 실행 결과 */
	int iReturn = 0;

	/* recvfrom 함수를 사용하기 위한 변수 선언: 패킷의 발신지 주소와 그 크기가 저장될 곳 */
	SOCKADDR_IN ipSocketAddress;
	int nSocketAddress = sizeof(ipSocketAddress);

	/* setsockopt를 사용하기 위한 변수 */
	DWORD sockopt = 1;

	/* WinSock 초기화 */
	WSAStartup(MAKEWORD(1, 1), &wsaData);
	ipSocket = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);

	/* 패킷을 보낼 때 IP 헤더까지 직접 편집할 것임을 지정합니다. */
	setsockopt(ipSocket, IPPROTO_IP, IP_HDRINCL, (const char *)&sockopt, sizeof(sockopt));

	/* 자신의 IP 주소 */
	ZeroMemory(&ipSourceAddress, sizeof(ipSourceAddress));
	ipSourceAddress.sin_family = AF_INET;
	ipSourceAddress.sin_port = 0;
	ipSourceAddress.sin_addr.s_addr = inet_addr("192.168.0.3");

	/* 패킷을 보낼 대상 */
	ZeroMemory(&ipDestinationAddress, sizeof(ipDestinationAddress));
	ipDestinationAddress.sin_family = AF_INET; // 인터넷 망에 접속
	ipDestinationAddress.sin_port = 0; // 포트 번호: 0번
	ipDestinationAddress.sin_addr.s_addr = inet_addr("173.194.127.212"); // 구글 www.google.com의 IP 주소

	/* 보낼 패킷 구성 */
	ZeroMemory(&icmpRequest, sizeof(icmpRequest));
	icmpRequest.icmpHeader.bType = 8;
	icmpRequest.icmpHeader.bCode = 0;
	icmpRequest.icmpHeader.wChecksum = 0;
	icmpRequest.icmpHeader.wIdentification = htons(1);
	icmpRequest.icmpHeader.wSequence = htons(1);

	icmpRequest.icmpHeader.wChecksum = GetChecksumWord((LPBYTE)&icmpRequest, sizeof(icmpRequest));

	icmpRequest.ipHeader.bVersionIHL = 0x45;
	icmpRequest.ipHeader.bTypeOfService = 0x00;
	icmpRequest.ipHeader.wLength = sizeof(icmpRequest);
	icmpRequest.ipHeader.wIdentification = 1;
	icmpRequest.ipHeader.wFlagOffset = 0x00;
	icmpRequest.ipHeader.bTimeToLive = 128;
	icmpRequest.ipHeader.bProtocol = 1; // IP 헤더 이후 ICMP 헤더가 붙음을 명시
	icmpRequest.ipHeader.wChecksum = 0;
	icmpRequest.ipHeader.addressSource = ipSourceAddress.sin_addr;
	icmpRequest.ipHeader.addressDestination = ipDestinationAddress.sin_addr;

	icmpRequest.ipHeader.wChecksum = GetChecksumWord(&icmpRequest, sizeof(icmpRequest));

	/* 패킷 보내기 */
	iReturn = sendto(ipSocket, (LPSTR)&icmpRequest, sizeof(icmpRequest), 0, (SOCKADDR *)&ipDestinationAddress, sizeof(struct sockaddr_in));
	printf("sendto = %d\n", iReturn);

	/* 에코 수신 대기 */
	FD_ZERO(&fdset);
	FD_SET(ipSocket, &fdset);
	timeValue.tv_sec = 1;
	timeValue.tv_usec = 500;
	iReturn = select(1, &fdset, NULL, NULL, &timeValue); // -1: 오류 발생, 0: 타임아웃, 양수: 송수신 가능한 소켓의 개수
	printf("select = %d\n", iReturn);

	/* 에코 수신 */
	iReturn = recvfrom(ipSocket, (LPSTR)&icmpResponse, sizeof(icmpResponse), 0, (SOCKADDR *)&ipSocketAddress, &nSocketAddress); // 받기
	printf("recvfrom = %d TTL = %d\n", iReturn, icmpRequest.ipHeader.bTimeToLive);

	closesocket(ipSocket);

	WSACleanup();
	return 0;
}

 실행 결과는 다음과 같습니다.