작성일: 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;
}