diff --git a/Makefile b/Makefile index 72eac36..61b05eb 100644 --- a/Makefile +++ b/Makefile @@ -10,6 +10,7 @@ TEST_OBJS := $(notdir $(patsubst %.c,%.o, $(wildcard $(TESTS_DIR)/*.c))) CC := gcc CFLAGS := -Wall -Wextra -pedantic -Werror +CFLAGSTEST := -Wall -Wextra -pedantic -Werror -fsanitize=address -g LDFLAGS := -lpcap -lpthread $(NAME): dir $(OBJS) @@ -19,13 +20,21 @@ $(OBJS): @$(CC) $(CFLAGS) -o $(BUILD_DIR)/$@ -c $(SRC_DIR)/$*.c test: dir $(TEST_OBJS) $(OBJS) - @$(CC) $(CFLAGS) -o $(TESTS_DIR)/$(BIN_DIR)/run_all_tests \ + @$(CC) $(CFLAGSTEST) -o $(TESTS_DIR)/$(BIN_DIR)/run_all_tests \ $(patsubst %,$(BUILD_DIR)/%, $(filter-out main.o, $(OBJS))) \ $(patsubst %,$(TESTS_DIR)/$(BUILD_DIR)/%, $(TEST_OBJS)) $(LDFLAGS) @sudo $(TESTS_DIR)/$(BIN_DIR)/run_all_tests - + @echo "\n-- SEPARATE LEAK TESTS --" + @$(MAKE) leaks + $(TEST_OBJS): - @$(CC) $(CFLAGS) -o $(TESTS_DIR)/$(BUILD_DIR)/$@ -c $(TESTS_DIR)/$*.c + @$(CC) $(CFLAGSTEST) -o $(TESTS_DIR)/$(BUILD_DIR)/$@ -c $(TESTS_DIR)/$*.c + +leaks: CFLAGS := $(CFLAGSTEST) +leaks: clean dir $(NAME) + @sudo chmod +x $(TESTS_DIR)/leaks.sh + @sudo $(TESTS_DIR)/leaks.sh + @$(MAKE) clean dir: @mkdir -p $(BIN_DIR) $(BUILD_DIR) $(TESTS_DIR)/$(BIN_DIR) \ @@ -35,4 +44,4 @@ clean: @rm -rf $(BUILD_DIR) $(BIN_DIR) $(TESTS_DIR)/$(BIN_DIR) \ $(TESTS_DIR)/$(BUILD_DIR) -.PHONY: dir test clean +.PHONY: dir test clean leaks diff --git a/README.md b/README.md index 4e09109..ebeab61 100644 --- a/README.md +++ b/README.md @@ -15,10 +15,14 @@ Disco is a cross-platform network utility available on Linux and macOS. It suppo - [Usage](#usage) - [Examples](#examples) - [Testing](#testing) + - [Integration Tests](#integration-tests) + - [Memory Leak Tests](#memory-leak-tests) - [Technical Details](#technical-details) - [Address Resolution Protocol (ARP)](#address-resolution-protocol-arp) - [ICMP Echo Request (Ping)](#icmp-echo-request-ping) - [TCP SYN Scanning](#tcp-syn-scanning) + - [OS Fingerprinting](#os-fingerprinting) + - [Network Diagnostics](#network-diagnostics) ## Installation 1. Clone the repository @@ -69,7 +73,7 @@ disco - network utility for host discovery and port enumeration author: pilsnerfrajz usage: disco target [-h] [-p ports] [-o] [-n] [-P] [-a] [-S] - [-w file] [-f] + [-w file] [-f] options: target : host to scan (IP address or domain) -p, --ports : ports to scan, e.g., -p 1-1024 or -p 21,22,80 @@ -98,6 +102,8 @@ sudo ./bin/disco 127.0.0.1 -n -p 1-65535 ``` ## Testing + +### Integration Tests The program includes comprehensive **integration tests** that validate real network functionality. Run with `make test` from the project root to test: - ARP - Requests to LAN devices @@ -111,13 +117,14 @@ The program includes comprehensive **integration tests** that validate real netw - Port scan of IPv4/IPv6 localhost - Port scan of LAN device - Port scan of IPv4/IPv6 external hosts -- CLI - - Setting all available CLI arguments - - Printing of usage message with `-h` flag +- OS Fingerprinting + - Windows, Linux and BSD-like (e.g. macOS) systems +- Memory leaks (see Section [Memory Leak Tests](#memory-leak-tests)) Some tests may fail due to hardcoded IP addresses and port numbers not accessible or open on the targets in your network. Test cases that involve localhost or domains should still pass however. -The future plan is to implement these tests in a CI pipeline using Docker to ensure working features, regardless of device and network configurations. +### Memory Leak Tests +Memory leak tests are included to ensure proper memory management. These tests utilize the `AddressSanitizer` available in `clang`. Run with `make leaks` from the project root to execute leak tests with various argument combinations. Each test will report if any memory leaks were detected. ## Technical Details Disco is implemented in C using `libpcap` for frame injection and packet filtering. This section describes the implementation of ARP, ping and port scanning in more detail for those interested. diff --git a/src/arp.c b/src/arp.c index 437ecf8..c75d65f 100644 --- a/src/arp.c +++ b/src/arp.c @@ -183,6 +183,9 @@ int arp(char *address) return PCAP_OPEN; } + /* Free memory */ + free(if_name); + if (pcap_inject(handle, &arp_frame, sizeof(arp_frame)) < 0) { pcap_close(handle); @@ -210,6 +213,9 @@ int arp(char *address) return PCAP_FILTER; } + /* Free filter malloc */ + pcap_freecode(&filter); + struct callback_data c_data = {0}; memcpy(&c_data.arp_frame, &arp_frame, sizeof(c_data.arp_frame)); c_data.reply_found = 0; diff --git a/src/ping.c b/src/ping.c index afcf402..92784b6 100644 --- a/src/ping.c +++ b/src/ping.c @@ -283,8 +283,7 @@ int ping(char *address, int tries) free(reply_hdr); } } - - if (dst->ai_family == AF_INET6) + else if (dst->ai_family == AF_INET6) { for (int attempt = 0; attempt < tries; attempt++) { diff --git a/src/syn_scan.c b/src/syn_scan.c index da90919..5cc9303 100644 --- a/src/syn_scan.c +++ b/src/syn_scan.c @@ -731,6 +731,10 @@ static int pcap_filter_setup(char *address, struct src_info src_info) { return PCAP_FILTER; } + + /* Free filter malloc */ + pcap_freecode(&filter); + return 0; } diff --git a/src/utils.c b/src/utils.c index 052d6f3..595d2bc 100644 --- a/src/utils.c +++ b/src/utils.c @@ -74,13 +74,16 @@ struct addrinfo *get_dst_addr_struct(char *dst, int sock_type) return NULL; } - res->ai_addr = malloc(sizeof(struct addrinfo)); + /* Clear struct in case there is garbage */ + memset(res, 0, sizeof(struct addrinfo)); + + res->ai_addr = malloc(temp->ai_addrlen); if (res->ai_addr == NULL) { freeaddrinfo(dst_info); return NULL; } - memcpy(res->ai_addr, temp->ai_addr, sizeof(struct addrinfo)); + memcpy(res->ai_addr, temp->ai_addr, temp->ai_addrlen); res->ai_family = temp->ai_family; res->ai_addrlen = temp->ai_addrlen; diff --git a/tests/fingerprint_test.c b/tests/fingerprint_test.c index afd6378..057dcb8 100644 --- a/tests/fingerprint_test.c +++ b/tests/fingerprint_test.c @@ -9,7 +9,7 @@ void fingerprint_tests(void) { - printf("-- Fingerprint Tests --\n"); + printf("-- FINGERPRINT TESTS --\n"); set_test_print_flag(0); diff --git a/tests/leaks.sh b/tests/leaks.sh new file mode 100755 index 0000000..c175f66 --- /dev/null +++ b/tests/leaks.sh @@ -0,0 +1,72 @@ +#!/bin/bash + +BIN="./bin/disco" + +run_test() { + ERR_LOG=$(mktemp) + + # run binary with argument + $BIN "$@" > /dev/null 2> "$ERR_LOG" + if grep -q "Sanitizer" "$ERR_LOG"; then + echo "❌ Leak test with arguments '$@': failed" + cat "$ERR_LOG" + exit 1 + else + echo "✅ Leak test with arguments '$@': passed" + fi + rm -f "$ERR_LOG" +} + +run_test "-h" +run_test --help +run_test + +run_test localhost -P +run_test localhost --ping-only +run_test localhost -a +run_test localhost --arp-only +run_test localhost -S +run_test localhost --syn-only +run_test localhost -n + +run_test localhost -p 80 +run_test localhost -p 22,80,443 +run_test localhost -p 1-100 +run_test localhost -p 1-5,80,8080-8090 + +run_test localhost -p 0 +run_test localhost -p 70000 +run_test localhost -p 80,,22 +run_test 0.0.0.0 -P +run_test 255.255.255.255 -P + +run_test localhost -P -w out.txt +rm -f out.txt + +run_test localhost -p 80 -o -f +run_test localhost -n -p 22,80 -w out.txt +rm -f out.txt + +run_test 192.168.1.1 -P +run_test 192.168.1.1 --ping-only +run_test 192.168.1.1 -a +run_test 192.168.1.1 --arp-only +run_test 192.168.1.1 -S +run_test 192.168.1.1 --syn-only +run_test 192.168.1.1 -n + +run_test 192.168.1.1 -p 80 +run_test 192.168.1.1 -p 22,80,443 +run_test 192.168.1.1 -p 1-100 +run_test 192.168.1.1 -p 1-5,80,8080-8090 + +run_test 192.168.1.1 -p 0 +run_test 192.168.1.1 -p 70000 +run_test 192.168.1.1 -p 80,,22 + +run_test 192.168.1.1 -P -w out.txt +rm -f out.txt + +run_test 192.168.1.1 -p 80 -o -f +run_test 192.168.1.1 -n -p 22,80 -w out.txt +rm -f out.txt \ No newline at end of file