- Linux에서 사용자 공간 TCP/IP 스택을 직접 만들며 네트워크 계층을 배우는 시리즈의 첫 단계로, 레이어 2의 이더넷 프레임 처리와 ARP 응답부터 구현함
- 커널의 저수준 네트워크 트래픽은 TAP 장치로 받아오며, 반환된 파일 디스크립터를 통해 가상 장치의 이더넷 버퍼를
read/write할 수 있음 - 이더넷 헤더는
dmac,smac,ethertype,payload로 다루고,ethertype값이 1536 이상이면 페이로드 타입, 그보다 작으면 페이로드 길이를 뜻함 - ARP는 IPv4 주소 같은 프로토콜 주소를 48비트 MAC 주소에 동적으로 매핑하며, 요청에 응답하면서 변환 테이블을 갱신하는 흐름으로 구현됨
arping테스트에서 커스텀 스택의 ARP 응답을 Linux 커널이 인식해tap0인터페이스의 ARP 캐시에10.0.0.4항목이 추가됨
사용자 공간 TCP/IP 스택의 출발점
- 목표는 Linux에서 최소 사용자 공간 TCP/IP 스택을 구현하면서 네트워크와 시스템 프로그래밍을 더 깊게 이해하는 것임
- TCP는 30년 넘게 여러 명세가 쌓여 복잡하지만, 핵심 구현 요소는 TCP 헤더 파싱, 상태 머신, 혼잡 제어, 재전송 타임아웃 계산으로 좁혀볼 수 있음
- 이더넷과 IP는 TCP보다 복잡도가 낮아, 시리즈는 레이어 2부터 구현을 시작함
TAP 장치로 이더넷 트래픽 받기
- Linux 커널의 저수준 네트워크 트래픽을 가로채기 위해 Linux TAP 장치를 사용함
- TUN/TAP 장치는 사용자 공간 네트워킹 애플리케이션이 각각 L3/L2 트래픽을 조작할 때 자주 쓰임
- 터널링은 패킷을 다른 패킷의 페이로드 안에 감싸는 방식임
- OpenVPN 같은 프로그램도 TUN/TAP 장치를 사용함
- 레이어 2부터 네트워크 스택을 만들기 때문에 TUN이 아니라 TAP 장치가 필요함
- TAP 장치는
/dev/net/tap을 열고ioctl(fd, TUNSETIFF, ...)로 생성함IFF_TAP은 TAP 장치를 선택함IFF_NO_PI는 이더넷 프레임 앞에 불필요한 패킷 정보가 붙지 않게 함
- 생성 후 반환된 파일 디스크립터
fd로 가상 장치의 이더넷 버퍼를 읽고 씀
이더넷 프레임 형식
- 이더넷은 LAN에서 컴퓨터를 연결하는 기반 기술이며, 첫 이더넷 표준은 1980년에 Digital Equipment Corporation, Intel, Xerox가 발표함
- 첫 버전은 약 10Mb/s 속도와 반이중 통신을 사용했기 때문에 데이터 흐름을 조정하는 MAC 프로토콜이 필요했음
- 반이중 이더넷 인터페이스에서는 CSMA/CD가 MAC 방식으로 필요함
- 100BASE-T는 twisted-pair 배선을 사용해 전이중 통신과 더 높은 처리량을 가능하게 함
- 이더넷 스위치가 널리 보급되면서 CSMA/CD의 필요성은 대체로 줄어듦
- 이더넷 표준은 IEEE 802.3 작업 그룹이 관리함
구현에 쓰는 이더넷 헤더
- 구현은 Linux의
if_ether.h를 포함해 ethertype과 16진수 값의 매핑을 사용함 - 이더넷 헤더는 C 구조체에서 다음 필드로 표현됨
dmac: 목적지 MAC 주소smac: 출발지 MAC 주소ethertype: 페이로드 길이 또는 타입payload: ARP 또는 IPv4 패킷을 담는 페이로드 포인터
ethertype은 2옥텟 필드이며 값에 따라 의미가 달라짐- 값이 1536 이상이면 IPv4, ARP 같은 페이로드 타입을 나타냄
- 값이 그보다 작으면 페이로드 길이를 나타냄
- 이더넷 프레임에는 VLAN이나 QoS를 나타내는 태그가 붙을 수 있지만, 이 구현에서는 프레임 태그를 제외함
- 페이로드 길이가 태그 없이 최소 요구 크기인 48바이트보다 작으면 끝에 패딩 바이트가 붙음
- Ethernet Frame Format 끝에는 CRC로 무결성을 확인하는 Frame Check Sequence 필드가 있지만, 이 구현에서는 처리하지 않음
이더넷 프레임 파싱 방식
- 구조체 선언의
packed속성은 GNU C 컴파일러가 데이터 정렬용 패딩 바이트로 구조체 메모리 레이아웃을 최적화하지 않게 함 - 구현의 파싱은 버퍼를 적절한 프로토콜 구조체로 타입 캐스팅하는 방식임
- 예:
struct eth_hdr *hdr = (struct eth_hdr *) buf;
- 예:
- 더 이식성 있는 방식은 프로토콜 데이터를 수동으로 직렬화하는 것임
- 이 경우 컴파일러가 프로세서별 데이터 정렬 요구에 맞춰 패딩 바이트를 추가할 수 있음
- 수신 이더넷 프레임 처리는 단순한 흐름을 따름
- TAP 장치에서 버퍼를 읽음
init_eth_hdr(buf)로 이더넷 헤더를 초기화함handle_frame(&netdev, hdr)가ethertype값을 보고 다음 동작을 결정함
ARP 패킷 구조와 역할
- ARP(Address Resolution Protocol) 는 IPv4 주소 같은 프로토콜 주소를 48비트 이더넷 주소인 MAC 주소에 동적으로 매핑함
- ARP는 IPv4에만 한정되지 않고 여러 L3 프로토콜과 함께 사용할 수 있음
- 예시로 CHAOS는 16비트 프로토콜 주소를 선언함
- 일반적인 LAN 통신에서는 서비스의 IP 주소를 알고 있어도 실제 전송에는 MAC 주소가 필요함
- ARP는 네트워크에 브로드캐스트 질의를 보내 해당 IP 주소의 소유자가 자신의 하드웨어 주소를 알리게 함
ARP 헤더와 IPv4용 페이로드
- ARP 헤더는 다음 필드로 구성됨
hwtype: 링크 계층 타입을 나타내는 2옥텟 필드이며, 이더넷의 값은0x0001protype: 프로토콜 타입을 나타내는 2옥텟 필드이며, IPv4의 값은0x0800hwsize: 하드웨어 주소 크기를 나타내는 1옥텟 필드이며, MAC 주소는 6바이트prosize: 프로토콜 주소 크기를 나타내는 1옥텟 필드이며, IPv4 주소는 4바이트opcode: ARP 메시지 타입을 나타내는 2옥텟 필드
opcode값은 네 가지로 구분됨- ARP request:
1 - ARP reply:
2 - RARP request:
3 - RARP reply:
4
- ARP request:
- IPv4용 ARP 데이터는
arp_ipv4구조체로 다룸smac: 송신자 MAC 주소sip: 송신자 IP 주소dmac: 수신자 MAC 주소dip: 수신자 IP 주소
주소 해석 알고리듬과 캐시
- RFC 826의 주소 해석 알고리듬은 하드웨어 타입과 프로토콜 타입을 확인한 뒤 변환 테이블을 갱신하고, 대상 주소가 자신이면 응답을 만드는 흐름임
- translation table은 ARP 결과를 저장해 호스트가 이미 보유한 항목을 캐시에서 조회할 수 있게 함
- 이 캐시는 중복 ARP 요청으로 네트워크가 불필요하게 채워지는 일을 줄여줌
- 구현 코드는
arp.c에 있음
ARP 응답 테스트와 다음 단계
- ARP 구현의 최종 테스트는 요청에 올바르게 응답하는지 확인하는 것임
arping -I tap0 10.0.0.4실행 결과10.0.0.4에서00:0C:29:6D:50:25MAC 주소로 유니캐스트 응답이 돌아옴- 이후
arp출력에서 Linux 커널의 ARP 캐시에10.0.0.4 ether 00:0c:29:6d:50:25 tap0항목이 생김 - 최소 이더넷 프레임 처리와 ARP 구현만으로도 커스텀 이더넷 장치가 Linux 호스트의 ARP 캐시를 채우는 결과를 확인할 수 있음
- 프로젝트 소스 코드는 GitHub에 있으며, 다음 단계는 ICMP echo/reply인 ping과 IPv4 패킷 파싱 구현임