본문 바로가기

리눅스 커널/네트워크

Raw socket incoming packet capture

시스템으로 들어오는 패킷을 캡쳐하는 방식은 크게 세가지가 있다.

여기서는 유저레벨에서 할 수 있는 raw socket을 이용하여 패킷을 스니핑 해보겠다.

 

raw socket에 대한 개념은 따로 언급하지 않고 사용 방법에 대해서만 정리해보겠다.

 

먼저 네트워크 계층 까지만 다룰 수 있는 raw socket과 데이터 링크 계층까지 전부 다룰 수 있는 raw socket 

이렇게 두가지 가 있다는 것을 이해하고 아래 코드를 살펴보겠다.

#include <stdio.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include "pktfilter.c" //패킷을 뜯어서 터미널에 출력하는 함수가 정의된 파일.

int main(void)
{
	int sock;
	unsigned char buf[65536];

	//domain, type, protocol
	sock = socket(PF_INET, SOCK_RAW, IPPROTO_TCP);
	if (sock < 0)
	{
		perror("socket error");
		return -1;
	}

	while (1)
	{
		int rcv_bytes = recvfrom(sock, buf, sizeof(buf), 0, NULL, NULL);
	
		if (rcv_bytes < 0)
			break;

		ip_handle((struct iphdr *)buf);	//ip헤더부터 확인하면 된다.
	}

	close(sock);
	return 0;
}

socket()을 호출 할 때,

domain은 Ipv4 인터넷 프로토콜로, 

type은 SOCK_RAW로, raw network 프로토콜 접근을 제공한다.

protocoo은 TCP로 TCP만 스니핑 하겠다는 것이다.

 

또한 위 코드를 컴파일 한 후 관리자 권한으로 실행해야 소켓을 온전히 열 수 있다.

#include <stdio.h>
#include <sys/socket.h>
#include <linux/if_packet.h>
#include <net/ethernet.h>
#include <arpa/inet.h>
#include <unistd.h>
#include "pktfilter.c" //패킷을 뜯어서 터미널에 출력하는 함수가 정의된 파일.

int main(void)
{
	int sock;
	unsigned char buf[65536];	

	sock = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
	if (sock < 0)
	{
		perror("socket error");
		return -1;
	}

	while (1)
	{
		int type;
		int rcv_bytes = recvfrom(sock, buf, sizeof(buf), 0, NULL, NULL);
	
		if (rcv_bytes < 0)
			break;
		
		eth_handle((struct ethhdr *)buf); //이더넷 헤더부터 확인해야 한다.
	}

	close(sock);
	return 0;
}

위 코드와 앞선 코드의 차이점은 단 하나 소켓을 열 때 이다.

domain은 PF_PACKET으로 저수준의 패킷 인터페이스를 제공한다. 더 자세한 내용은 man packet을 참조하면 된다.

type은 똑같고,

protocol은 모든 것에 대해서이며, 여기서는 endian을  한 차례 변경해주어야 한다. 이는 이더넷 헤더에서 프로토콜 관련 필드가 2Bytes이기 때문이다.

 

끝으로 pktfilter.c 에 구현에 대한 내용이다.

#include <stdio.h>
#include <arpa/inet.h>
#include <linux/tcp.h>
#include <linux/udp.h>
#include <linux/ip.h>
#include <linux/if_ether.h>
#include <linux/if_arp.h>

struct ether_addr;
extern char * ether_ntoa(struct ether_addr *);

static void tcp_handle(struct tcphdr * tcp)
{
	printf("-----TCP-----\n");
	printf("%d -> %d\n", ntohs(tcp->source), ntohs(tcp->dest));
}

static void udp_handle(struct udphdr * udp)
{
	printf("-----UDP-----\n");
	printf("%d -> %d\n", ntohs(udp->source), ntohs(udp->dest));
	printf("data length: %d\n", ntohs(udp->len));
}

static void ip_handle(struct iphdr * ip)
{
	printf("-----NETWORK------\n");
	printf("%s -> ", inet_ntoa(*(struct in_addr *)&ip->saddr));
	printf("%s\n", inet_ntoa(*(struct in_addr *)&ip->daddr));
	printf("total length: %d\n", ntohs(ip->tot_len));

	switch (ip->protocol)
	{
	case IPPROTO_TCP:
		tcp_handle((struct tcphdr *)(ip + 1));
		break;
	case IPPROTO_UDP:
		udp_handle((struct udphdr *)(ip + 1));
		break;
	//more protocol...
	}
}

static void arp_handle(struct arphdr * arp)
{
	unsigned char * ptr = (unsigned char *)(arp + 1);
	printf("-----ARP-----\n");
	printf("%s\n", ntohs(arp->ar_op) == ARPOP_REQUEST ? "Request" : "Reply");
	printf("shw: %s, sip: %s\n", ether_ntoa((struct ether_addr *)(ptr)),
					inet_ntoa(*(struct in_addr *)(ptr + 6)));
	printf("thw: %s, tip: %s\n", ether_ntoa((struct ether_addr *)(ptr + 6 + 4)),
					inet_ntoa(*(struct in_addr *)(ptr + 6 + 4 + 6)));
}

static void eth_handle(struct ethhdr * eth)
{
	printf("-----DATA-LINK-----\n");
	printf("%s -> ", ether_ntoa((struct ether_addr *)eth->h_source));
	printf("%s\n", ether_ntoa((struct ether_addr *)eth->h_dest));

	switch (ntohs(eth->h_proto))
	{
	case ETH_P_ARP:
		arp_handle((struct arphdr *)(eth + 1));
		break;
	case ETH_P_IP:
		ip_handle((struct iphdr *)(eth + 1));
		break;
	//more protocol...
	}
}

ether_ntoa()를 사용하려면 보통은 netinet/ether.h를 포함하면 되지만, 포함 했 을 시

패킷 헤더에 대한 정의를 담고 있는 헤더파일과 구조체 재정의로 인한 충돌로 인해 컴파일 에러가 발생해서 필요한 부분만 따로 작성하였다.

 

또 arp_handle()에서 arp op는 여러가지가 있지만 대표적으로 자주 사용되는 것이 주로 request, reply여서 간단하게 작성하였다.

 

'리눅스 커널 > 네트워크' 카테고리의 다른 글

Kernel hooking packet capture  (0) 2021.10.24
Raw socket outgoing packet capture  (0) 2021.10.24
Ethernet & ARP Header  (0) 2021.10.24
IP & ICMP Header  (0) 2021.10.24
TCP & UDP Header  (0) 2021.10.24