반응형

 

2024년 4월 2일

 

 

일단, 메모만 해놓고 나중에 자세히 스터디하기~

 

 

https://www.ntop.org/products/packet-capture/pf_ring/

 

PF_RING

 

 

 

 

PF_RING Modules

 

 

 

 

https://www.ntop.org/products/packet-capture/pf_ring/pf_ring-zc-zero-copy/

 

 

 

Zero Copy Operations to KVM VM instances

 

 

 

 

 

 


 

 

반응형

 


 

작성일: 2023년 10월 10일

 

패킷 보내기 (Send a packet)

예제 코드

/**
 * How to build
 *  $  gcc send-raw-packet.c -o send-raw-packet
 *
 * How to run
 *  $ ./send-raw-packet
 *    or
 *  $ ./send-raw-packet  eth0
 */

#include <arpa/inet.h>
#include <linux/if_packet.h>
#include <linux/ip.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <net/if.h>
#include <netinet/ether.h>
#include <errno.h>


// FIXME: MY_DEST_MACX 값은 각자 테스트 환경이 맞게 수정해서 사용.
#define MY_DEST_MAC0	0x52
#define MY_DEST_MAC1	0x54
#define MY_DEST_MAC2	0x00
#define MY_DEST_MAC3	0xcf
#define MY_DEST_MAC4	0xab
#define MY_DEST_MAC5	0x76

// FIXME: DEFAULT_IF 값은 각자 테스트 환경이 맞게 수정해서 사용.
#define DEFAULT_IF	"enp7s0"

#define BUF_SIZ		1024


int main(int argc, char *argv[])
{
	int                  sockfd;
	int                  tx_len = 0;
	char                 sendbuf[BUF_SIZ];
	char                 ifName[IFNAMSIZ];
	struct ifreq         if_idx;
	struct ifreq         if_mac;
	struct ether_header  *eh = (struct ether_header *) sendbuf;
	struct iphdr         *iph = (struct iphdr *) (sendbuf + sizeof(struct ether_header));
	struct sockaddr_ll   socket_address;

	// Network interface name 지정하기 (예: eth0)
	if (argc > 1)
	{
		strcpy(ifName, argv[1]);
	}
	else
	{
		strcpy(ifName, DEFAULT_IF);
	}

	// RAW socket 사용을 위한 File descriptor 생성하기
	if ((sockfd = socket(AF_PACKET, SOCK_RAW, IPPROTO_RAW)) == -1)
	{
		printf("socket() error: %d (%s)\n", errno, strerror(errno));
		return 0;
	}

	// Network interface의 index 값 구하기
	memset(&if_idx, 0, sizeof(struct ifreq));
	strncpy(if_idx.ifr_name, ifName, IFNAMSIZ-1);
	if (ioctl(sockfd, SIOCGIFINDEX, &if_idx) < 0)
	{
		printf("ioctl(SIOCGIFINDEX, %s) error: %d (%s)\n", ifName, errno, strerror(errno));
		return 0;
	}

	// Network interface의 MAC address 구하기
	memset(&if_mac, 0, sizeof(struct ifreq));
	strncpy(if_mac.ifr_name, ifName, IFNAMSIZ-1);
	if (ioctl(sockfd, SIOCGIFHWADDR, &if_mac) < 0)
	{
		printf("ioctl(SIOCGIFHWADDR, %s) error: %d (%s)\n", ifName, errno, strerror(errno));
		return 0;
	}

	// Ehternet header 구성하기 (참고: sendbuf pointer가 eh 주소를 pointing)
	memset(sendbuf, 0, BUF_SIZ);
	/*
	 * ioctl() 함수를 이용해서 얻은 'enp7s0' NIC에 대한 MAC Address 값을
	 * ethernet header 구조체의 ether_shost 변수에 복사한다.
	 */
	printf("( %s )  MAC address = ", ifName);
	for (int idx = 0; idx < 6; idx++)
	{
		eh->ether_shost[idx] = ((uint8_t *)&if_mac.ifr_hwaddr.sa_data)[idx];
		printf("%02x", eh->ether_shost[idx]);
		if (idx < 5)
		{
			printf(":");
		}
	}
	printf("\n");

	/*
	 * 일반적으로 NIC port의 MAC address를 ethernet frame의 source address로 사용하지만
	 * Ethernet packet 전송 테스트를 위해서 가짜 Source MAC address를 만들었다.
	 */
	eh->ether_shost[3] = 0x01;
	eh->ether_shost[4] = 0x02;
	eh->ether_shost[5] = 0x03;

	// Ethernet frame - Destination host MAC address
	eh->ether_dhost[0] = MY_DEST_MAC0;
	eh->ether_dhost[1] = MY_DEST_MAC1;
	eh->ether_dhost[2] = MY_DEST_MAC2;
	eh->ether_dhost[3] = MY_DEST_MAC3;
	eh->ether_dhost[4] = MY_DEST_MAC4;
	eh->ether_dhost[5] = MY_DEST_MAC5;

	// Ethertype:  Internet Protocol (0x0800)
	eh->ether_type = htons(ETH_P_IP);
	tx_len += sizeof(struct ether_header);

	// FIXME: IP Header
	//   각자 테스트 환경에 맞게 iph 변수를 수정하여 사용하기
  	iph->ihl = 20 >> 2;  // NOTE: (20 >> 2) * 4 = 20 bytes (IHL은 4 byte 단위로 해석되기 때문)
	iph->version = 4;
	iph->protocol = IPPROTO_IP;
	iph->saddr = inet_addr("10.1.1.10");
	iph->daddr = inet_addr("10.1.1.11");
	iph->tot_len = 46 + 32;

	tx_len += sizeof(struct iphdr);

	/* Payload (Packet data) */
	for (char idx = 0; idx < 32; idx++)
	{
		sendbuf[tx_len++] = idx;
	}

	/* Index of the network device */
	socket_address.sll_ifindex = if_idx.ifr_ifindex;
	/* Address length*/
	socket_address.sll_halen = ETH_ALEN;
	/* Destination MAC */
	socket_address.sll_addr[0] = MY_DEST_MAC0;
	socket_address.sll_addr[1] = MY_DEST_MAC1;
	socket_address.sll_addr[2] = MY_DEST_MAC2;
	socket_address.sll_addr[3] = MY_DEST_MAC3;
	socket_address.sll_addr[4] = MY_DEST_MAC4;
	socket_address.sll_addr[5] = MY_DEST_MAC5;

	/* Send packet */
	if (sendto(sockfd, sendbuf, tx_len, 0, (struct sockaddr*)&socket_address, sizeof(struct sockaddr_ll)) < 0)
	    printf("Send failed\n");

	return 0;
}

 

 

패킷 받기 (Receive a packet)

예제 코드

/**
 * How to build
 *  $  gcc recv-raw-packet.c -o recv-raw-packet
 *
 * How to run
 *  $ ./recv-raw-packet
 *    or
 *  $ ./recv-raw-packet  eth0
 */

#include <unistd.h>
#include <arpa/inet.h>
#include <linux/if_packet.h>
#include <linux/ip.h>
#include <linux/udp.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <net/if.h>
#include <netinet/ether.h>
#include <errno.h>

#define ETHER_TYPE	0x0800

#define DEFAULT_IF	"enp7s0"
#define BUF_SIZ		1024

int main(int argc, char *argv[])
{
	char sender[INET6_ADDRSTRLEN];
	int sockfd, ret;
	int sockopt;
	ssize_t pktbytes;
	struct ifreq ifopts;	// To set promiscuous mode
	struct ifreq if_ip;	    // To get IP address of this host NIC
	struct sockaddr_storage peer_addr;
	uint8_t buf[BUF_SIZ];
	char ifName[IFNAMSIZ];

	// Network interface name 지정하기 (예: eth0)
	if (argc > 1)
	{
		strcpy(ifName, argv[1]);
	}
	else
	{
		strcpy(ifName, DEFAULT_IF);
	}

	// Ethernet + IP + UDP header
	struct ether_header *eh = (struct ether_header *) buf;
	struct iphdr *iph = (struct iphdr *) (buf + sizeof(struct ether_header));
	struct udphdr *udph = (struct udphdr *) (buf + sizeof(struct iphdr) + sizeof(struct ether_header));

	if ((sockfd = socket(PF_PACKET, SOCK_RAW, htons(ETHER_TYPE))) == -1)
	{
		printf("socket(PF_PACKET, SOCK_RAW, ETHER_TYPE) error: %d (%s)\n", errno, strerror(errno));
		return -1;
	}

	// Set interface to promiscuous mode
	strncpy(ifopts.ifr_name, ifName, IFNAMSIZ-1);
	ioctl(sockfd, SIOCGIFFLAGS, &ifopts);
	ifopts.ifr_flags |= IFF_PROMISC;
	ioctl(sockfd, SIOCSIFFLAGS, &ifopts);
	if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &sockopt, sizeof sockopt) == -1) {
		printf("setsockopt(SO_REUSEADDR) error: %d (%s)\n", errno, strerror(errno));
		close(sockfd);
		exit(0);
	}

	// Bind to device
	if (setsockopt(sockfd, SOL_SOCKET, SO_BINDTODEVICE, ifName, IFNAMSIZ-1) == -1)	{
		printf("setsockopt(SO_BINDTODEVICE, %s) error: %d (%s)\n", ifName, errno, strerror(errno));
		close(sockfd);
		exit(0);
	}

repeat:
	printf("\nWaiting to recvfrom...\n");
	pktbytes = recvfrom(sockfd, buf, BUF_SIZ, 0, NULL, NULL);
	printf("  Got packet %lu bytes\n", pktbytes);

	printf("Destination  MAC address = ");
	for (int idx = 0; idx < 6; idx++)
	{
		printf("%02x", eh->ether_dhost[idx]);
		if (idx < 5)
		{
			printf(":");
		}
	}
	printf("\n");

	// Get source IP
	((struct sockaddr_in *)&peer_addr)->sin_addr.s_addr = iph->saddr;
	inet_ntop(AF_INET, &((struct sockaddr_in*)&peer_addr)->sin_addr, sender, sizeof sender);

	// Look up my device IP addr
	memset(&if_ip, 0, sizeof(struct ifreq));
	strncpy(if_ip.ifr_name, ifName, IFNAMSIZ-1);
	if (ioctl(sockfd, SIOCGIFADDR, &if_ip) >= 0) {
		printf("Source IP: %s\n My IP: %s\n", sender,
				inet_ntoa(((struct sockaddr_in *)&if_ip.ifr_addr)->sin_addr));
		// Ignore if I sent it
		if (strcmp(sender, inet_ntoa(((struct sockaddr_in *)&if_ip.ifr_addr)->sin_addr)) == 0)	{
			printf("but I sent it :(\n");
			ret = -1;
			goto done;
		}
	}

	/* UDP payload length */
	ret = ntohs(udph->len) - sizeof(struct udphdr);

	/* Print packet */
	printf("\tData:");
	for (int idx = 0; idx < pktbytes; idx++)
	{
		printf("%02x ", buf[idx]);
	}
	printf("\n");

done:
	goto repeat;

	close(sockfd);
	return ret;
}

 

 

 

 

 


 

반응형

tcpdump 명령만 이용하면, 출력을 보기 좋게 또는 예쁘게 터미널로 출력하기 어렵다.

보기 좋은 형식으로 출력하려면 tshark 명령을 사용하는 것을 추천한다.

 

 

tshark 명령 사용

tshark을 사용하면, Network Packet이 정말 예쁘게 출력된다.  

아래와 같이 wireshark 패키지를 설치한다.  (참고로 tshark은 wireshark 패키지에 포함되어 있다)

##
## 아래 예시처럼 yum install 명령을 이용하여 wireshark 패키지를 설치한다.
##

$ yum install wireshark
...

##
## 설치 완료 후, 아래 명령과 같이 실행
## 참고: -V 옵션은 Packet을 Decode해서 출력하라는 뜻
##
$ tshark -i eth0 -nV port 53

 

위 예제 명령 정도의 옵션만 사용해도 꽤 보기 좋은 모양으로 IP 네트워크 패킷이 출력된다.

 

 

tcpdump 명령의 -A 옵션 사용해서 가독성 높게 출력하기

tshark을 사용할 수 없는 경우라면, 아래 명령처럼 -A 옵션을 주면, TCP 또는 UDP의 Payload가 ASCII 로 출력된다.

(사람 눈으로 읽을 수 있는, 즉 Readable Character만 터미널에 출력함) 

 

-A 옵션만 사용하면, TCP 세션을 수립하기 위한 SYNACKFIN 패킷도 모두 보여지기 때문에 

Filter에  tcp[32:4] = 0x47455420 조건을 추가한다. ("GET "에 대한 Pointcode 값이다) 

 

 

HTTP GET 요청 메시지만 출력
$ tcpdump -i p2p2 -s 1500 -An 'src net 192.168.0.0/24' and dst port 80 and 'tcp[32:4] = 0x47455420'

 

 

HTTP GET 요청과 응답 메시지 모두 출력
$ tcpdump -i em2 -s 1500 -An 'src net 192.168.0.0/24' and 'tcp[32:4] = 0x48545450' or 'tcp[32:4] = 0x47455420'

 

  또는 아래와 같은 방식을 사용할 수 있다.

(이 방식을 추천함)

$ tcpdump -i em2 -An -s 0 'tcp port 80 and (((ip[2:2] - ((ip[0]&0xf)<<2)) - ((tcp[12]&0xf0)>>2)) != 0)'

 


참고: 위의 3개 명령 중에서 제일 마지막 명령이 제일 출력 포맷이 예쁘다. 

+ Recent posts