PF_PACKET 초간단 강좌
일반적인 소켓은 TCP 또는 UDP 데이터를 다룬다. 바꾸어 말하자면 소켓을 이용해서 주고 받는 데이터는 TCP(또는 UDP) 레이어의 데이터 뿐이라는 것이다. 그 이하 레이어는 보낼때나 받을때 신경을 쓸 필요가 없는것이다. 우리가 데이터를 건데면 커널은 알아서 IP 헤더와 이더넷 헤더를 붙여서 보내주고, 받을때도 앞의 헤더들을 다 띄어내고 TCP/UDP 데이터만 건데 준다.
이러한 방식은 일반적으로 편리하지만, 때로는 TCP이하의 레이어를 건드려야만 할때가 있다. 이럴때 사용할 수 있는 것이 바로 Raw소켓이다. 하지만 Raw소켓은 IP레이어까지만 조작이 가능하다. 만약에 이더넷 레이어까지 건드리고 싶다면 어떻게 해야 할까.
가장 좋은 해결책은 pcap라이브러리를 사용하는 것이다. pcap은 다양한 플랫폼에 포팅되어있으므로 이식성 좋은 코드를 작성할 수 있다. 하지만 리눅스에서만 쓸것이라면 단연 PF_PACKET을 추천한다. PF_PACKET은 특정한 라이브러리 없이 일반적인 소켓을 사용하는 것과 유사하게 이더넷 레이어까지 조작할수있는 방법을 제공한다.
사용법은
socket(PF_PACKET, int socket_type, int protocol);
이런식으로 소켓생성시에 PF_PACKET을 명시해주면 된다. 너무 간단한가? 그것이 바로 이 PF_PACKET의 장점이다. 이렇게 생성된 소켓은 작성한 데이터를 원하는 레이어까지 그대로 전달한다. 단지 주소를 표시할때, sockaddr_ll 구조체를 사용해야하는 점이 다르다.
예를 들어 다음의 코드를 보자
struct icmp_packet
{
struct ethhdr eth;
struct iphdr ip;
struct icmphdr icmp;
char data[1472];
} __attribute__ ((packed));
int sock;
struct icmp_packet packet;
struct sockaddr_ll dest;
sock = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL);
dest.sll_family = PF_PACKET;
dest.sll_protocol = htons(ETH_P_IP);
dest.sll_ifindex = 사용하고자하는 인터페이스의 인덱스;
memcpy(dest.sll_addr, 받을 대상의 MAC,6);
// packet의 내용을 적당히 잘 채워준다 (체크섬까지 해야하는거 알죠!)
sendto(sock, &packet, sizeof(icmp_packet), 0, (struct sockaddr *) &dest, sizeof(dest));
라고한다면, 이미 이더넷 헤더와 IP헤더를 포함하고 있는 packet을 바로 네트워크로 보내버린다(따로 IP헤더를 만들지도 않고 ARP도 하지않는다).
사용법을 정리하자면,
1. PF_PACKET으로 소켓을 연다.
2. 이더넷 레이어까지 포함하는 데이터를 만든다.
3. sockaddr_ll 구조체에 받을 대상의 MAC을 넣는다.
4. send!
이렇게 할 경우 이더넷 레이어까지 사용자가 원하는대로 조작이 가능해진다. 위의 예는 PING을 쏠때, 발신자의 이더넷 주소를 조작하기 위한 코드의 일부분이다. 이런 방식을 응용한다면, 이더넷 소스 어드레스를 바꿔 보낸다던지 하는 조작이 가능하다(ARP패킷을 만들어서 보낼수도 있다).
만약에 이더넷 레이어를 포함한 패킷전체를 받고싶다면, bind를 이용하면된다. 일단 참고문서는 man packet(man 페이지 답게 그렇게 자세하지는 않다). UNP최신판에는 PF_PACKET에 대해 나와있다고 하는데, 확인해보지는 않았다.
Posted by jungjun at 2004년 11월 04일 06:07 PM | 리눅스 다이어리
TrackBack URL: http://jungjun.net/mt/mt-tb.cgi/43
PF_PACKET
2011. 2. 22. 19:22