기존에 서버 소켓을 바인딩 할 때 “같은 IP 와 PORT” 로 는 바인딩이 불가능했으나, 서버 소켓에 SO_REUSEADDR 옵션을 사용할 경우 기존에 Bind 한 소켓과 “같은 IP 와 PORT” 로 소켓에 바인드하고 listen 시킬 수 있다.
⇒ SO_REUSEADDR 옵션을 사용해 서버를 여러 개 listen 시켰을 때, 클라이언트를 실행하면 어떤 서버로 connect 될까??
⇒ 클라이언트는 기본적으로 1번째 서버에 connect 되고,
⇒ 1번째 서버가 종료되었을 때, 이후 클라이언트의 connect 는 미리 SO_REUSEADDR 옵션으로 LISTEN 한 서버 소켓에 붙게 된다 !!
⇒ 즉, SO_REUSEADDR 서버 소켓 옵션은 서버가 비정상 종료될 때를 대비해, 다시 빠르게 동일한 IP/PORT 로 바인드하여 서버가 재시작해야할 때 사용한다.
서버에 NIC(네트워크 인터페이스 카드) 가 여러 개일 경우 IP 가 여러 개이므로, 악성 코드가 SO_REUSEADDR 옵션을 악용하여 두 번째 서버를 open, 첫 번째 서버에 해를 가하고, 악성 코드가 의도한 두 번째 서버로 통신이 이뤄진다.
⇒ 서버 소켓에 주소를 특정해서 사용할 것 !
code (server)
#include "stdafx.h"
#include <winsock2.h>
#pragma comment(lib, "ws2_32")
int _tmain(int argc, _TCHAR* argv[]) {
WSADATA wsa = { 0 };
if (::WSAStartup(MAKEWORD(2, 2), &wsa) != 0) {
puts("ERROR: winsock 을 초기화할 수 없습니다.");
return 0;
}
SOCKET hSocket = ::socket(AF_INET, SOCK_STREAM, 0);
if (hSocket == INVALID_SOCKET) {
puts("ERROR: 접속 대기 소켓을 생성할 수 없습니다.");
return 0;
}
// * 바인딩 전에 IP 주소와 포트를 재사용하도록 소켓 옵션을 변경
BOOL bOption = TRUE;
if (::setsockopt(hSocket, SOL_SOCKET, SO_REUSEADDR, (char*)bOption, sizeof(BOOL)) == SOCKET_ERROR) {
puts("ERROR: 소켓 옵션을 변경할 수 없습니다.");
return 0;
}
// 바인딩
SOCKADDR_IN svraddr = { 0 };
svraddr.sin_family = AF_INET;
svraddr.sin_port = htons(25000);
svraddr.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
svraddr.sin_addr.S_un.S_addr = inet_addr("192.168.2.190");
if (::bind(hSocket, (SOCKADDR*)&svraddr, sizeof(svraddr)) == SOCKET_ERROR) {
puts("ERROR: 소켓에 ip 주소와 포트를 바인드 할 수 없습니다.");
return 0;
}
if (::listen(hSocket, SOMAXCONN) == SOCKET_ERROR) {
puts("ERROR: 리슨 상태로 전환할 수 없습니다.");
return 0;
}
puts("Echo 서버를 시작합니다.");
SOCKADDR_IN clientaddr = { 0 };
int nAddrLen = sizeof(clientaddr);
SOCKET hClient = 0;
char szBuffer[128] = { 0 };
int nReceive = 0;
while ((hClient = ::accept(hSocket, (SOCKADDR*)&clientaddr, &nAddrLen)) != INVALID_SOCKET) {
puts("새 클라이언트가 연결되었습니다.");
while ((nReceive = ::recv(hClient, szBuffer, sizeof(szBuffer), 0)) > 0) {
::send(hClient, szBuffer, sizeof(szBuffer), 0);
puts(szBuffer);
memset(szBuffer, 0, sizeof(szBuffer));
}
::closesocket(hClient);
puts("클라이언트 연결이 끊겼습니다.");
}
::closesocket(hSocket);
::WSACleanup();
return 0;
}