Skip to content
Extraits de code Groupes Projets
Valider aca0dee8 rédigé par François De Keersmaeker's avatar François De Keersmaeker
Parcourir les fichiers

Merge branch 'main' of github.com:smart-home-network-security/protocol-parsers into main

parents 711ae535 a6c7b862
Aucune branche associée trouvée
Aucune étiquette associée trouvée
Aucune requête de fusion associée trouvée
#!/bin/bash
apt update
apt install -y gcc make cmake libcunit1 libcunit1-dev net-tools valgrind cppcheck
apt-get update
DEBIAN_FRONTEND=noninteractive apt-get install -y gcc make cmake libcunit1 libcunit1-dev net-tools valgrind cppcheck
......@@ -31,4 +31,6 @@ set(PARSERS header dns dhcp http igmp ssdp coap)
# Subdirectories containing code
add_subdirectory(src)
add_subdirectory(test)
IF( NOT OPENWRT_CROSSCOMPILING )
add_subdirectory(test)
ENDIF()
/**
* @file include/dns.h
* @file include/parsers/dns.h
* @author François De Keersmaeker (francois.dekeersmaeker@uclouvain.be)
* @brief DNS message parser
* @date 2022-09-09
*
......@@ -15,6 +16,11 @@
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "packet_utils.h"
#include "dns_map.h"
......@@ -71,7 +77,7 @@ typedef struct dns_header {
*/
typedef struct dns_question {
char *qname;
dns_rr_type_t qtype;
uint16_t qtype;
uint16_t qclass;
} dns_question_t;
......@@ -89,7 +95,7 @@ typedef union {
*/
typedef struct dns_resource_record {
char *name;
dns_rr_type_t rtype;
uint16_t rtype;
uint16_t rclass;
uint32_t ttl;
uint16_t rdlength;
......@@ -187,10 +193,10 @@ dns_question_t* dns_get_question(dns_question_t *questions, uint16_t qdcount, ch
/**
* @brief Retrieve the IP addresses corresponding to a given domain name in a DNS Answers list.
*
*
* Searches a DNS Answer list for a specific domain name and returns the corresponding IP address.
* Processes each Answer recursively if the Answer Type is a CNAME.
*
*
* @param answers DNS Answers list to search in
* @param ancount number of Answers in the list
* @param domain_name domain name to search for
......@@ -199,6 +205,37 @@ dns_question_t* dns_get_question(dns_question_t *questions, uint16_t qdcount, ch
ip_list_t dns_get_ip_from_name(dns_resource_record_t *answers, uint16_t ancount, char *domain_name);
///// COMMUNICATE /////
/**
* @brief Convert domain name to message format.
*
* @param dst converted domain name
* @param src domain name to convert
*/
void dns_convert_qname(char *dst, char *src, uint16_t len);
/**
* @brief Send a DNS query for the given domain name.
*
* @param qname domain name to query for
* @param sockfd socket file descriptor
* @param server_addr DNS server IPv4 address
* @return 0 if the query was sent successfully, -1 otherwise
*/
int dns_send_query(char *qname, int sockfd, struct sockaddr_in *server_addr);
/**
* @brief Receive a DNS response.
*
* @param sockfd socket file descriptor
* @param server_addr DNS server IPv4 address
* @param dns_message allocated buffer which will be filled with the DNS response message, upon success
* @return 0 if DNS response was received successfully, -1 otherwise
*/
int dns_receive_response(int sockfd, struct sockaddr_in *server_addr, dns_message_t *dns_message);
///// DESTROY /////
/**
......
/**
* @file src/dns.c
* @file src/parsers/dns.c
* @author François De Keersmaeker (francois.dekeersmaeker@uclouvain.be)
* @brief DNS message parser
* @date 2022-09-09
*
......@@ -9,6 +10,13 @@
#include "dns.h"
// DNS message timeout
#define TIMEOUT 5 // Timeout value, in seconds
struct timeval timeout = {
.tv_sec = TIMEOUT,
.tv_usec = 0
};
///// PARSING /////
......@@ -191,7 +199,7 @@ dns_resource_record_t* dns_parse_rrs(uint16_t count, uint8_t *data, uint16_t *of
// Parse domain name
(rrs + i)->name = dns_parse_domain_name(data, offset);
// Parse rtype, rclass and TTL
dns_rr_type_t rtype = ntohs(*((uint16_t *) (data + *offset)));
uint16_t rtype = ntohs(*((uint16_t *) (data + *offset)));
(rrs + i)->rtype = rtype;
(rrs + i)->rclass = ntohs(*((uint16_t *) (data + *offset + 2))) & DNS_CLASS_MASK;
(rrs + i)->ttl = ntohl(*((uint32_t *) (data + *offset + 4)));
......@@ -199,7 +207,7 @@ dns_resource_record_t* dns_parse_rrs(uint16_t count, uint8_t *data, uint16_t *of
uint16_t rdlength = ntohs(*((uint16_t *) (data + *offset + 8)));
(rrs + i)->rdlength = rdlength;
*offset += 10;
(rrs + i)->rdata = dns_parse_rdata(rtype, rdlength, data, offset);
(rrs + i)->rdata = dns_parse_rdata((dns_rr_type_t) rtype, rdlength, data, offset);
}
return rrs;
}
......@@ -326,39 +334,48 @@ dns_question_t* dns_get_question(dns_question_t *questions, uint16_t qdcount, ch
/**
* @brief Retrieve the IP addresses corresponding to a given domain name in a DNS Answers list.
*
*
* Searches a DNS Answer list for a specific domain name and returns the corresponding IP address.
* Processes each Answer recursively if the Answer Type is a CNAME.
*
*
* @param answers DNS Answers list to search in
* @param ancount number of Answers in the list
* @param domain_name domain name to search for
* @return struct ip_list representing the list of corresponding IP addresses
*/
ip_list_t dns_get_ip_from_name(dns_resource_record_t *answers, uint16_t ancount, char *domain_name) {
ip_list_t dns_get_ip_from_name(dns_resource_record_t *answers, uint16_t ancount, char *domain_name)
{
ip_list_t ip_list;
ip_list.ip_count = 0;
ip_list.ip_addresses = NULL;
char *cname = domain_name;
for (uint16_t i = 0; i < ancount; i++) {
if (strcmp((answers + i)->name, cname) == 0) {
for (uint16_t i = 0; i < ancount; i++)
{
if (strcmp((answers + i)->name, cname) == 0)
{
dns_rr_type_t rtype = (answers + i)->rtype;
if (rtype == A || rtype == AAAA)
{
// Handle IP list length
if (ip_list.ip_addresses == NULL) {
ip_list.ip_addresses = (ip_addr_t *) malloc(sizeof(ip_addr_t));
} else {
if (ip_list.ip_addresses == NULL)
{
ip_list.ip_addresses = (ip_addr_t *)malloc(sizeof(ip_addr_t));
}
else
{
void *realloc_ptr = realloc(ip_list.ip_addresses, (ip_list.ip_count + 1) * sizeof(ip_addr_t));
if (realloc_ptr == NULL) {
if (realloc_ptr == NULL)
{
// Handle realloc error
free(ip_list.ip_addresses);
fprintf(stderr, "Error reallocating memory for IP list.\n");
ip_list.ip_count = 0;
ip_list.ip_addresses = NULL;
return ip_list;
} else {
ip_list.ip_addresses = (ip_addr_t*) realloc_ptr;
}
else
{
ip_list.ip_addresses = (ip_addr_t *)realloc_ptr;
}
}
// Handle IP version and value
......@@ -375,6 +392,152 @@ ip_list_t dns_get_ip_from_name(dns_resource_record_t *answers, uint16_t ancount,
}
///// COMMUNICATE /////
/**
* @brief Convert domain name to message format.
*
* @param dst converted domain name
* @param src domain name to convert
*/
void dns_convert_qname(char *dst, char *src, uint16_t len) {
int lock = 0; // Points to the next index to fill in the output array
// Start by iterating over each character in the input domain name
for (int i = 0; i < len; i++)
{
int segment_len = 0; // Length of the current segment
// Count the length of the current segment until we hit a dot or the end of the string
while (*(src + i) != '.' && *(src + i) != '\0')
{
*(dst + lock + 1 + segment_len) = *(src + i);
segment_len++;
i++;
}
*(dst + lock) = segment_len; // Prefix the segment with its length
lock += segment_len + 1; // Move the index past the current segment
}
*(dst + lock) = '\0'; // Null terminate the DNS label format string
}
/**
* @brief Send a DNS query for the given domain name.
*
* @param qname domain name to query for
* @param sockfd socket file descriptor
* @param server_addr DNS server IPv4 address
*/
int dns_send_query(char *qname, int sockfd, struct sockaddr_in *server_addr) {
// Buffer that will contain the message
uint16_t qname_len = strlen(qname);
uint16_t qname_labels_len = qname_len + sizeof(uint8_t) * 2;
uint16_t dns_questions_size = qname_labels_len + sizeof(uint16_t) * 2;
uint16_t dns_message_size = DNS_HEADER_SIZE + dns_questions_size;
uint8_t *buffer = (uint8_t *) malloc(dns_message_size);
// Populate DNS Header fields
dns_header_t dns_header;
dns_header.id = htons((uint16_t) getpid());
dns_header.flags = htons((uint16_t) (0b0000000100000000));
dns_header.qr = 0; // Query: qr = 0
dns_header.qdcount = htons((uint16_t) 1); // Only 1 question
dns_header.ancount = 0;
dns_header.nscount = 0;
dns_header.arcount = 0;
// Populate DNS Question fields
dns_question_t dns_question;
dns_question.qname = (char *)malloc(qname_labels_len);
dns_convert_qname(dns_question.qname, qname, qname_len);
dns_question.qtype = htons((uint16_t) A);
dns_question.qclass = htons((uint16_t) 1);
// Copy all DNS fields
memcpy(buffer, &dns_header, sizeof(uint16_t) * 2);
memcpy(buffer + sizeof(uint16_t) * 2, &(dns_header.qdcount), sizeof(uint16_t) * 4);
memcpy(buffer + DNS_HEADER_SIZE, dns_question.qname, qname_labels_len);
memcpy(buffer + DNS_HEADER_SIZE + qname_labels_len, &(dns_question.qtype), sizeof(uint16_t) * 2);
// Set socket timeout
if (setsockopt(sockfd, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(timeout)) < 0)
{
perror("Error setting socket send timeout");
// Free memory
free(dns_question.qname);
free(buffer);
return -1;
}
// Send DNS message
#ifdef DEBUG
printf("Sending DNS query for domain name %s to server %s\n", qname, inet_ntoa(server_addr->sin_addr));
#endif /* DEBUG */
if (sendto(sockfd, buffer, dns_message_size, 0, (struct sockaddr *)server_addr, sizeof(*server_addr)) < 0)
{
if (errno == EWOULDBLOCK || errno == EAGAIN)
{
printf("DNS query for %s timed out.\n", qname);
} else {
perror("Failed sending DNS query.");
}
// Free memory
free(dns_question.qname);
free(buffer);
return -1;
}
// DNS query was sent successfully
free(dns_question.qname);
free(buffer);
return 0;
}
/**
* @brief Receive a DNS response.
*
* @param sockfd socket file descriptor
* @param server_addr DNS server IPv4 address
* @param dns_message allocated buffer which will be filled with the DNS response message, upon success
* @return 0 if DNS response was received successfully, -1 otherwise
*/
int dns_receive_response(int sockfd, struct sockaddr_in *server_addr, dns_message_t* dns_message)
{
// Receiving buffer
int bufsize = 65536;
uint8_t *buffer = (uint8_t *)malloc(bufsize);
// Set socket timeout
if (setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)) < 0)
{
perror("Error setting socket receive timeout");
free(buffer);
return -1;
}
// Await response
int n = recvfrom(sockfd, (char *)buffer, bufsize, 0, NULL, NULL);
if (n < 0)
{
if (errno == EWOULDBLOCK || errno == EAGAIN)
{
printf("DNS receive timed out\n");
} else {
perror("Failed receiving DNS response.");
}
free(buffer);
return -1;
}
// DNS response was received successfully, parse it
*dns_message = dns_parse_message(buffer);
free(buffer);
return 0;
}
///// DESTROY /////
/**
......@@ -410,7 +573,7 @@ static void dns_free_rrs(dns_resource_record_t *rrs, uint16_t count) {
dns_resource_record_t rr = *(rrs + i);
if (rr.rdlength > 0) {
free(rr.name);
dns_free_rdata(rr.rdata, rr.rtype);
dns_free_rdata(rr.rdata, (dns_rr_type_t) rr.rtype);
}
}
free(rrs);
......@@ -539,7 +702,7 @@ void dns_print_rr(char* section_name, dns_resource_record_t rr) {
printf(" Class: %hd\n", rr.rclass);
printf(" TTL [s]: %d\n", rr.ttl);
printf(" Data length: %hd\n", rr.rdlength);
printf(" RDATA: %s\n", dns_rdata_to_str(rr.rtype, rr.rdlength, rr.rdata));
printf(" RDATA: %s\n", dns_rdata_to_str((dns_rr_type_t) rr.rtype, rr.rdlength, rr.rdata));
}
/**
......
/**
* @file test/dns.c
* @file test/parsers/dns.c
* @author François De Keersmaeker (francois.dekeersmaeker@uclouvain.be)
* @brief Unit tests for the DNS parser
* @date 2022-09-09
*
......@@ -63,6 +64,27 @@ void compare_rrs(uint16_t count, dns_resource_record_t *actual, dns_resource_rec
}
}
/**
* @brief Unit test for the dns_convert_qname function.
*/
void test_dns_convert_qname() {
// Test parameters
char *qname = "www.google.com";
uint8_t qname_len = strlen(qname);
char *expected = "\3www\6google\3com";
uint8_t converted_len = qname_len + 2;
// Execute function
char *actual = (char*) malloc(converted_len);
dns_convert_qname(actual, qname, qname_len);
// Verify result
CU_ASSERT_STRING_EQUAL(actual, expected);
// Clean up
free(actual);
}
/**
* Unit test for the DNS parser.
*/
......@@ -130,16 +152,16 @@ void test_dns_xiaomi() {
CU_ASSERT_TRUE(dns_contains_full_domain_name(message.questions, message.header.qdcount, domain_name));
char *suffix = "api.io.mi.com";
CU_ASSERT_TRUE(dns_contains_suffix_domain_name(message.questions, message.header.qdcount, suffix, strlen(suffix)));
domain_name = "www.example.org";
domain_name = "swag.framinem.org";
CU_ASSERT_FALSE(dns_contains_full_domain_name(message.questions, message.header.qdcount, domain_name));
suffix = "example.org";
suffix = "framinem.com";
CU_ASSERT_FALSE(dns_contains_suffix_domain_name(message.questions, message.header.qdcount, suffix, strlen(suffix)));
// Get question from domain name
domain_name = "business.smartcamera.api.io.mi.com";
dns_question_t *question_lookup = dns_get_question(message.questions, message.header.qdcount, domain_name);
CU_ASSERT_PTR_NOT_NULL(question_lookup);
domain_name = "www.example.org";
domain_name = "swag.framinem.org";
question_lookup = dns_get_question(message.questions, message.header.qdcount, domain_name);
CU_ASSERT_PTR_NULL(question_lookup);
......@@ -150,7 +172,7 @@ void test_dns_xiaomi() {
CU_ASSERT_EQUAL(ip_list.ip_count, 1);
CU_ASSERT_STRING_EQUAL(ipv4_net_to_str(ip_list.ip_addresses->value.ipv4), ip_address);
free(ip_list.ip_addresses);
domain_name = "www.example.org";
domain_name = "swag.framinem.org";
ip_list = dns_get_ip_from_name(message.answers, message.header.ancount, domain_name);
CU_ASSERT_EQUAL(ip_list.ip_count, 0);
CU_ASSERT_PTR_NULL(ip_list.ip_addresses);
......@@ -278,16 +300,16 @@ void test_dns_office() {
CU_ASSERT_TRUE(dns_contains_full_domain_name(message.questions, message.header.qdcount, domain_name));
char* suffix = "office.com";
CU_ASSERT_TRUE(dns_contains_suffix_domain_name(message.questions, message.header.qdcount, suffix, strlen(suffix)));
domain_name = "www.example.org";
domain_name = "swag.framinem.org";
CU_ASSERT_FALSE(dns_contains_full_domain_name(message.questions, message.header.qdcount, domain_name));
suffix = "example.org";
suffix = "framinem.org";
CU_ASSERT_FALSE(dns_contains_suffix_domain_name(message.questions, message.header.qdcount, suffix, strlen(suffix)));
// Get question from domain name
domain_name = "outlook.office.com";
dns_question_t *question_lookup = dns_get_question(message.questions, message.header.qdcount, domain_name);
CU_ASSERT_PTR_NOT_NULL(question_lookup);
domain_name = "www.example.org";
domain_name = "swag.framinem.org";
question_lookup = dns_get_question(message.questions, message.header.qdcount, domain_name);
CU_ASSERT_PTR_NULL(question_lookup);
......@@ -305,7 +327,7 @@ void test_dns_office() {
CU_ASSERT_STRING_EQUAL(ipv4_net_to_str((ip_list.ip_addresses + i)->value.ipv4), ip_addresses[i]);
}
free(ip_list.ip_addresses);
domain_name = "www.example.org";
domain_name = "swag.framinem.org";
ip_list = dns_get_ip_from_name(message.answers, message.header.ancount, domain_name);
CU_ASSERT_EQUAL(ip_list.ip_count, 0);
CU_ASSERT_PTR_NULL(ip_list.ip_addresses);
......@@ -314,6 +336,39 @@ void test_dns_office() {
dns_free_message(message);
}
/**
* @brief Test the `dns_send_query` and `dns_receive_response` functions.
*/
void test_dns_send_receive() {
// Initialize
int ret;
char *domain_name = "www.google.com";
// Open socket
int sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
CU_ASSERT_TRUE(sockfd > 0);
// Server address: network gateway
struct sockaddr_in server_addr;
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(53);
server_addr.sin_addr.s_addr = inet_addr("8.8.8.8");
// Send query for dummy domain name
ret = dns_send_query(domain_name, sockfd, &server_addr);
CU_ASSERT_EQUAL(ret, 0);
// Receive response
dns_message_t dns_response;
ret = dns_receive_response(sockfd, &server_addr, &dns_response);
CU_ASSERT_EQUAL(ret ,0);
CU_ASSERT_STRING_EQUAL(dns_response.questions->qname, domain_name);
// Free memory
dns_free_message(dns_response);
}
/**
* Main function for the unit tests.
*/
......@@ -325,8 +380,10 @@ int main(int argc, char const *argv[])
printf("Test suite: dns\n");
CU_pSuite suite = CU_add_suite("dns", NULL, NULL);
// Run tests
CU_add_test(suite, "dns-convert-qname", test_dns_convert_qname);
CU_add_test(suite, "dns-xiaomi", test_dns_xiaomi);
CU_add_test(suite, "dns-office", test_dns_office);
CU_add_test(suite, "dns-send-receive", test_dns_send_receive);
CU_basic_run_tests();
CU_cleanup_registry();
return 0;
......
0% Chargement en cours ou .
You are about to add 0 people to the discussion. Proceed with caution.
Terminez d'abord l'édition de ce message.
Veuillez vous inscrire ou vous pour commenter