주의 사항 원래 코드 파일인 C로는 컴파일되는 코드지만 CPP 로 하면 안되는 부분이 있습니다. GetProcAddress 와 FreeLibrary 인 곳인데, 첫번째 인수가 struct HISTANCE 형이여야 합니다. C 코드는 그냥 void 포인터로 넣어도 문제가 없는데 CPP 에서는 형검사(타입 체킹)을 하기 때문에 문제가 발생합니다.
cannot convert parameter 1 from 'void *' to 'struct HINSTANCE__ *'
(HISTANCE)를 붙여서 강제 형변환(type-casting)을 하면 해결할수 있습니다. GetProcAddress(hndlIcmp, "IcmpCreateFile"); -> GetProcAddress((HINSTANCE)hndlIcmp, "IcmpCreateFile"); FreeLibrary(hndlIcmp); -> FreeLibrary((HINSTANCE)hndlIcmp);
C 에서는 문제가 없습니다.
실행 화면
코드 내용
/*
저작권
http://www.sockaddr.com/ExampleSourceCode.html
http://cakel.tistory.com
교육용을 전제로 자유롭게 사용할수 있습니다.
PingI.cpp -- 마이크로소프트 ICMP API 를 사용한 간단한 ping 프로그램
사용법 : pingi.exe [호스트 주소]
*/
#include <windows.h>
#include <winsock.h>
#include <stdio.h>
#include <string.h>
#pragma comment(lib,"wsock32.lib")
typedef struct tagIPINFO
{ // IP 정보 태그 관련 구조체 입니다.
u_char Ttl; // Time To Live
u_char Tos; // Type Of Service
u_char IPFlags; // IP 플래그
u_char OptSize; // 옵션 데이터의 크기
u_char FAR *Options; // 옵션 데이터 버퍼
}IPINFO, *PIPINFO;
typedef struct tagICMPECHO
{ // 에코 요청 자료의 구조체
u_long Source; // 원본 주소
u_long Status; // IP 상태
u_long RTTime; // 순회 시간(Round Trip Time : RTT) ms
u_short DataSize; // 응답 데이터 크기
u_short Reserved; // 알수 없음
void FAR* pData; // 응답 데이터 버퍼
IPINFO ipInfo; // 응답 옵션
}ICMPECHO, *PICMPECHO;
// ICMP.DLL 출력 함수 포인터
HANDLE (WINAPI* pIcmpCreateFile)(VOID);
BOOL (WINAPI* pIcmpCloseHandle)(HANDLE);
DWORD (WINAPI* pIcmpSendEcho)
(HANDLE, DWORD, LPVOID, WORD, PIPINFO, LPVOID, DWORD, DWORD);
//
// main 함수 부분입니다.
int main(int argc, char* *argv)
{
WSADATA wsaData; // WinSock Applicatoin DATA
ICMPECHO icmpEcho; // Internet Control Message Protocol 에코 응답 버퍼
HANDLE hndlIcmp; // ICMP.DLL 를 다룰 LoadLibrary() 함수의 핸들
HANDLE hndlFile; // IcmpCreateFile() 함수의 핸들
LPHOSTENT pHost; // 호스트 객체 구조를 가리키는 포인터
struct in_addr iaDest; // 인터넷 주소 구조체
DWORD *dwAddress; // IP 주소
IPINFO ipInfo; // IP 옵션 구조
int nRet; // 일반적으로 응답 코드로 사용
DWORD dwRet; // DWORD(더블워드형) 응답 코드
int x;
// 인수 확인
if(argc != 2)
{
fprintf(stderr,"\n사용법: pingi [호스트 또는 IP주소]\n");
return -1;
}
// 동적으로 ICMP.DLL 을 코드 수행시 바로 불러옵니다.
hndlIcmp = LoadLibrary("ICMP.DLL");
if(hndlIcmp == NULL)
{
fprintf(stderr,"\nICMP.DLL 파일을 불러오지 못했습니다.\n");
return -1;
}
// ICMP 함수 포인터를 반환 받습니다.
pIcmpCreateFile = ( HANDLE (WINAPI*)(void) )GetProcAddress((HINSTANCE)hndlIcmp, "IcmpCreateFile");
pIcmpCloseHandle = ( BOOL (WINAPI* )(HANDLE) )GetProcAddress((HINSTANCE)hndlIcmp, "IcmpCloseHandle");
pIcmpSendEcho = (DWORD (WINAPI* )(HANDLE, DWORD, LPVOID, WORD, PIPINFO, LPVOID, DWORD, DWORD))
GetProcAddress((HINSTANCE)hndlIcmp,"IcmpSendEcho");
//모든 함수 포인터를 확인합니다.
if(pIcmpCreateFile == NULL || pIcmpCloseHandle == NULL || pIcmpSendEcho == NULL)
{
fprintf(stderr,"\nICMP 프로시져 주소를 찾는데 문제가 생겼습니다.\n");
FreeLibrary((HINSTANCE)hndlIcmp);
return -1;
}
// 윈속을 시작 합니다.
nRet = WSAStartup(0x0101, &wsaData); // 귀찮아서 MAKEWORD(1,1)을 쓰지 않고 바로 0x0101 로 WORD 형 자료를 완성합니다.
if(nRet)
{
fprintf(stderr,"\nWSAStartup() 오류 발생했습니다. 코드 번호 : %d\n", nRet);
WSACleanup();
FreeLibrary((HINSTANCE)hndlIcmp);
return -1;
}
// 윈속 버전을 확인 합니다.
if (0x0101 != wsaData.wVersion)
{
fprintf(stderr,"\n이 컴퓨터의 윈속이 버전 1.1을 지원하지 않습니다.\n");
WSACleanup();
FreeLibrary((HINSTANCE)hndlIcmp);
return -1;
}
// 목적지를 검색합니다.
// inet_addr() 함수로 호스트 명인지 호스트 주소인지 검사 합니다.
iaDest.s_addr = inet_addr(argv[1]);
if(iaDest.s_addr == INADDR_NONE) // IP주소가 아닌 경우
pHost = gethostbyname(argv[1]);
else // IP 주소인 경우
pHost = gethostbyaddr( (const char*)&iaDest, sizeof(struct in_addr), AF_INET);
if (pHost == NULL) // 목적지 호스트에 도달하지 못한 경우
{
fprintf(stderr,"\n%s 호스트를 찾을수 없었습니다.\n", argv[1]);
WSACleanup();
FreeLibrary((HINSTANCE)hndlIcmp);
return -1;
}
// 사용자에게 우리가 무얼 할지 가르쳐 줍니다.
printf("%s [%s] 호스트 로 핑(Ping)합니다.", pHost->h_name, inet_ntoa( (*(LPIN_ADDR)pHost->h_addr_list[0])) );
// IP 주소를 복사 합니다.
dwAddress = (DWORD*) (*pHost->h_addr_list);
// ICMP 에코 요청 핸들을 가집니다.
hndlFile = pIcmpCreateFile();
for(x = 0; x < 4; x++) // 4번 재시도 합니다.
{
// 그럴싸한 기본 값을 설정합니다.
ipInfo.Ttl = 255;
ipInfo.Tos = 0;
ipInfo.OptSize = 0;
ipInfo.Options = NULL;
// icmpEcho.ipInfo.Ttl = 256;
// ICMP 에코 요청을 합니다.
dwRet = pIcmpSendEcho(
hndlFile, // IcmpCreateFile() 함수에서 가저온 핸들
*dwAddress, // 목적지 IP 주소
NULL, // 보낼 버퍼를 가리키는 포인터
0, // 보낼 버퍼의 바이트 길이
&ipInfo, // 요구 옵션
&icmpEcho, // 응답 버퍼
sizeof(struct tagICMPECHO), // 에코 보낼 자료의 주소 길이
5000); // 기타릴 시간 밀리초(천분의 1초 => 5초)
// 결과를 출력합니다.
iaDest.s_addr = icmpEcho.Source;
printf("\n %s 에서 응답이 왔습니다. 시간 = %ldms TTL=%d",
inet_ntoa(iaDest),
icmpEcho.RTTime,
icmpEcho.ipInfo.Ttl);
if(icmpEcho.Status)
{
printf("\n오류 : icmpEcho.Status 상태코드 = %ld", icmpEcho.Status);
break;
}
}
printf("\n");
// 요구 했던 파일 핸들을 닫습니다.
pIcmpCloseHandle(hndlFile);
FreeLibrary((HINSTANCE)hndlIcmp);
WSACleanup();
return 0;
}
// 저작권
// http://www.sockaddr.com/ExampleSourceCode.html
// http://cakel.tistory.com
// 교육용을 전제로 자유롭게 쓰실수 있습니다.
//
// ping.h - RFC 규격에 맞게 ICMP 와 IP 헤더를 지정하는 파일입니다.
//
// packing 용량 단위 설정하는 부분입니다. alignment 를 1 로 두어서 최소 자료 전송을 노립니다.
// 대신에 접근하는 속도가 떨어지므로 좋은 방법만은 아닙니다.
#pragma pack(1)
#define ICMP_ECHOREPLAY 0
#define ICMP_ECHOREQ 8
// IP 헤더 부분 -- RFC 791
// http://www.faqs.org/rfcs/rfc791.html
// http://blog.naver.com/after1007/50014330586 참고
typedef struct tagIPHDR
{
u_char VIHL; // 버전과 IHL(Internet Header Length) 값
u_char TOS; // Type of Service (서비스의 형태)
short TotLen; // Total Length (전체 길이)
short ID; // Identification (식별자)
short FlagOff; // Fragment Offset (단편 좌표)
u_char TTL; // Time to Live (생존 시간)
u_char Protocol; // Protocol (프토토콜, 전송 규약)
u_short Checksum; // 체크섬(값 비교)
struct in_addr iaSrc; // Internet Address - Source (인터넷 주소 - 원본)
struct in_addr iaDsc; // Internet Address - Destination (인터넷 주소 - 대상)
} IPHDR, *PIPHDR;
// ICMP 헤더 -- RFC 792
// http://www.faqs.org/rfcs/rfc792.html
// http://www.microsoft.com/korea/technet/deploy/tcpintro5.asp
// http://technet2.microsoft.com/WindowsServer/ko/Library/732438fe-70c5-4e68-9663-ecbd955d29ea1042.mspx?mfr=true
// http://blog.naver.com/isnta/20024953151 참고
typedef struct tagICMPHDR
{
u_char Type; // 형식
u_char Code; // 코드
u_short Checksum; // 체크섬
u_short ID; // 식별자
u_short Seq; // 순서
char Data; // 자료(데이터)
} ICMPHDR, *PICMPHDR;
#define REQ_DATASIZE 32 // 에코(Echo) 요구 자료 길이
// ICMP Echo Request - 에코 자료 요구
typedef struct tagECHOREQUEST
{
ICMPHDR icmpHdr;
DWORD dwTime;
char cData[REQ_DATASIZE];
} ECHOREQUEST, *PECHOREQUEST;
// ICMP Echo Reply // 에코 응답
typedef struct tagECHOREPLY
{
IPHDR ipHdr;
ECHOREQUEST echoRequest;
char cFiller[256];
} ECHOREPLY, *PECHOREPLY;
#pragma pack() // packing 용량 단위 기본값으로 변경합니다.
//
// 저작권
// http://www.sockaddr.com/ExampleSourceCode.html
// http://cakel.tistory.com
// 교육용을 전제로 자유롭게 쓰실수 있습니다.
//
// ping.cpp -- ICMP 와 RAW 소켓을 사용하는 Ping 프로그램입니다.
//
#include <stdio.h>
#include <stdlib.h>
#include <winsock.h>
#include "ping.h"
#pragma comment(lib,"wsock32.lib")
// 내부 함수
void Ping(LPCSTR pstrHost);
void ReportError(LPCSTR pstrFrom);
int WaitForEchoReply(SOCKET s);
u_short in_cksum(u_short *addr, int Len);
// ICMP 에코 요구/응답 함수
int SendEchoRequest(SOCKET, LPSOCKADDR_IN);
DWORD RecvEchoReply(SOCKET, LPSOCKADDR_IN, u_char*);
// 시작
int main(int argc, char* *argv)
{
WSADATA wsaData;
WORD wVersionRequested = MAKEWORD(1,1);
int nRet;
// 인수 비교
if(argc != 2)
{
fprintf(stderr,"\n사용법 : ping [호스트명]\n");
return 1;
}
// 윈속 시작
nRet = WSAStartup(wVersionRequested, &wsaData);
if(nRet)
{
fprintf(stderr,"\n윈속을 시작하는데 오류 발생했습니다.\n");
return 1;
}
// ping 작업을 합니다.
Ping(argv[1]);
// 윈속을 비웁니다.
WSACleanup();
return 0;
}
// Ping() 함수
// SendEchoRequest() 함수와 RecvEchoReply() 함수를 수행하고
// 결과를 출력합니다.
void Ping(LPCSTR pstrHost)
{
SOCKET rawSocket;
LPHOSTENT lpHost;
struct sockaddr_in saDest;
struct sockaddr_in saSrc;
DWORD dwTimeSent;
DWORD dwElapsed;
u_char cTTL;
int nLoop;
int nRet;
// Raw 소켓을 생성합니다.
rawSocket = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
if(rawSocket == SOCKET_ERROR)
{
ReportError("socket() 함수 문제 발생");
return;
}
// 호스트를 찾아 봅니다(Lookup host)
lpHost = gethostbyname(pstrHost);
if(lpHost == NULL)
{
fprintf(stderr, "\n호스트를 찾을수가 없습니다 : %s\n", pstrHost);
return;
}
// 목적지 소켓 주소로 연결을 시도 합니다.
saDest.sin_addr.s_addr = *((u_long FAR *)(lpHost->h_addr));
saDest.sin_family = AF_INET;
saDest.sin_port = 0;
// 사용자에게 프로그램이 할려고 하는 작업을 표시 합니다.
printf("\n%s [%s] 호스트에게 %d 바이트의 자료를 핑합니다.\n",
pstrHost,
inet_ntoa(saDest.sin_addr),
REQ_DATASIZE);
// 몇번(4) 핑 작업을 합니다.
for(nLoop = 0; nLoop < 4; nLoop++)
{
// ICMP 에코 요구(Echo request)를 보냅니다.
SendEchoRequest(rawSocket, &saDest);
// select() 함수로 자료가 받을때 까지 기다립니다.
nRet = WaitForEchoReply(rawSocket);
if(nRet == SOCKET_ERROR)
{
ReportError("select() 함수에서 문제 발생");
break;
}
if(!nRet)
{
printf("\n시간 초과");
break;
}
// 응답을 받습니다.
dwTimeSent = RecvEchoReply(rawSocket, &saSrc, &cTTL);
// 소요된 시간을 계산합니다.
dwElapsed = GetTickCount() - dwTimeSent;
printf("\n%s 에서 응답을 받았습니다. 바이트=%d 시간 %ldms TTL=%d",
inet_ntoa(saSrc.sin_addr), REQ_DATASIZE,
dwElapsed, cTTL);
}
printf("\n");
nRet = closesocket(rawSocket);
if(nRet == SOCKET_ERROR)
ReportError("closesocket() 함수 수행중 오류 발생.");
}
// SendEchoRequest()
// 에고 요구 헤더 정보를 채우고 목적지로 보냅니다.
int SendEchoRequest(SOCKET s, LPSOCKADDR_IN lpstToAddr)
{
static ECHOREQUEST echoReq;
static nId = 1;
static nSeq = 1;
int nRet;
// 에코 요구 정보를 채웁니다.
echoReq.icmpHdr.Type = ICMP_ECHOREQ;
echoReq.icmpHdr.Code = 0;
echoReq.icmpHdr.Checksum = 0;
echoReq.icmpHdr.ID = nId++;
echoReq.icmpHdr.Seq = nSeq++;
// 보낼 정보를 채웁니다.
for(nRet = 0; nRet < REQ_DATASIZE; nRet++)
echoReq.cData[nRet] = ' '+nRet;
// 보낼 때 틱 횟수(tick count, 시간) 저장합니다.
echoReq.dwTime = GetTickCount();
// 보낼 자료를 패킷에다 넣고 체크섬(비교값)을 계산합니다.
echoReq.icmpHdr.Checksum = in_cksum( (u_short*)&echoReq, sizeof(ECHOREQUEST) );
// 에코 요구 신호를 보냅니다.
// sendto(소켓, 전송할 데이터, 데이터의 길이, 역할 나타내는 플래그, 원격 호스트 주소, 원격 호스트 주소의 크기)
// 소켓이 접속의 유무와 관계 없이 일방적으로 전송하는 함수 입니다.
// http://white.chungbuk.ac.kr/~jchern/sendto.html 참고
nRet = sendto(s, // 소켓
(LPSTR)&echoReq, // 버퍼
sizeof(ECHOREQUEST), // 버퍼 크기
0, // 플래그
(LPSOCKADDR)lpstToAddr, // 목적지
sizeof(SOCKADDR_IN)); // 주소 길이
if(nRet == SOCKET_ERROR)
ReportError("sendto() 함수 수행중 문제가 발생했습니다.");
return (nRet);
}
// RecvEchoReply() 함수
// 들어오는 자료를 받고 받은 항목들을 분석합니다.
DWORD RecvEchoReply(SOCKET s, LPSOCKADDR_IN lpsaFrom, u_char* pTTL)
{
ECHOREPLY echoReply;
int nRet;
int nAddrLen = sizeof(struct sockaddr_in);
// 에코 응답을 받습니다.
// recvform(소켓 식별자, 수신된 데이터를 받을 버퍼, 버퍼의 길이, 역할을 나타내는 플래그, 준 곳의 주소를
// 리턴 받기 위한 버퍼 포인터, 버퍼 사이즈를 지정한 포인터)
// 소켓으로 부터 데이터그램을 받고, 송신자의 주소와 포트를 반환하는 함수 입니다.
// http://white.chungbuk.ac.kr/~jchern/recvfrom.html 참고
nRet = recvfrom(s, // 소켓
(LPSTR)&echoReply, // 버퍼
sizeof(ECHOREPLY), // 버퍼의 크기
0, // 플래그
(LPSOCKADDR)lpsaFrom, // 받는 주소
&nAddrLen); // 주소 길의를 담는 자료를 가리키는 포인터
// 되돌아 오는 값을 검사합니다.
if(nRet == SOCKET_ERROR)
ReportError("recvfrom() 함수 수행중 문제가 발생했습니다.");
// 보낸 시간 값과 IP TTL 값을 받습니다.
*pTTL = echoReply.ipHdr.TTL;
return(echoReply.echoRequest.dwTime);
}
// ReportError() 함수
// 무슨 일 있습니까?
void ReportError(LPCSTR pWhere)
{
fprintf(stderr,"\n%s WSAGetLastError 오류 코드 번호 : %d\n", WSAGetLastError());
}
// WaitforEchoReply() 함수
// select() 함수를 사용하여 언제 자료가 읽혀 질지 결정합니다.
int WaitForEchoReply(SOCKET s)
{
struct timeval Timeout;
fd_set readfds;
readfds.fd_count = 1;
readfds.fd_array[0] = s;
Timeout.tv_sec = 5;
Timeout.tv_usec = 0;
// select(I/O 변화를 감지하는 소켓의 갯수, 읽기 상태 변화 감지할 소켓, 쓰기 상태 변화 감지할 소켓
// 예외 상태 변화 감지할 소켓, 기다리기 위한 시간)
// http://white.chungbuk.ac.kr/~jchern/select.html 참고
return( select(1, &readfds, NULL, NULL, &Timeout) );
}
// 마이크 무유스(Mike Muuss) 의 in_cksum() 함수
// 원래 핑(ping) 코드와 그의 설명입니다.
// 참고 http://myhome.shinbiro.com/~eaglelee/rawsocket.txt
//
//* 저자 -
//* 마이크 무유스(Mike Muuss)
//* 미 육군 탄도 연구소(U. S. Army Ballistic Research Laboratory)
//* 1983년 12월
/*
* I N _ C K S U M
*
*
* 인터넷 프로토콜 계열 헤더를 위한 체크섬 계산 루틴(C 버전)
*
*/
u_short in_cksum(u_short *addr, int len)
{
register int nleft = len;
register u_short *w = addr;
register u_short answer;
register int sum = 0;
/*
* 우리의 원래 알고리듬은 간단합니다. 32 비트 누산기(덧셈)를 써서,
* 우리는 16비트 단어를 순차적으로 더한 후 마지막에,
* 상위 16비트를 뒤로 접어 하위 16비트로 내립니다.
*
*/
while(nleft > 1){
sum += *w++;
nleft -= 2;
}
/* 필요하다면, 홀수 바이트 부분을 정리 합니다. */
if(nleft == 1) {
u_short u = 0;
*(u_char*)(&u) = *(u_char*)w;
sum += u;
}
/*
* 상위 16비트를 하위 16비트로 더합니다.
*/
sum = (sum >> 16) + (sum &0xffff); /* 상위 16비트를 하위 16비트로 더하기 */
sum += (sum >> 16); /* 자리 올림수를 더합니다. */
answer = ~sum; /* 16 비트로 자릅니다. */
return (answer);
}