Newer
Older
/*
* dns.c: Sending dns query and parsing response
*
* Copyright (C) 2021-2024 iopsys Software Solutions AB. All rights reserved.
*
* Author: jomily joseph <jomily.joseph@iopsys.eu>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA
*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <uci.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/ip.h>
#include <netinet/ip6.h>
#include <netinet/tcp.h>
#include <linux/udp.h>
#include <ctype.h>
// Converts url name to a format required in dns query.
static void url_to_dns_format(unsigned char *dns, char *host)
{
int lock = 0, i;
strncat((char *)host, DELIM, MAX_URL_LENGTH - strlen(host));
for (i = 0; i < (int)strlen((char *)host); i++) {
for (; lock < i; lock++)
}
// Creates dns query and send.
// @param ip dns server ip
// @param j index of global dns cache.
// @return nothing
static void create_dns_query(char *ip, char *url_name, bool is_ipv6)
{
struct DNS_HEADER *dns = NULL;
struct QUESTION *qinfo = NULL;
unsigned char buf[65536], *qname;
int s;
// Set the DNS structure to standard queries
dns = (struct DNS_HEADER *)&buf;
dns->qr = 0;
dns->opcode = 0;
dns->aa = 0;
dns->tc = 0;
dns->rd = 1;
dns->ra = 0;
dns->z = 0;
dns->ad = 0;
dns->cd = 0;
dns->rcode = 0;
dns->q_count = htons(1);
dns->ans_count = 0;
dns->auth_count = 0;
dns->add_count = 0;
// Point to the query portion
qname = (unsigned char *)&buf[sizeof(struct DNS_HEADER)];
url_to_dns_format(qname, url_name);
qinfo = (struct QUESTION *)&buf[sizeof(struct DNS_HEADER) +
size_t len = sizeof(struct DNS_HEADER) +
(strlen((const char *)qname) + 1) + sizeof(struct QUESTION);
struct sockaddr_in dest;
s = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (s < 0) {
log_error("socket open failed");
return;
}
int flags = fcntl(s, F_GETFL, 0);
fcntl(s, F_SETFL, flags | O_NONBLOCK);
dest.sin_family = AF_INET;
dest.sin_port = htons(DNS_PORT);
dest.sin_addr.s_addr = inet_addr(ip);
if (sendto(s, (char *)buf, len, 0, (struct sockaddr *)&dest, sizeof(dest)) < 0)
log_error("sendto failed");
struct sockaddr_in6 dest6;
s = socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP);
if (s < 0) {
log_error("socket open failed");
return;
}
int flags = fcntl(s, F_GETFL, 0);
fcntl(s, F_SETFL, flags | O_NONBLOCK);
dest6.sin6_family = AF_INET6;
dest6.sin6_port = htons(DNS_PORT);
inet_pton(AF_INET6, ip, &dest6.sin6_addr);
if (sendto(s, (char *)buf, len, 0, (struct sockaddr *)&dest6, sizeof(dest6)) < 0)
log_error("sendto failed");
static void get_url_from_wildcard(const char *wildcard, char *url)
{
if (*wildcard == '*') {
wildcard++;
snprintf(url, 1024, "www%s", wildcard);
} else {
snprintf(url, 1024, "%s", wildcard);
}
return;
}
void send_dns_query(struct list_head *lh, int i)
struct sockaddr_in dest;
struct sockaddr_in6 dest6;
struct url_list *url = NULL;
list_for_each_entry(url, lh, list) {
get_url_from_wildcard(url->url, url_q);
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL); // Avoid resource leak of sock fds
if (inet_pton(AF_INET, dns_server_ip[i], &dest.sin_addr)) {
create_dns_query(dns_server_ip[i], url_q, false);
} else if (inet_pton(AF_INET6, dns_server_ip[i], &dest6.sin6_addr)) {
create_dns_query(dns_server_ip[i], url_q, true);
}
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
}
}
// Sends dns query for all blacklisted url's.
void *send_dns_queries(void *arg __attribute__((unused)))
// this seed is used for generating dns transaction id in the create_dns_query function
// Create and send dns query to all the IPv4 dns servers for urls already present in
// the dns cache
for (i = 0; i < dns_server_cnt; i++) {
// Iterate over the global profile list
// so, we loop over all filters of all profiles
// to cover all possible urls
list_for_each_entry(p, &profiles, list) {
struct urlfilter *filter = NULL;
list_for_each_entry(filter, &p->filters, list) {
// Check if the filter is enabled
if (!filter->enable) {
continue;
}
if (filter->bundle_cnt) {
for (unsigned int j = 0; j < filter->bundle_cnt; j++) {
send_dns_query(&filter->bundles[j]->custom_url, i);
}
}
}
// Exit from the current thread.
pthread_exit(NULL);
}
// Get url name from the dns format
// @param name url name
// @return nothing
static void get_url_from_dns_format(unsigned char *name)
{
if (!name)
return;
int i, j;
for (i = 0; i < (int)strlen((const char *)name); i++) {
unsigned int p = name[i];
for (j = 0; j < (int)p; j++) {
name[i] = name[i + 1];
// Convert to lowercase
// so that checks cannot be bypassed by using different case
// this is based on the assumption that dns uses lower case
name[i] = (unsigned char)tolower(name[i]);
i = i + 1;
}
name[i] = '.';
}
name[i - 1] = '\0';
}
// Read qname from the dns response packet
// @param reader pointer to read
// @param buffer contains the data
// @param count number of counts reader needs to be incremented
// @returns url name
static unsigned char *read_qname_from_dns(unsigned char *reader,
unsigned char *buffer, int *count)
{
unsigned char *name = NULL;
unsigned int p = 0, jumped = 0, offset;
name = (unsigned char *) calloc(MAX_SIZE, 1);
if (name == NULL)
return NULL;
name[0] = '\0';
// Read the names in 3www6google3com format.
// Using the DNS name notation and message compression techniques
// to get the qname. DNS name is encoded with a two byte subfield,
// which represents a pointer to a location where the name can be
// found.
while (*reader != 0) {
if (*reader >= 192) {
offset = (*reader) * MAX_SIZE + *(reader + 1U) - 49152U;
reader = buffer + offset - 1;
jumped = 1;
name[p++] = *reader;
reader = reader + 1;
if (jumped == 0)
name[p] = '\0';
if (jumped == 1)
get_url_from_dns_format(name);
return name;
}
static void cleanup_answers(struct RES_RECORD answers[], int answer_cnt)
{
int i;
for (i = 0; i < answer_cnt; i++) {
if (answers[i].name) {
free(answers[i].name);
answers[i].name = NULL;
}
if (answers[i].rdata) {
free(answers[i].rdata);
answers[i].rdata = NULL;
}
}
}
// Function parses dns response.
// @param data_p payload
// @return nothing
void parse_dns_response(unsigned char *data_p)
size_t ipv4_addr = 0, j, cname_cnt = 0;
int stop = 0, i;
struct RES_RECORD answers[MAX_DNS_ANS_RECORD] = {{0}};
unsigned char *reader = NULL, *qname = NULL;
dns = (struct DNS_HEADER *) data_p;
offset = sizeof(struct DNS_HEADER);
qname = (unsigned char *) &data_p[offset];
offset += (strlen((const char *)qname) + 1) + sizeof(struct QUESTION);
int answer_cnt = ntohs(dns->ans_count);
char url_cname[answer_cnt][MAX_URL_LENGTH];
memset(&url_cname, '\0', sizeof(url_cname));
// Start reading answers in dns response
for (i = 0; i < answer_cnt; i++) {
answers[i].name = read_qname_from_dns(reader, data_p, &stop);
if (answers[i].name == NULL) {
break;
}
if ('\0' == answers[i].name[0]) {
break;
}
reader = reader + stop;
answers[i].resource = (struct R_DATA *)(reader);
reader = reader + sizeof(struct R_DATA);
// to avoid reverse dns look ups
// or maybe we should check for in-addr.arpa in qname
if ((ntohs(answers[i].resource->type) == T_PTR)) {
break;
} else if ((ntohs(answers[i].resource->type) == T_A) || (ntohs(answers[i].resource->type) == T_AAAA)) {
// if the ip address is IPv4 then store the address
// note the data_len + 1, to accommodate for additional null char
answers[i].rdata = (unsigned char *) calloc(ntohs(answers[i].resource->data_len) + 1, 1);
for (j = 0; j < ntohs(answers[i].resource->data_len); j++)
answers[i].rdata[ntohs(answers[i].resource->data_len)] = '\0';
reader = reader + ntohs(answers[i].resource->data_len);
ipv4_addr++;
} else if (ntohs(answers[i].resource->type) == T_CNAME) {
unsigned char *cname = NULL;
if (cname != NULL) {
strncpy(url_cname[cname_cnt], (char *)cname, MAX_URL_LENGTH);
cname_cnt++;
}
reader = reader + ntohs(answers[i].resource->data_len);
reader = reader + ntohs(answers[i].resource->data_len);
}
// Store qname and ip address corresponding to it in dns cache
if ((ipv4_addr > 0) || (ipv6_addr > 0))
store_qname_ipaddr_in_cache(dns, answers, ipv4_addr, url_cname, cname_cnt, ipv6_addr);
// Clean up answers after processing
cleanup_answers(answers, answer_cnt);