Skip to content
Snippets Groups Projects
filter.c 12.6 KiB
Newer Older
  • Learn to ignore specific revisions
  • /*
     * filter.c: URL filter daemon
     *
    
     * 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 <stdlib.h>
    
    Rahul Thakur's avatar
    Rahul Thakur committed
    #include <unistd.h>
    
    #include <string.h>
    #include <arpa/inet.h>
    #include <linux/netfilter.h>
    #include <syslog.h>
    #include <libnetfilter_queue/libnetfilter_queue.h>
    #include <libnetfilter_queue/linux_nfnetlink_queue.h>
    #include <libubox/uloop.h>
    
    #include <libbbfdm-ubus/bbfdm-ubus.h>
    
    #include <pthread.h>
    
    #include <errno.h>
    
    
    #include "filter.h"
    
    #ifdef PARENTAL_CONTROL_ULRFILTER
    
    Rahul Thakur's avatar
    Rahul Thakur committed
    #include "filter_utils.h"
    #include "utils.h"
    #include "dns_cache.h"
    
    #include "parentalcontrol.h"
    
    extern DM_MAP_OBJ tDynamicObj[];
    static struct bbfdm_context bbfdm_ctx = {0};
    
    #ifdef PARENTAL_CONTROL_ULRFILTER
    
    static struct ubus_event_handler config_reload_ev = {0};
    
    struct nfq_handle *nfq_h;
    struct nfq_q_handle *queue_h;
    
    static pthread_t ptid;
    
    struct uloop_fd uloop_fd;
    
    static bool serv_enabled;
    
    static struct profile *get_profile_for_mac_address(char *mac_addr)
    
    {
    	struct profile *p = NULL;
    
    	// Iterate over the global profile list
    
    	list_for_each_entry(p, &profiles, list) {
    		// check if host is present in this profile
    
    		if (p->hosts.cmph_hash && p->hosts.data) {
    
    			// check stringstore from memory
    			if (stringstore_check_string_from_memory(&p->hosts, mac_addr)) {
    
    // Checks if the packet needs to be accepted or not based on mac address
    // and url.
    // @param payload packet payload
    // @return PKT_ACCEPT or PKT_DROP
    static int handle_packet_filtering(struct nfq_data *payload)
    {
    	struct nfqnl_msg_packet_hw *hw_hdr;
    	unsigned char *data = NULL;
    
    	char payload_pkt_arr[INET6_ADDRSTRLEN] = {0};
    	char *payload_pkt = payload_pkt_arr;
    
    	uint8_t proto;
    
    	size_t data_len;
    	int ret = 0;
    
    	bool is_https_packet = false;
    
    Amit Kumar's avatar
    Amit Kumar committed
    	bool is_ipv6 = false;
    
    
    	// Get the source mac address.
    	hw_hdr = nfq_get_packet_hw(payload);
    	if (!hw_hdr) {
    		return PKT_ACCEPT;
    	}
    
    
    	bool is_mac_zero = true;
    	for (int i = 0; i < 6; i++) {
    		if (hw_hdr->hw_addr[i] != 0) {
    			is_mac_zero = false;
    			break;
    		}
    	}
    
    	// a lot of packets are received with mac address all zeroes
    	// it is said that this happens when a packet is retrieved at a
    	// layer where mac addresses are not available, like above link layer
    	// however, it has been observed that these packets are safe to discard
    	if (is_mac_zero) {
    		return PKT_ACCEPT;
    	}
    
    	char mac_addr[MAC_LENGTH] = {0};
    
    	// Only format if the MAC address is not all zeroes
    	// get mac_addr in lower case (%02x for lower case %02X for upper case)
    
    	snprintf(mac_addr, sizeof(mac_addr), "%02x:%02x:%02x:%02x:%02x:%02x",
    
    		 hw_hdr->hw_addr[0], hw_hdr->hw_addr[1], hw_hdr->hw_addr[2],
    		 hw_hdr->hw_addr[3], hw_hdr->hw_addr[4], hw_hdr->hw_addr[5]);
    
    	ret = nfq_get_payload(payload, &data);
    	if (ret <= 0) {
    
    	data_len = (size_t)ret;
    
    
    	// Get the payload offset.
    	int payload_offset;
    
    	payload_offset = get_ip_packet_payload(data, &proto, data_len, &is_ipv6);
    
    Rahul Thakur's avatar
    Rahul Thakur committed
    	if (payload_offset <= 0) {
    
    		// Just accept the packet and return since the packet either does not match
    
    Rahul Thakur's avatar
    Rahul Thakur committed
    		// urlfilter criteria or is a dns response.
    
    Rahul Thakur's avatar
    Rahul Thakur committed
    	ret = get_payload_pkt(proto, data, payload_offset,
    
    			      &payload_pkt, &is_https_packet, is_ipv6);
    
    Amit Kumar's avatar
    Amit Kumar committed
    
    
    	size_t len = strlen(payload_pkt);
    
    	if (len == 0) {
    
    	struct profile *pkt_profile = get_profile_for_mac_address(mac_addr);
    
    	if (pkt_profile == NULL)
    		return PKT_ACCEPT;
    
    
    	// Check if the URL has been blacklisted in this profile.
    
    	ret = profile_based_url_filtering(pkt_profile, payload_pkt, mac_addr, is_https_packet, is_ipv6);
    
    	return ret;
    }
    
    
    // Callback function for handling packets.
    // @param queue_h queue 0 handle
    // @param nfmsg nfq message received
    // @param nfa message data
    // @param data store data
    // @return 0 on success and non zero otherwise
    
    static int handle_packets(struct nfq_q_handle *q_h, struct nfgenmsg *nfmsg __attribute__((unused)),
    			  struct nfq_data *nfa, void *data __attribute__((unused)))
    
    {
    	struct nfqnl_msg_packet_hdr *pkt_hdr;
    
    	int pkt_decision;
    	uint32_t pkt_id = 0;
    
    
    	// Fetch the packet header.
    	pkt_hdr = nfq_get_msg_packet_hdr(nfa);
    
    		pkt_id = ntohl(pkt_hdr->packet_id);
    
    	// Check if we should block this packet on the basis of URL and source mac address.
    	pkt_decision = handle_packet_filtering(nfa);
    
    	if (pkt_decision == PKT_ACCEPT)
    
    		return nfq_set_verdict(q_h, pkt_id, NF_ACCEPT, 0, NULL);
    
    		return nfq_set_verdict(q_h, pkt_id, NF_DROP, 0, NULL);
    
    }
    
    // Function to unbind and bind to nf_queue handler.
    // @return nothing
    
    static void update_nf_queue_handler(void)
    
    {
    	int nfq_bind_v4 = 1, nfq_bind_v6 = 1;
    
    	// Unbinding existing nf_queue handler.
    
    	if (nfq_unbind_pf(nfq_h, AF_INET) < 0) {
    
    		log_error("Error during nfq_unbind_pf() for IPv4");
    
    	if (nfq_unbind_pf(nfq_h, AF_INET6) < 0) {
    
    		log_error("Error during nfq_unbind_pf() for IPv6");
    
    	if (!(nfq_bind_v4 || nfq_bind_v6)) {
    
    		log_error("Error during nfq_unbind_pf()");
    
    		exit(1);
    	}
    
    	// Binding nfnetlink_queue as nf_queue handler.
    	nfq_bind_v4 = nfq_bind_v6 = 1;
    
    	if (nfq_bind_pf(nfq_h, AF_INET < 0)) {
    
    		log_error("Error during nfq_bind_pf() for IPv4");
    
    	if (nfq_bind_pf(nfq_h, AF_INET6) < 0) {
    
    		log_error("Error during nfq_bind_pf() for IPv6");
    
    	if (!(nfq_bind_v4 || nfq_bind_v6)) {
    
    		log_error("Error during nfq_bind_pf()");
    
    		exit(1);
    	}
    }
    
    // Open a netlink connection and returns file descriptor
    // @return fd of the netlink socket
    
    static int open_netlink_connection(void)
    
    {
    	// Opening netlink socket.
    	nfq_h = nfq_open();
    	if (!nfq_h) {
    
    		log_error("Error during nfq_open()");
    
    		exit(1);
    	}
    
    	update_nf_queue_handler();
    
    	// Binding this socket to queue.
    	queue_h = nfq_create_queue(nfq_h,  0, &handle_packets, NULL);
    	if (!queue_h) {
    
    		log_error("Error during nfq_create_queue()");
    
    		exit(1);
    	}
    
    	// Setting copy_packet mode.
    	if (nfq_set_mode(queue_h, NFQNL_COPY_PACKET, 0xffff) < 0) {
    
    		log_error("nfq_set_mode error");
    
    		exit(1);
    	}
    
    	return nfq_fd(nfq_h);
    }
    
    // This function reads the packets received on the fd
    // @param fd is the pointer to the file descriptor to read on
    
    static void url_filter_recv(struct uloop_fd *fd, unsigned int events)
    
    {
    	char buf[BUFSIZE];
    
    	memset(buf, 0, sizeof(buf));
    
    	int rv = (int)recv(fd->fd, buf, sizeof(buf), 0);
    	if (rv >= 0) {
    		nfq_handle_packet(nfq_h, buf, rv);
    
    static int register_nfqueue_fd(void)
    
    	memset(&uloop_fd, 0, sizeof(struct uloop_fd));
    
    	uloop_fd.fd = open_netlink_connection();
    	uloop_fd.cb = url_filter_recv;
    	if (uloop_fd_add(&uloop_fd, ULOOP_READ)) {
    		log_error("Could not register nfqueue FD");
    		return -1;
    	}
    
    	return 0;
    }
    
    
    static int call_bundle_sync_script(void)
    {
    	pid_t pid = fork();
    
    	if (pid < 0) {
    		log_error("fork failed");
    		return 1;
    	}
    
    	if (pid == 0) {  // Child process
    		char *const args[] = { "/lib/parentalcontrol/sync_bundles.sh", NULL };
    		execv(args[0], args);
    		log_error("execl failed");
    		_exit(127);  // Exit with failure status
    	}
    
    	// Parent process does not wait
    	return 0;
    }
    
    
    static void config_reload_cb(struct ubus_context *ctx __attribute__((unused)),
    			struct ubus_event_handler *ev __attribute__((unused)),
    			const char *type __attribute__((unused)),
    			struct blob_attr *msg __attribute__((unused)))
    {
    
    	// if service was enabled before, perform cleanup before next run
    
    	if (serv_enabled) {
    		uloop_fd_delete(&uloop_fd);
    
    		// Destroying the queue handle and closing nfq handle.
    		if (queue_h != NULL) {
    			nfq_destroy_queue(queue_h);
    			queue_h = NULL;
    
    
    		if (nfq_h != NULL) {
    			nfq_close(nfq_h);
    			nfq_h = NULL;
    		}
    
    		// Terminate the thread
    		int rc = pthread_cancel(ptid);
    		if (rc != 0 && rc != ESRCH) {
    			log_error("Failed to terminate helper thread. Daemon EXIT");
    			exit(1);
    		}
    
    
    		pthread_join(ptid, NULL);  // Wait for thread to terminate
    
    	free_all(&bbfdm_ctx.ubus_ctx);  // Free all allocated resources
    
    	// Call function to update the global structures.
    	if (read_config(&serv_enabled)) {
    		log_error("Failed to read config");
    		exit(1);
    
    	if (serv_enabled) { // parental control service enabled
    
    		if (register_bundle_update(&bbfdm_ctx.ubus_ctx) != 0) {
    			log_error("Failed to register bundle_update event");
    			exit(1);
    		}
    
    		call_bundle_sync_script();
    
    
    		// Create and add nfqueue FD in uloop
    		if (register_nfqueue_fd() != 0) {
    			log_error("uloop_fd_add failed");
    			exit(1);
    		}
    
    		// Start thread to update dns-cache
    		if (pthread_create(&ptid, NULL, &send_dns_queries, NULL) != 0) {
    			log_error("pthread creation failed, Daemon EXIT");
    			exit(1);
    		}
    	}
    }
    
    static int register_config_change(struct ubus_context *uctx)
    {
    	int ret;
    
    
    	memset(&config_reload_ev, 0, sizeof(struct ubus_event_handler));
    	config_reload_ev.cb = config_reload_cb;
    
    	ret = ubus_register_event_handler(uctx, &config_reload_ev, "parentalcontrol.reload");
    
    static void unregister_config_change(struct ubus_context *uctx)
    {
    	// unregister ubus handler if it exists
    	if (uctx) {
    		ubus_unregister_event_handler(uctx, &config_reload_ev);
    	}
    }
    
    Vivek Dutta's avatar
    Vivek Dutta committed
    static void usage(char *prog)
    {
    	fprintf(stderr, "Usage: %s [options]\n", prog);
    	fprintf(stderr, "\n");
    	fprintf(stderr, "options:\n");
    	fprintf(stderr, "    -l <log level>  As per syslog, 0 to 7\n");
    
    	fprintf(stderr, "    -d <schema dm>  Display the schema data model supported by micro-service\n");
    
    Vivek Dutta's avatar
    Vivek Dutta committed
    	fprintf(stderr, "    -h <help>       To print this help menu\n");
    	fprintf(stderr, "\n");
    }
    
    
    int main(int argc, char **argv)
    {
    
    Vivek Dutta's avatar
    Vivek Dutta committed
    	int log_level = 6; // default LOG_INFO
    
    
    	while ((ch = getopt(argc, argv, "hdl:")) != -1) {
    
    Vivek Dutta's avatar
    Vivek Dutta committed
    		switch (ch) {
    		case 'l':
    			log_level = (int)strtoul(optarg, NULL, 10);
    			if (log_level < 0 || log_level > 7)
    				log_level = 6;
    			break;
    
    Vivek Dutta's avatar
    Vivek Dutta committed
    		case 'h':
    			usage(argv[0]);
    			exit(0);
    		default:
    			break;
    		}
    	}
    
    
    	memset(&bbfdm_ctx, 0, sizeof(struct bbfdm_context));
    	bbfdm_ubus_set_service_name(&bbfdm_ctx, "parentalcontrol");
    
    Vivek Dutta's avatar
    Vivek Dutta committed
    	bbfdm_ubus_set_log_level(log_level);
    
    	bbfdm_ubus_load_data_model(tDynamicObj);
    
    
    	if (dm_type > 0) {
    		int res = bbfdm_print_data_model_schema(&bbfdm_ctx, dm_type);
    		exit(res);
    	}
    
    	openlog("urlfilter", LOG_CONS | LOG_PID | LOG_NDELAY, LOG_LOCAL1);
    
    
    #ifdef PARENTAL_CONTROL_ULRFILTER
    
    	// Call function to update the global structures.
    
    	if (read_config(&serv_enabled)) {
    
    		log_error("failed to read config");
    
    	// Create a list of dns server IPs.
    	if (init_dns_server_list() != 0) {
    		log_error("error in initialising dns server ip");
    	}
    
    
    	queue_h = NULL;
    	nfq_h = NULL;
    
    	if (bbfdm_ubus_regiter_init(&bbfdm_ctx)) {
    		log_error("Failed to register ubus");
    
    #ifdef PARENTAL_CONTROL_ULRFILTER
    
    	if (register_config_change(&bbfdm_ctx.ubus_ctx) != 0) {
    		log_error("Failed to register reload event");
    		goto exit;
    	}
    
    	if (serv_enabled) { // parental control service enabled
    
    		if (register_bundle_update(&bbfdm_ctx.ubus_ctx) != 0) {
    			log_error("Failed to register bundle_update event");
    			goto exit;
    		}
    
    		call_bundle_sync_script();
    
    
    		if (register_nfqueue_fd() != 0) {
    			log_error("url filter: uloop_fd_add failed");
    			goto exit;
    		}
    
    		// Create a thread which would send out dns query
    		// for all the blacklisted urls to create a dns cache.
    		if (pthread_create(&ptid, NULL, &send_dns_queries, NULL) != 0) {
    			log_error("pthread create failed");
    			goto destroy;
    		}
    
    
    	// Main loop of urlfilter
    	uloop_run();
    
    
    #ifdef PARENTAL_CONTROL_ULRFILTER
    
    	// Terminate the thread before exiting
    	if (serv_enabled) {
    		pthread_cancel(ptid);
    	}
    
    
    destroy:
    	// Destroying the queue handle and closing nfq handle.
    
    	if (queue_h != NULL)
    		nfq_destroy_queue(queue_h);
    
    	if (nfq_h != NULL)
    		nfq_close(nfq_h);
    
    #ifdef PARENTAL_CONTROL_ULRFILTER
    
    	free_all(&bbfdm_ctx.ubus_ctx);  // Free all allocated resources
    
    	unregister_config_change(&bbfdm_ctx.ubus_ctx);
    
    	bbfdm_ubus_regiter_free(&bbfdm_ctx);