TCP IP 파이썬 활용

원시 소켓 방식에 따른 헤더의 생성

웹하는빡통 2020. 7. 7. 19:46

이번 시간에는 원시 소켓 방식에 따른 헤더의 생성에 대하여 배워보겠다.

지난 시간에는  socket 모듈을 이용해 소켓 객체를 생성해 보고 서버/클라이언트 모델을 간단하게 구현하였다.

그런데 이러한 소켓 활용은 모두 표준 소켓 방식에 기반한 작업이다.

 

표준 소켓 방식은 운영체제에서 TCP/IP 계층별 데이터 전송 단위와 헤더 구조 등을 자동으로 처리하기 때문에 사용자는

이러한 일련의 과정을 고려할 필요가 없다. 그러나 표준 소켓 방식은 이미 정해진 방식에 따라 소켓을 생성하기 때문에 소켓 활용에 대한 유연성은 없다. 다시 말해, 새로운 프로토콜을 개발하거나 패킷 스니퍼 등과 같은 정교한 응용 도구를 구현하는 경우에는 표준 소켓 방식이 아닌 원시 소켓 방식에 기반해야 한다. 

 

원시 소켓을 사용하기 위해서는 몇 가지 선행 지식이 필요하다. 

 

위 사진과 같이 s1 방식은 표준 소켓 방식에 따른 소켓 객체 생성이고, s2 방식은 원시 소켓 방식에 따른 소켓 객체 생성이다. s1 방식과 s2 방식을 자세히 비교하면 두 번째 매개 변수가 다르다. 또한 원시 소켓 방식에서는 표준 소켓 방식과 달리 세 번째 매개 변수를 생략할 수 없다.

 

다음 사진은 TCP 방식의 원시 소켓 생성에 대한 일례이다. 

 

다음으로 setsockopt() 함수의 기능 변화다. setsockopt() 함수는 소켓 객체를 종료하자마자 해당 포트 번호를 재사용하도록 허용하겠다는 용도로 사용했다. 그러나 원시 소켓 방식에서는 사용자가 헤더에 접근하도록 허용하겠다는 용도로 사용할 수 있다. 

 

소켓 객체 s1에서 사용한 setsockopt() 함수의 기능은 소켓 객체를 종료하자마자 해당 포트 번호를 재사용하도록 허용하겠다는 의미다.(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

 

소켓 객체 s2에서 사용한 setsockopt() 함수의 기능은 UDP 헤더뿐만 아니라, IP 헤더까지 사용자가 직접 생성하도록 허용하겠다는 의미다. (socket.IPPROTO_IP, socket.IP_HDRINCL, 1). 다시 말해, 사용자가 직접 UDP 헤더와 IP 헤더를 생성하겠다는 뜻이다. 그렇다면 소켓 객체 s3 의미는 자연스럽게 이해할 수 있다. 

 

소켓 객체 s3은 사용자가 ICMP 헤더와 IP 헤더를 직접 생성하겠다는 의미다. 

 

이와 같이 소켓 객체 s2와 s3같은 생성 방식을 상위 계층 기반의 원시 소켓 생성이라고 지칭하겠다. 상위 계층 기반의 원시 소켓 생성은 일반적으로 송신을 구현하는 방식이다. 

 

한편, 이더넷 프레임까지 확장해 원시 소켓을 생성하는 경우 하위 계층 기반의 원시 소켓 생성이라고 부르겠다. 

하위 계층 기반의 원시 소켓은 일반적으로 수신을 구현하는 방식이다. 다시 말해, 데이터 링크 계층에 기반해 들어오는 데이터를 상위 계층별로 복원할 경우에는 하위 계층 기반의 원시 소켓 생성을 사용한다. 

 

일례로 유닉스/리눅스에서 하위 계층 기반의 RAW 소켓은 아래 사진과 같다.

첫 번째 매개 변수로 AF_INET이 아닌 AF_PACKET을 설정하고(유닉스/리눅스), 세 번째 매개 변수로 htons(0x0800)을 설정한다. 여기서 0x0800은 IP 패킷을 의미한다. 하위 계층 기반의 로우 소켓을 생성하기 위해서는 LAN 카드의 속성까지 고려해야 한다. 

 

나중에 더 자세히 상위 계층 기반의 원시 소켓 생성 방식과 하위 계층 기반의 원시 소켓 생성 방식의 차이를 확인해 보자.  

 

먼저 사용자가 위와 같이 설정했다고 하자. 사용자가 8바이트 크기의 UDP 헤더를 직접 구현하겠다는 의미다. 그럼 지금 부터 UDP 헤더의 항목 크기에 따라 각각의 변수를 설정해 UDP 헤더를 작성해 보자.  

 

H 형식 문자열 크기는 2바이트이기 때문에 HHHH 크기는 2+2+2+2=8바이트이며, 이것은 8바이트 크기의 UDP 헤더에 해당한다.  

 

다음으로 사용자가 아래와 같이 설정했다고 하자. 

위 사진과 같이 사용자가 20바이트 크기의 TCP 헤더를 직접 구현하겠다는 의미다. 이제 TCP 헤더의 항목 크기에 따라 각각의 변수를 설정해 TCP 헤더를 작성해 보자. 

 

42번째 줄에서 H 형식 문자열 크기는 2바이트이고 L 형식 문자열 크기는 4바이트이고 B 형식 문자열 크기는 1바이트이기 때문에 HHLLBBHHH 크기는 2+2+4+4+1+1+2+2+2= 20바이트이며, 이것은 20바이트 크기의 TCP 헤더에 해당한다. 

 

그런데 TCP 헤더에서 오류 검사 항목이 활성 상태인 경우에는 12바이트 크기의 가상 헤더를 추가적으로 결합하는데

표준 소켓 방식과 달리 원시 소켓 방식에서는 오류 검사 항목이 활성 상태인 경우라면 가상 헤더까지 직접 작성해야 한다. 따라서 TCP 헤더에서 오류 검사 항목이 활성 상태인 경우 아래와 같이 가상 헤더를 구현하면 된다.

 

s형식 문자열 크기는 1바이트이고, B형식 문자열 크기는 1바이트이고, H형식 문자열 크기는 2바이트이기 때문에 4s4sBBH 크기는 4+4+1+1+2 = 12바이트이며, 이것은 12바이트 크기의 가상 헤더에 해당한다. 

 

다음으로 사용자가 아래 사진과 같이 설정했다고 하자. 

위 사진과 같이 사용자가 TCP 헤더에 이어 20바이트 크기의 IP 헤더까지 직접 구현하겠다는 의미이다.(socket.IPPROTO_IP, socket.IP_HDRINCL, 1). 이와 같이 IP 헤더의 항목 크기에 따라 각각의 변수를 설정해 IP 헤더를 작성하면 아래와 같다. 

 

B형식 문자열 크기는 1바이트이고, H 형식 문자열 크기는 2바이트이고, s 형식 문자열 크기는 1바이트이기 때문에 BBHHHBBH4s4s 크기는 1+1+2+2+2+1+1+2+4+4 = 20바이트이며, 이것은 20바이트 크기의 IP 헤더에 해당한다. 

 

끝으로 사용자가 아래와 같이 설정했다고 하자.

사용자가 8바이트 크기의 ICMP 헤더를 직접 구현하겠다는 의미다. 위와 같이 ICMP 헤더의 항목 크기에 따라 각각의 변수를 설정해 ICMP 헤더를 작성하면 아래와 같다. 

ICMP 헤더에 대응하는 형식 문자열에 따라 ICMP 헤더를 완성(이때 pack() 함수는 10진수를 16진수로 변경하는 기능 수행) B 형식 문자열 크기는 1바이트이고, H 형식 문자열 크기는 2바이트 이기 때문에 BBHHH 크기는 1+1+2+2+2=8바이트이며, 이것은 8바이트 크기의 ICMP 헤더에 해당한다.