Skip to content
Snippets Groups Projects
dns.c 10 KiB
Newer Older
  • Learn to ignore specific revisions
  • /*
     * 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>
    
    Rahul Thakur's avatar
    Rahul Thakur committed
     * Author: Rahul Thakur <rahul.thakur@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 <fcntl.h>
    
    Rahul Thakur's avatar
    Rahul Thakur committed
    
    
    #include "filter.h"
    
    Rahul Thakur's avatar
    Rahul Thakur committed
    #include "dns_cache.h"
    #include "utils.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++) {
    
    		if ('.' == host[i]) {
    
    			*dns++ = (unsigned char)(i - lock);
    
    				*dns++ = (unsigned char)host[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->id = (unsigned short)random();
    
    	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) +
    
    						      (strlen((const char *)qname) + 1)];
    
    
    	qinfo->qclass = htons(1);
    
    
    	size_t len = sizeof(struct DNS_HEADER) +
    
    		     (strlen((const char *)qname) + 1) + sizeof(struct QUESTION);
    
    	if (!is_ipv6) {
    
    		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);
    
    
    		qinfo->qtype = htons(1);
    
    		if (sendto(s, (char *)buf, len, 0, (struct sockaddr *)&dest, sizeof(dest)) < 0)
    			log_error("sendto failed");
    
    	} else {
    
    		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);
    
    
    		qinfo->qtype = htons(28);
    
    		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)
    
    Amit Kumar's avatar
    Amit Kumar committed
    	struct sockaddr_in dest;
    	struct sockaddr_in6 dest6;
    
    	char url_q[1024] = {0};
    
    	struct url_list *url = NULL;
    
    
    	list_for_each_entry(url, lh, list) {
    
    		pthread_testcancel();
    
    		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
    
    	srandom((unsigned int)time(NULL));
    
    	// 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++) {
    
    		struct profile *p = NULL;
    
    
    		pthread_testcancel();
    
    		// 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;
    
    
    			pthread_testcancel();
    
    			list_for_each_entry(filter, &p->filters, list) {
    
    				pthread_testcancel();
    
    
    				// Check if the filter is enabled
    				if (!filter->enable) {
    					continue;
    				}
    
    
    				if (filter->bundle_cnt) {
    					for (unsigned int j = 0; j < filter->bundle_cnt; j++) {
    
    						pthread_testcancel();
    
    						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)
    {
    
    	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]);
    
    // 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;
    
    			*count = *count + 1;
    	}
    
    
    		*count = *count + 1;
    
    
    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 offset;
    
    	size_t ipv4_addr = 0, j, cname_cnt = 0;
    	int stop = 0, i;
    
    	struct DNS_HEADER *dns = NULL;
    
    	struct RES_RECORD answers[MAX_DNS_ANS_RECORD] = {{0}};
    	unsigned char *reader = NULL, *qname = NULL;
    
    	size_t ipv6_addr = 0;
    
    Amit Kumar's avatar
    Amit Kumar committed
    	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);
    
    	reader = &data_p[offset];
    
    
    	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++) {
    
    Amit Kumar's avatar
    Amit Kumar committed
    		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[j] = reader[j];
    
    			answers[i].rdata[ntohs(answers[i].resource->data_len)] = '\0';
    			reader = reader + ntohs(answers[i].resource->data_len);
    
    Amit Kumar's avatar
    Amit Kumar committed
    			if (ntohs(answers[i].resource->type) == T_A)
    
    Amit Kumar's avatar
    Amit Kumar committed
    			else
    				ipv6_addr++;
    
    		} else if (ntohs(answers[i].resource->type) == T_CNAME) {
    
    Amit Kumar's avatar
    Amit Kumar committed
    			cname = read_qname_from_dns(reader, data_p, &stop);
    
    				strncpy(url_cname[cname_cnt], (char *)cname, MAX_URL_LENGTH);
    
    				free(cname);
    
    			}
    			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);