⇒ 상대방의 응답을 기다리지 않기 때문에 TCP 에 비해 속도가 빠름
그렇기 때문에 일부 정보가 유실될 수 있으나, 이러한 상황에서도 필요 시 사용한다. (ex. 실시간 영상 스트리밍에선 일부 데이터가 유실되어 해당 픽셀만 잘 나오지 않아도 바로바로 출력하는 속도가 더 중요함)
typedef struct UdpHeader {
unsigned short srcPort;
unsigned short dstPort;
unsigned short length;
unsigned short checksum;
} UdpHeader;
Checksum
IP 데이터그램(패킷) 이나 TCP/UDP 세그먼트가 전송 중 손상되었는지 확인하기 위해 사용하는 16비트 오류 검출 코드
⇒ 실제 checksum 값과 계산식을 통해 계산한 값이 일치하는지를 확인하는 것 (일반적으로는 NIC 에서 계산을 해주긴 함)
⇒ Checksum 을 이해하고 계산할 수 있으면, TCP/UDP 패킷을 조작해서 생성(이 땐 NIC 에서 계산해주지 않음) 후 수신자에 보낼 수 있는 필수 조건은 충족하게 된다.
의사 헤더(Pseudo Header)
// IPv4 헤더의 checksum 계산
unsigned short CalcChecksumIp(IpHeader* pIpHeader)
{
// IHL 값(IP 헤더 길이) 계산
unsigned char ihl = (pIpHeader->verIhl & 0x0F) << 2; //*4와 동일
unsigned short wData[30] = { 0 };
unsigned int dwSum = 0;
// IP 헤더에서 IP 헤더 길이만큼, 즉 전체를 wData 에 복사
memcpy(wData, (BYTE*)pIpHeader, ihl);
//((IpHeader*)wData)->checksum = 0x0000;
/*
각 16bit 단위로 합산(단, checksum 필드 index 5 제외)
overflow 발생 시 carry 처리
*/
for (int i = 0; i < ihl / 2; i++) // IHL: Internet Header Length (Byte 단위)
// → IHL은 4바이트(32bit) 단위이므로, Byte 단위로 입력된 ihl을 2로 나누면 16bit 단위(WORD)의 개수가 됨.
// → 즉, IP 헤더 전체를 16비트씩 순회
{
if (i != 5)
// i == 5일 때는 IP 헤더의 checksum 필드이므로 계산에서 제외 (값이 0이어야 함)
dwSum += wData[i]; // 16비트 단위로 누적 합산
if (dwSum & 0xFFFF0000)
{
// 16비트 초과한 값(오버플로우)은 상위 비트를 잘라내고 carry를 하위에 더함 (End-around carry)
dwSum &= 0x0000FFFF; // 상위 비트 제거 (16비트만 남김)
dwSum++; // carry(1) 더함
}
}
// 최종 체크섬인 1의 보수를 반환
return ~(dwSum & 0x0000FFFF);
}
// TCP Segment 의 체크섬을 계산 (UDP 일 경우 주석 확인)
unsigned short CalcChecksumTcp(IpHeader* pIpHeader, TcpHeader* pTcpHeader)
{
// TCP 체크섬 계산을 위한 PseudoHeader 생성
PseudoHeader pseudoHeader = { 0 };
unsigned short* pwPseudoHeader = (unsigned short*)&pseudoHeader;
// UDP 일 땐 : (unsigned short*)pUdpHeader;
unsigned short* pwDatagram = (unsigned short*)pTcpHeader;
int nPseudoHeaderSize = 6; //WORD 6개 배열
int nSegmentSize = 0; //헤더 포함
UINT32 dwSum = 0;
int nLengthOfArray = 0;
// IP 헤더의 출발지/목적지 IP, 프로토콜 번호(6), TCP 세그먼트 길이를 구성
pseudoHeader.srcIp = *(unsigned int*)pIpHeader->srcIp;
pseudoHeader.dstIp = *(unsigned int*)pIpHeader->dstIp;
pseudoHeader.zero = 0;
// UDP 일 땐 : pseudoHeader.protocol = 17;
pseudoHeader.protocol = 6;
// UDP 일 땐 : pseudoHeader.length = pUdpHeader->length;
pseudoHeader.length = htons(ntohs(pIpHeader->length) - 20);
nSegmentSize = ntohs(pseudoHeader.length);
// TCP 세그먼트의 총 길이가 홀수이면 마지막 1바이트를 0-padding 하기 위해 총 WORD 개수를 1개 더 확보
if (nSegmentSize % 2)
nLengthOfArray = nSegmentSize / 2 + 1; // 홀수: 마지막 1바이트 채우기 위한 여분 확보
else
nLengthOfArray = nSegmentSize / 2; // 짝수: 그대로 WORD 개수 계산
// 1. Pseudo Header 6개 WORD 합산
for (int i = 0; i < nPseudoHeaderSize; i++)
{
dwSum += pwPseudoHeader[i]; // 16비트 단위로 합산
if (dwSum & 0xFFFF0000) // 오버플로 발생 시
{
dwSum &= 0x0000FFFF; // 상위 비트 제거
dwSum++; // 캐리 추가
}
}
// 2. TCP 헤더 + 데이터 부분 합산
for (int i = 0; i < nLengthOfArray; i++)
{
// UDP 일 땐 : if (i != 3)
if (i != 8) // TCP 헤더의 checksum 필드(9번째 WORD)는 0으로 간주하고 제외
dwSum += pwDatagram[i]; // 16비트 단위로 합산
if (dwSum & 0xFFFF0000) // 오버플로 발생 시
{
dwSum &= 0x0000FFFF; // 상위 비트 제거
dwSum++; // 캐리 추가
}
}
// 3. 1의 보수 취해서 checksum 반환
return (USHORT)~(dwSum & 0x0000FFFF);
}