Probably you have all worked with Wireshark before. The handy tool that is used to capture the network traffic on different interfaces. In this tutorial, we are going to make a simple command line tool to use sdns library and some to capture and parse DNS packets like Wireshark. Again, this tool is just good to show you how to use sdns.
So here is the scenario of the project:
Capture the network traffic and select those that are IPv4. Then we select those that are UDP packets and then only keep the packets with the source port or destination port of 53. We pass these packets to sdns library to see it can parse it successfully or not. Those packets that are successfully parsed, will be printed in the output with the following format:
<DNS_ID> SRCIP:SRCPORT -> DSTIP:DSTPORT ---- [Q|R] Qname Qclass RR
For example
23456 1.2.3.4:26123 -> 2.3.4.5:53 ---- [Q] gogole.com IN AAAA
23456 2.3.4.5:53 -> 1.2.3.4:26123 ---- [R] gogole.com IN AAAA
First we explains those functions that have nothing to do with sdns library and finally we write the main routine where we parse and print the DNS packet. We don't use any third party library to keep things as simple as possible.
Here is the whole source code:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <net/ethernet.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <linux/if_packet.h>
#include "neat_print.h"
void analyze_data(char *, ssize_t);
void process_udp_payload(char * src_ip, uint16_t src_port, char* dst_ip, uint16_t dst_port, char * buffer, uint16_t len);
int main(int argc, char** argv){
struct sockaddr saddr;
int sock_addr_len = sizeof(saddr);
int sockfd;
sockfd = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
if (sockfd == -1){
perror("Can not create RAW socket");
return 1;
}
char * buffer = (char*) malloc(65535);
if (NULL == buffer){
fprintf(stderr, "Can not allocate buffer with malloc()\n");
return 1;
}
ssize_t recv_data;
do{
recv_data = recvfrom(sockfd, buffer, 65535, 0, &saddr, (socklen_t*)&sock_addr_len);
if (recv_data < 0){
perror("Error in receiving data");
break;
}
analyze_data(buffer, recv_data);
}while(1);
free(buffer);
close(sockfd);
return 0;
}
void analyze_data(char * ethbuffer, ssize_t ethbuffer_len){
if (ethbuffer_len < 24)
return;
char * buffer = ethbuffer + 14;
if ((((uint8_t)buffer[0] >> 4) & 0x0F) != 4)
return;
if ((uint8_t)buffer[9] != 17)
return;
unsigned int iphl = ((uint8_t) buffer[0] & 0x0F) * 4;
char * udp_packet = buffer + iphl;
char * src_ip = NULL;
char * dst_ip = NULL;
struct in_addr ip_addr;
in_addr_t ipaddress = (buffer[12] << 24) | (buffer[13] << 16) |
(buffer[14] << 8 ) | (buffer[15]);
ip_addr.s_addr = ipaddress;
src_ip = strdup(inet_ntoa(ip_addr));
ipaddress = (buffer[16] << 24) | (buffer[17] << 16) |
(buffer[18] << 8 ) | (buffer[19]);
ip_addr.s_addr = ipaddress;
dst_ip = strdup(inet_ntoa(ip_addr));
uint16_t src_port = (udp_packet[0] << 8) | (udp_packet[1]);
uint16_t dst_port = (udp_packet[2] << 8) | (udp_packet[3]);
if (src_port != 53 && dst_port != 53){
free(src_ip);
free(dst_ip);
return;
}
uint16_t payload_len = ((udp_packet[4] << 8) | (udp_packet[5])) - 8;
process_udp_payload(src_ip, src_port, dst_ip, dst_port, udp_packet + 8, payload_len);
free(src_ip);
free(dst_ip);
return;
}
void process_udp_payload(char * src_ip, uint16_t src_port, char* dst_ip, uint16_t dst_port, char * buffer, uint16_t len){
if (res == 0){
char qr[20];
strcpy(qr, "[Q]");
else
strcpy(qr, "[R]");
fprintf(stdout, "%d %s:%d -> %s:%d ---- %s: ", dns_id, src_ip, src_port, dst_ip, dst_port, qr);
}
return;
}
void sdns_free_context(sdns_context *ctx)
Frees the context allocated by sdns_init_context().
sdns_context * sdns_init_context(void)
Initialize and create a new DNS context.
int sdns_from_wire(sdns_context *ctx)
Converts the raw data received from socket (bytes) to a DNS packet.
void sdns_neat_print_question(sdns_context *ctx)
sdns_message * msg
This is the DNS packet.
Definition sdns.h:689
uint16_t raw_len
Length of the raw data we received from socket.
Definition sdns.h:691
char * raw
The raw bytes we received from socket.
Definition sdns.h:690
sdns_header header
See sdns_header for more info.
Definition sdns.h:486
You can compile it manually with
gcc naivewireshark.c -I. sdns.c dns_utils.c dynamic_buffer.c neat_print.c -o naivewireshark
Let's break it in piece and explain each part!
int main(int argc, char** argv){
struct sockaddr saddr;
int sock_addr_len = sizeof(saddr);
int sockfd;
sockfd = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
if (sockfd == -1){
perror("Can not create RAW socket");
return 1;
}
char * buffer = (char*) malloc(65535);
if (NULL == buffer){
fprintf(stderr, "Can not allocate buffer with malloc()\n");
return 1;
}
ssize_t recv_data;
do{
recv_data = recvfrom(sockfd, buffer, 65535, 0, &saddr, (socklen_t*)&sock_addr_len);
if (recv_data < 0){
perror("Error in receiving data");
break;
}
analyze_data(buffer, recv_data);
}while(1);
free(buffer);
close(sockfd);
return 0;
}
The main function is pretty much easy to understand. It opens a RAW socket, trying to capture everything in a do...while forever. Since we used SOCK_RAW, it means we need to run the code in superuser mode to be able to listen to raw socket. I am running the code with "sudo" on my machine.
Whenever we receive a packet, we store it in buffer and we call another function analyze_data(buffer, recv_data) with buffer and length of the data we received from the socket. All the magic is done in analyze_data() function.
void analyze_data(char * ethbuffer, ssize_t ethbuffer_len){
if (ethbuffer_len < 24)
return;
char * buffer = ethbuffer + 14;
if ((((uint8_t)buffer[0] >> 4) & 0x0F) != 4)
return;
if ((uint8_t)buffer[9] != 17)
return;
unsigned int iphl = ((uint8_t) buffer[0] & 0x0F) * 4;
char * udp_packet = buffer + iphl;
char * src_ip = NULL;
char * dst_ip = NULL;
struct in_addr ip_addr;
in_addr_t ipaddress = (buffer[12] << 24) | (buffer[13] << 16) |
(buffer[14] << 8 ) | (buffer[15]);
ip_addr.s_addr = ipaddress;
src_ip = strdup(inet_ntoa(ip_addr));
ipaddress = (buffer[16] << 24) | (buffer[17] << 16) |
(buffer[18] << 8 ) | (buffer[19]);
ip_addr.s_addr = ipaddress;
dst_ip = strdup(inet_ntoa(ip_addr));
uint16_t src_port = (udp_packet[0] << 8) | (udp_packet[1]);
uint16_t dst_port = (udp_packet[2] << 8) | (udp_packet[3]);
if (src_port != 53 && dst_port != 53){
free(src_ip);
free(dst_ip);
return;
}
uint16_t payload_len = ((udp_packet[4] << 8) | (udp_packet[5])) - 8;
process_udp_payload(src_ip, src_port, dst_ip, dst_port, udp_packet + 8, payload_len);
free(src_ip);
free(dst_ip);
return;
}
In analyze_data() function, we receive an ether packet. What we are interested in, is the DNS packets so we have to do some filtering first. The first step is to extract the IP packet from Ether packet. That means we need to skip the first 14 bytes of the Ether packet (header part) to get the ether payload which is the IP packet.
# here is what we do in analyze_data() function.
_______________ _______________ _______________ _______________
| | | | | | | | YES
| Ether packet | ---> | IP packet | ---> | UDP packet | ---> | DNS packet? | -------> Print it!
|_______________| |_______________| |_______________| |_______________|
(it was really difficult to create this chart)
The first 4 bits of the IP packet is the IP version (4 or 6). For IPv4, it's always "4". So we return from the function if this value is not 4. The 10th byte of the IP packet is the protocol and UDP code is 17. So we only continue if buffer[9] == 17. After that, we extract the source and destionation IP address as well as the port numbers. If one of the source or destination port is 53, we send the rest of the packet to the next function (we assume it's a DNS packet).
void process_udp_payload(char * src_ip, uint16_t src_port, char* dst_ip, uint16_t dst_port, char * buffer, uint16_t len){
if (res == 0){
char qr[20];
strcpy(qr, "[Q]");
else
strcpy(qr, "[R]");
fprintf(stdout, "%d %s:%d -> %s:%d ---- %s: ", dns_id, src_ip, src_port, dst_ip, dst_port, qr);
}
return;
}
The last function is the process_udp_payload() which receives its payload from analyze_data() function. This is where we use sdns_from_wire() function to convert what we received from the socket to a DNS structure. If we get error from sdns_from_wire() function, we assume that this is not a valid DNS packet and we just drop it. Otherwise, we print the question section of the packet along with the DNS ID and address of the sender and the receiver.
In the last line of the function, we use sdns_free_context() to free the memory. However, notice that we set raw pointer to NULL before passing the context to sdns_free_context() (ctx->raw = NULL;). The reason is simple but very important: ctx->raw refers to the buffer we received as an argument in process_udp_payload() function. This buffer has been created in the main() function using malloc(). We also free this memory at the very end of the main() function. sdns_free_context() tries to free the raw buffer by calling free(ctx->raw) (you can check the source code in sdns.c) and this will result in double free which we can easily avoid just by setting this pointer to NULL before passing the context to sdns_free_context() function.
Here is what I get on my PC after compiling and running the code:
49902 1.0.2.89:53 -> 19.0.168.255:33137 ---- [R]: img-prod-cms-rt-microsoft-com.akamaized.net. IN A
53909 53.0.0.127:53 -> 1.0.0.127:61277 ---- [R]: img-prod-cms-rt-microsoft-com.akamaized.net. IN A
53909 53.0.0.127:53 -> 1.0.0.127:61277 ---- [R]: img-prod-cms-rt-microsoft-com.akamaized.net. IN A
44504 1.0.2.89:53 -> 19.0.168.255:58445 ---- [R]: e13678.dscb.akamaiedge.net. IN HTTPS
27081 53.0.0.127:53 -> 1.0.0.127:30296 ---- [R]: www.microsoft.com. IN HTTPS
27081 53.0.0.127:53 -> 1.0.0.127:30296 ---- [R]: www.microsoft.com. IN HTTPS
20533 1.0.2.89:53 -> 19.0.168.255:54053 ---- [R]: img-prod-cms-rt-microsoft-com.akamaized.net. IN HTTPS
58223 19.0.168.255:65445 -> 1.0.2.89:53 ---- [Q]: a1449.dscg2.akamai.net. IN HTTPS
51281 1.0.2.89:53 -> 19.0.168.255:50754 ---- [R]: e40491.dscg.akamaiedge.net. IN HTTPS
38503 53.0.0.127:53 -> 1.0.0.127:51459 ---- [R]: res.cdn.office.net. IN HTTPS
38503 53.0.0.127:53 -> 1.0.0.127:51459 ---- [R]: res.cdn.office.net. IN HTTPS
14838 1.0.0.127:16392 -> 53.0.0.127:53 ---- [Q]: portal.office.com. IN A
14838 1.0.0.127:16392 -> 53.0.0.127:53 ---- [Q]: portal.office.com. IN A
9083 1.0.0.127:38013 -> 53.0.0.127:53 ---- [Q]: portal.office.com. IN HTTPS
9083 1.0.0.127:38013 -> 53.0.0.127:53 ---- [Q]: portal.office.com. IN HTTPS
21131 19.0.168.255:48483 -> 1.0.2.89:53 ---- [Q]: portal.office.com. IN A
53035 19.0.168.255:58716 -> 1.0.2.89:53 ---- [Q]: portal.office.com. IN HTTPS
27473 1.0.0.127:65420 -> 53.0.0.127:53 ---- [Q]: outlook.office.com. IN A
27473 1.0.0.127:65420 -> 53.0.0.127:53 ---- [Q]: outlook.office.com. IN A
And now you know my IP address :-)