From b05ada45e9b9b0cc16219ee20cc7cea539e570aa Mon Sep 17 00:00:00 2001 From: DL6ER Date: Sat, 13 May 2023 12:40:39 +0200 Subject: [PATCH 01/23] Move dhcp-discover into a dedicated "tools" target Signed-off-by: DL6ER --- src/CMakeLists.txt | 4 ++-- src/args.c | 2 +- src/tools/CMakeLists.txt | 18 ++++++++++++++++++ src/{ => tools}/dhcp-discover.c | 0 src/{ => tools}/dhcp-discover.h | 0 5 files changed, 21 insertions(+), 3 deletions(-) create mode 100644 src/tools/CMakeLists.txt rename src/{ => tools}/dhcp-discover.c (100%) rename src/{ => tools}/dhcp-discover.h (100%) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index e7138179..cee7b1b3 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -116,8 +116,6 @@ set(sources daemon.h datastructure.c datastructure.h - dhcp-discover.c - dhcp-discover.h dnsmasq_interface.c dnsmasq_interface.h edns0.c @@ -180,6 +178,7 @@ add_executable(pihole-FTL $ $ $ + $ ) if(STATIC STREQUAL "true") set_target_properties(pihole-FTL PROPERTIES LINK_SEARCH_START_STATIC ON) @@ -253,3 +252,4 @@ add_subdirectory(lua) add_subdirectory(lua/scripts) add_subdirectory(tre-regex) add_subdirectory(syscalls) +add_subdirectory(tools) diff --git a/src/args.c b/src/args.c index e5917f15..2df29b46 100644 --- a/src/args.c +++ b/src/args.c @@ -33,7 +33,7 @@ // LUA dependencies #include "lua/ftl_lua.h" // run_dhcp_discover() -#include "dhcp-discover.h" +#include "tools/dhcp-discover.h" // defined in dnsmasq.c extern void print_dnsmasq_version(const char *yellow, const char *green, const char *bold, const char *normal); diff --git a/src/tools/CMakeLists.txt b/src/tools/CMakeLists.txt new file mode 100644 index 00000000..d13a5942 --- /dev/null +++ b/src/tools/CMakeLists.txt @@ -0,0 +1,18 @@ +# Pi-hole: A black hole for Internet advertisements +# (c) 2020 Pi-hole, LLC (https://pi-hole.net) +# Network-wide ad blocking via your own hardware. +# +# FTL Engine +# /src/tools/CMakeList.txt +# +# This file is copyright under the latest version of the EUPL. +# Please see LICENSE file for your rights under this license. + +set(tools_sources + dhcp-discover.c + dhcp-discover.h + ) + +add_library(tools OBJECT ${tools_sources}) +target_compile_options(tools PRIVATE "${EXTRAWARN}") +target_include_directories(tools PRIVATE ${PROJECT_SOURCE_DIR}/src) diff --git a/src/dhcp-discover.c b/src/tools/dhcp-discover.c similarity index 100% rename from src/dhcp-discover.c rename to src/tools/dhcp-discover.c diff --git a/src/dhcp-discover.h b/src/tools/dhcp-discover.h similarity index 100% rename from src/dhcp-discover.h rename to src/tools/dhcp-discover.h From 29a33102f3b63272f86f54e49ce9957eeca973ad Mon Sep 17 00:00:00 2001 From: DL6ER Date: Sat, 13 May 2023 20:51:02 +0200 Subject: [PATCH 02/23] Add pihole-FTL arp-scan [{-v,-a}] Signed-off-by: DL6ER --- src/args.c | 37 ++- src/syscalls/recvfrom.c | 11 +- src/syscalls/sendto.c | 4 +- src/tools/CMakeLists.txt | 2 + src/tools/arp-scan.c | 486 ++++++++++++++++++++++++++++++++++++++ src/tools/arp-scan.h | 16 ++ src/tools/dhcp-discover.c | 2 +- src/tools/dhcp-discover.h | 1 + 8 files changed, 542 insertions(+), 17 deletions(-) create mode 100644 src/tools/arp-scan.c create mode 100644 src/tools/arp-scan.h diff --git a/src/args.c b/src/args.c index 2df29b46..f86a19e3 100644 --- a/src/args.c +++ b/src/args.c @@ -34,6 +34,8 @@ #include "lua/ftl_lua.h" // run_dhcp_discover() #include "tools/dhcp-discover.h" +// run_arp_scan() +#include "tools/arp-scan.h" // defined in dnsmasq.c extern void print_dnsmasq_version(const char *yellow, const char *green, const char *bold, const char *normal); @@ -163,6 +165,24 @@ void parse_args(int argc, char* argv[]) (argc > 1 && strEndsWith(argv[1], ".db"))) exit(sqlite3_shell_main(argc, argv)); + // DHCP discovery mode + if(argc > 1 && strcmp(argv[1], "dhcp-discover") == 0) + { + // Enable stdout printing + cli_mode = true; + exit(run_dhcp_discover()); + } + + // ARP scanning mode + if(argc > 1 && strcmp(argv[1], "arp-scan") == 0) + { + // Enable stdout printing + cli_mode = true; + const bool verbose = argc > 2 && strcmp(argv[2], "-v") == 0; + const bool arp_all = argc > 2 && strcmp(argv[2], "-a") == 0; + exit(run_arp_scan(verbose, arp_all)); + } + // start from 1, as argv[0] is the executable name for(int i = 1; i < argc; i++) { @@ -415,14 +435,6 @@ void parse_args(int argc, char* argv[]) } } - // Regex test mode - if(strcmp(argv[i], "dhcp-discover") == 0) - { - // Enable stdout printing - cli_mode = true; - exit(run_dhcp_discover()); - } - // List of implemented arguments if(strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "help") == 0 || strcmp(argv[i], "--help") == 0) { @@ -495,13 +507,18 @@ void parse_args(int argc, char* argv[]) printf("%sDebugging and special use:%s\n", yellow, normal); printf("\t%sd%s, %sdebug%s Enter debugging mode\n", green, normal, green, normal); - printf("\t%stest%s Don't start pihole-FTL but\n", green, normal); - printf("\t instead quit immediately\n"); + printf("\t%stest%s Don't start pihole-FTL but instead\n", green, normal); + printf("\t quit immediately\n"); printf("\t%s-f%s, %sno-daemon%s Don't go into daemon mode\n\n", green, normal, green, normal); printf("%sOther:%s\n", yellow, normal); printf("\t%sdhcp-discover%s Discover DHCP servers in the local\n", green, normal); printf("\t network\n"); + printf("\t%sarp-scan %s[{-v/-a}]%s Use ARP to scan local network for\n", green, cyan, normal); + printf("\t possible IP conflicts\n"); + printf("\t Append %s-v%s for verbose output mode\n", cyan, normal); + printf("\t Append %s-a%s to force scan on all\n", cyan, normal); + printf("\t interfaces\n"); printf("\t%s-h%s, %shelp%s Display this help and exit\n\n", green, normal, green, normal); exit(EXIT_SUCCESS); } diff --git a/src/syscalls/recvfrom.c b/src/syscalls/recvfrom.c index 8db0ac46..4dff7928 100644 --- a/src/syscalls/recvfrom.c +++ b/src/syscalls/recvfrom.c @@ -32,11 +32,14 @@ ssize_t FTLrecvfrom(int sockfd, void *buf, size_t len, int flags, struct sockadd // Backup errno value const int _errno = errno; - // Final error checking (may have failed for some other reason then an - // EINTR = interrupted system call) - if(ret < 0) + // Final error checking. May have failed for some other reason then an + // EINTR = interrupted system call. In that case, log a warning However, + // if the error is EAGAIN, this is not an error, but just a non-blocking + // socket that has no data available or we ran into an (expected) + // timeout. In that case, do not log a warning + if(ret < 0 && errno != EAGAIN) logg("WARN: Could not recvfrom() in %s() (%s:%i): %s", - func, file, line, strerror(errno)); + func, file, line, strerror(errno)); // Restore errno value errno = _errno; diff --git a/src/syscalls/sendto.c b/src/syscalls/sendto.c index cc7234c9..846c571e 100644 --- a/src/syscalls/sendto.c +++ b/src/syscalls/sendto.c @@ -33,8 +33,8 @@ ssize_t FTLsendto(int sockfd, void *buf, size_t len, int flags, const struct soc const int _errno = errno; // Final error checking (may have failed for some other reason then an - // EINTR = interrupted system call) - if(ret < 0) + // EINTR = interrupted system call), also ignore EPROTONOSUPPORT (ARP scanning) + if(ret < 0 && errno != EPROTONOSUPPORT) logg("WARN: Could not sendto() in %s() (%s:%i): %s", func, file, line, strerror(errno)); diff --git a/src/tools/CMakeLists.txt b/src/tools/CMakeLists.txt index d13a5942..47de88b1 100644 --- a/src/tools/CMakeLists.txt +++ b/src/tools/CMakeLists.txt @@ -9,6 +9,8 @@ # Please see LICENSE file for your rights under this license. set(tools_sources + arp-scan.c + arp-scan.h dhcp-discover.c dhcp-discover.h ) diff --git a/src/tools/arp-scan.c b/src/tools/arp-scan.c new file mode 100644 index 00000000..63bbdc29 --- /dev/null +++ b/src/tools/arp-scan.c @@ -0,0 +1,486 @@ +/* Pi-hole: A black hole for Internet advertisements +* (c) 2023 Pi-hole, LLC (https://pi-hole.net) +* Network-wide ad blocking via your own hardware. +* +* FTL Engine +* ARP scanning routines +* +* This file is copyright under the latest version of the EUPL. +* Please see LICENSE file for your rights under this license. */ +// Inspired by https://stackoverflow.com/a/39287433 but heavily modified + +#include "FTL.h" +#include "arp-scan.h" +#include "log.h" +// get_hardware_address() +#include "dhcp-discover.h" + +#include +#include +#include +//htons etc +#include + +// How many threads do we spawn at maximum? +// This is also the limit for interfaces +// we scan for DHCP activity. +#define MAXTHREADS 32 +#define MAX_MACS 3 +#define NUM_SCANS 10 +#define ARP_TIMEOUT 1 + +// Global lock used by all threads +static pthread_mutex_t lock; +static bool arp_verbose = false; +static bool arp_all = false; + +#define PROTO_ARP 0x0806 +#define ETH2_HEADER_LEN 14 +#define HW_TYPE 1 +#define MAC_LENGTH 6 +#define IPV4_LENGTH 4 +#define ARP_REQUEST 0x01 +#define ARP_REPLY 0x02 +#define BUF_SIZE 60 + +#pragma pack(push, 1) + +// ARP header struct +// See https://en.wikipedia.org/wiki/Address_Resolution_Protocol#Packet_structure +struct arp_header { + unsigned short hardware_type; + unsigned short protocol_type; + unsigned char hardware_len; + unsigned char protocol_len; + unsigned short opcode; + unsigned char sender_mac[MAC_LENGTH]; + unsigned char sender_ip[IPV4_LENGTH]; + unsigned char target_mac[MAC_LENGTH]; + unsigned char target_ip[IPV4_LENGTH]; +}; +#pragma pack(pop) + +struct arp_result { + unsigned int replied[NUM_SCANS]; + unsigned char mac[MAX_MACS][MAC_LENGTH]; +}; + +// Sends multiple ARP who-has request on interface ifindex, using source mac src_mac and source ip src_ip. +// Interates over all IP addresses in the range of dst_ip/cidr. +static int send_arps(const int fd, const int ifindex, const char *iface, const unsigned char *src_mac, + struct in_addr *src_ip, struct in_addr dst_ip, const int dst_cidr) +{ + int err = -1; + unsigned char buffer[BUF_SIZE]; + memset(buffer, 0, sizeof(buffer)); + + // Construct the Ethernet header + struct sockaddr_ll socket_address; + socket_address.sll_family = AF_PACKET; + socket_address.sll_protocol = htons(ETH_P_ARP); + socket_address.sll_ifindex = ifindex; + socket_address.sll_hatype = htons(ARPHRD_ETHER); + socket_address.sll_pkttype = PACKET_BROADCAST; + socket_address.sll_halen = MAC_LENGTH; + socket_address.sll_addr[6] = 0; + socket_address.sll_addr[7] = 0; + + struct ethhdr *send_req = (struct ethhdr *) buffer; + struct arp_header *arp_req = (struct arp_header *) (buffer + ETH2_HEADER_LEN); + ssize_t ret; + + // Destination is the broadcast address + memset(send_req->h_dest, 0xff, MAC_LENGTH); + + // Target MAC is zero (we don't know it) + memset(arp_req->target_mac, 0x00, MAC_LENGTH); + + // Source MAC to our own MAC address + memcpy(send_req->h_source, src_mac, MAC_LENGTH); + memcpy(arp_req->sender_mac, src_mac, MAC_LENGTH); + memcpy(socket_address.sll_addr, src_mac, MAC_LENGTH); + + // Protocol type is ARP + send_req->h_proto = htons(ETH_P_ARP); + + // Create ARP request + arp_req->hardware_type = htons(HW_TYPE); + arp_req->protocol_type = htons(ETH_P_IP); + arp_req->hardware_len = MAC_LENGTH; + arp_req->protocol_len = IPV4_LENGTH; + arp_req->opcode = htons(ARP_REQUEST); + + // Copy IP address to arp_req + memcpy(arp_req->sender_ip, &src_ip->s_addr, sizeof(src_ip->s_addr)); + + // Loop over all possible IP addresses in the range dst_ip/cidr + // We start at 1 because the first IP address has already been set above + for(unsigned int i = 0; i < (1u << (32 - dst_cidr)); i++) + { + // Fill in target IP address + memcpy(arp_req->target_ip, &dst_ip.s_addr, sizeof(dst_ip.s_addr)); + +#ifdef DEBUG + printf("Sending ARP request for %s@%s\n", inet_ntoa(*dst_ip), iface); +#endif + + // Send ARP request + ret = sendto(fd, buffer, 42, 0, (struct sockaddr *) &socket_address, sizeof(socket_address)); + if (ret == -1) + { + if(errno != EPROTONOSUPPORT) + printf("Unable to send ARP request for %s@%s: %s\n", + inet_ntoa(dst_ip), iface, strerror(errno)); + goto out; + } + + // Increment IP address + dst_ip.s_addr = htonl(ntohl(dst_ip.s_addr) + 1); + } + + err = 0; +out: + return err; +} + +static int create_arp_socket(const int ifindex, const char *iface) +{ + // Create socket for ARP communications + const int arp_socket = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ARP)); + if(arp_socket < 0) + { + printf("Unable to create socket for ARP communications on interface %s: %s\n", iface, strerror(errno)); + return -1; + } + + // Bind socket to interface + struct sockaddr_ll sll; + memset(&sll, 0, sizeof(struct sockaddr_ll)); + sll.sll_family = AF_PACKET; + sll.sll_ifindex = ifindex; + if (bind(arp_socket, (struct sockaddr*) &sll, sizeof(struct sockaddr_ll)) < 0) + { + printf("Unable to bind socket for ARP communications on interface %s: %s\n", iface, strerror(errno)); + close(arp_socket); + return -1; + } + + // Set timeout + struct timeval tv; + tv.tv_sec = ARP_TIMEOUT; + tv.tv_usec = 0; + if (setsockopt(arp_socket, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) < 0) + { + printf("Unable to set timeout for ARP communications on interface %s: %s\n", iface, strerror(errno)); + close(arp_socket); + return -1; + } + + return arp_socket; +} + +// Read all ARP responses +static ssize_t read_arp(const int fd, const char *iface, struct in_addr *dst_ip, + struct arp_result *result, const size_t result_len, const unsigned int scan_id) +{ + ssize_t ret = 0; + unsigned char buffer[BUF_SIZE]; + + // Read ARP responses + while(ret >= 0) + { + ret = recvfrom(fd, buffer, BUF_SIZE, 0, NULL, NULL); + if (ret == -1) + { + if(errno == EAGAIN) + { + // Timeout + ret = 0; + break; + } + + // Error + printf("recvfrom(): %s", strerror(errno)); + break; + } + struct ethhdr *rcv_resp = (struct ethhdr *) buffer; + struct arp_header *arp_resp = (struct arp_header *) (buffer + ETH2_HEADER_LEN); + if (ntohs(rcv_resp->h_proto) != PROTO_ARP) + { +#ifdef DEBUG + printf("Not an ARP packet"); +#endif + continue; + } + if (ntohs(arp_resp->opcode) != ARP_REPLY) + { +#ifdef DEBUG + printf("Not an ARP reply"); +#endif + continue; + } +#ifdef DEBUG + printf("received ARP len=%ld", ret); +#endif + struct in_addr sender_a; + memcpy(&sender_a.s_addr, arp_resp->sender_ip, sizeof(sender_a.s_addr)); + +#ifdef DEBUG + printf("%-16s %-20s\t%02x:%02x:%02x:%02x:%02x:%02x", + iface, inet_ntoa(sender_a), + arp_resp->sender_mac[0], + arp_resp->sender_mac[1], + arp_resp->sender_mac[2], + arp_resp->sender_mac[3], + arp_resp->sender_mac[4], + arp_resp->sender_mac[5]); +#endif + + // Check if we have already found this IP address + uint32_t i = ntohl(sender_a.s_addr) - ntohl(dst_ip->s_addr); + if(i >= result_len) + { + printf("Received IP address %s out of range\n", inet_ntoa(sender_a)); + continue; + } + + // Memorize that we have received a reply for this IP address + result[i].replied[scan_id]++; + + // Save MAC address + for(unsigned int j = 0; j < MAX_MACS; j++) + { + // Check if received MAC is already stored in result[i].mac[j] + if(memcmp(result[i].mac[j], arp_resp->sender_mac, MAC_LENGTH) == 0) + { + break; + } + // Check if result[i].mac[j] is all-zero + if(memcmp(result[i].mac[j], "\x00\x00\x00\x00\x00\x00", MAC_LENGTH) == 0) + { + // Copy MAC address to result[i].mac[j] + memcpy(result[i].mac[j], arp_resp->sender_mac, sizeof(arp_resp->sender_mac)); + break; + } + } + } + + return ret; +} + +// Convert netmask to CIDR +static int netmask_to_cidr(struct in_addr *addr) +{ + // Count the number of set bits in an unsigned integer + return __builtin_popcount(addr->s_addr); +} + +static void *arp_scan_iface(void *args) +{ + // Get interface details + struct ifaddrs *ifa = (struct ifaddrs*)args; + + // Get interface name + const char *iface = ifa->ifa_name; + + // Set interface name as thread name + prctl(PR_SET_NAME, iface, 0, 0, 0); + + // Get interface IPv4 address + struct sockaddr_in src_addr = { 0 }; + memcpy(&src_addr, ((struct ifaddrs*)args)->ifa_addr, sizeof(src_addr)); + char ipstr[INET_ADDRSTRLEN] = { 0 }; + inet_ntop(AF_INET, &src_addr.sin_addr, ipstr, INET_ADDRSTRLEN); + + // Get interface netmask + struct sockaddr_in mask = { 0 }; + memcpy(&mask, ((struct ifaddrs*)args)->ifa_netmask, sizeof(mask)); + // char netmask[INET_ADDRSTRLEN] = { 0 }; + // inet_ntop(AF_INET, &mask.sin_addr, netmask, INET_ADDRSTRLEN); + + // Convert subnet to CIDR + const int cidr = netmask_to_cidr(&mask.sin_addr); + + // Get interface index + const int ifindex = if_nametoindex(iface); + + // Scan only interfaces with CIDR >= 24 + if(cidr < 24 && !arp_all) + { + printf("Skipped interface %s (%s/%i)\n", iface, ipstr, cidr); + pthread_exit(NULL); + } + if(arp_verbose) + printf("Scanning interface %s (%s/%i)...\n", iface, ipstr, cidr); + + // Create socket for ARP communications + const int arp_socket = create_arp_socket(ifindex, iface); + + // Cannot create socket, likely a permission error + if(arp_socket < 0) + pthread_exit(NULL); + + // Get hardware address of client machine + unsigned char mac[16] = { 0 }; + get_hardware_address(arp_socket, iface, mac); + + // Define destination IP address by masking source IP with netmask + struct in_addr dst_addr = { 0 }; + dst_addr.s_addr = src_addr.sin_addr.s_addr & mask.sin_addr.s_addr; + + // Allocate memory for ARP response buffer + const size_t arp_result_len = 1 << (32 - cidr); + struct arp_result *result = calloc(arp_result_len, sizeof(struct arp_result)); + + for(unsigned int scan_id = 0; scan_id < NUM_SCANS; scan_id++) + { +#ifdef DEBUG + printf("Scanning interface %s (%s/%i) for the %i. time\n", iface, ipstr, cidr, scan_id + 1); +#endif + // Send ARP requests to all IPs in subnet + if(send_arps(arp_socket, ifindex, iface, mac, &src_addr.sin_addr, dst_addr, cidr) != 0) + break; + + // Read ARP responses + if(read_arp(arp_socket, iface, &dst_addr, result, arp_result_len, scan_id) != 0) + break; + } + + // Check if there are any results + unsigned int replies = 0; + for(unsigned int i = 0; i < arp_result_len; i++) + for(unsigned int j = 0; j < NUM_SCANS; j++) + replies += result[i].replied[j]; + + if(pthread_mutex_lock(&lock) != 0) + return NULL; + + if(replies == 0) + { + printf("No devices found on interface %s (%s/%i)\n", iface, ipstr, cidr); + goto arp_scan_iface_end; + } + + // Print results + printf("ARP scan on interface %s (%s/%i) finished\n", iface, ipstr, cidr); + printf("%-20s %-16s %-17s Reply matrix\n", "IP address", "Interface", "MAC address"); + for(unsigned int i = 0; i < arp_result_len; i++) + { + // Check if IP address replied + bool replied = false, multiple_replies = false; + for(unsigned int j = 0; j < NUM_SCANS; j++) + { + if(result[i].replied[j] > 0) + { + replied = true; + multiple_replies |= result[i].replied[j] > 1; + } + } + if(!replied) + continue; + + // Convert IP address to string + struct in_addr ip = { 0 }; + ip.s_addr = htonl(ntohl(dst_addr.s_addr) + i); + inet_ntop(AF_INET, &ip, ipstr, INET_ADDRSTRLEN); + + // Print MAC addresses + unsigned int j = 0; + for(j = 0; j < MAX_MACS; j++) + { + // Check if result[i].mac[j] is all-zero + if(memcmp(result[i].mac[j], "\x00\x00\x00\x00\x00\x00", 6) == 0) + break; + + // Print MAC address + printf("%-20s %-16s %02x:%02x:%02x:%02x:%02x:%02x ", + ipstr, iface, + result[i].mac[j][0], + result[i].mac[j][1], + result[i].mac[j][2], + result[i].mac[j][3], + result[i].mac[j][4], + result[i].mac[j][5]); + + for(unsigned int k = 0; k < NUM_SCANS; k++) + { + printf(" %s", result[i].replied[k] > 0 ? "X" : "-"); + } + putc('\n', stdout); + } + + // Print warning if multiple MAC addresses replied + if(j > 1) + printf("WARNING: Multiple MAC addresses replied as %s\n", ipstr); + if(multiple_replies) + printf("WARNING: Received multiple replies for %s\n", ipstr); + } + putc('\n', stdout); + +arp_scan_iface_end: + if(pthread_mutex_unlock(&lock) != 0) + return NULL; + + // Close socket + close(arp_socket); + pthread_exit(NULL); +} + +int run_arp_scan(const bool verbose, const bool scan_all) +{ + arp_verbose = verbose; + arp_all = scan_all; + puts("Discovering IPv4 hosts on the network using the Address Resolution Protocol (ARP)...\n"); + + // Get interface names for available interfaces on this machine + // and launch a thread for each one + pthread_t scanthread[MAXTHREADS]; + pthread_attr_t attr; + // Initialize thread attributes object with default attribute values + pthread_attr_init(&attr); + + // Create processing/logging lock + pthread_mutexattr_t lock_attr = {}; + // Initialize the lock attributes + pthread_mutexattr_init(&lock_attr); + // Initialize the lock + pthread_mutex_init(&lock, &lock_attr); + // Destroy the lock attributes since we're done with it + pthread_mutexattr_destroy(&lock_attr); + + struct ifaddrs *addrs, *tmp; + getifaddrs(&addrs); + tmp = addrs; + + // Loop until there are no more interfaces available + // or we reached the maximum number of threads + int tid = 0; + while(tmp != NULL && tid < MAXTHREADS) + { + // Create a thread for interfaces of type AF_INET + if(tmp->ifa_addr && tmp->ifa_addr->sa_family == AF_INET) + { + if(pthread_create(&scanthread[tid], &attr, arp_scan_iface, tmp ) != 0) + { + printf("Unable to launch thread for interface %s, skipping...\n", + tmp->ifa_name); + continue; + } + + // Increase thread ID + tid++; + } + + // Advance to the next interface + tmp = tmp->ifa_next; + } + + // Wait for all threads to join back with us + for(tid--; tid > -1; tid--) + pthread_join(scanthread[tid], NULL); + + // Free linked-list of interfaces on this client + freeifaddrs(addrs); + + return EXIT_SUCCESS; +} diff --git a/src/tools/arp-scan.h b/src/tools/arp-scan.h new file mode 100644 index 00000000..90b5fec7 --- /dev/null +++ b/src/tools/arp-scan.h @@ -0,0 +1,16 @@ +/* Pi-hole: A black hole for Internet advertisements +* (c) 2023 Pi-hole, LLC (https://pi-hole.net) +* Network-wide ad blocking via your own hardware. +* +* FTL Engine +* ARP scanning prototypes +* +* This file is copyright under the latest version of the EUPL. +* Please see LICENSE file for your rights under this license. */ + +#ifndef ARP_SCAN_H +#define ARP_SCAN_H + +int run_arp_scan(const bool verbose, const bool scan_all); + +#endif // ARP_SCAN_H diff --git a/src/tools/dhcp-discover.c b/src/tools/dhcp-discover.c index 54713552..5e2613ea 100644 --- a/src/tools/dhcp-discover.c +++ b/src/tools/dhcp-discover.c @@ -128,7 +128,7 @@ static int create_dhcp_socket(const char *iname) } // determines hardware address on client machine -static int get_hardware_address(const int sock, const char *iname, unsigned char *mac) +int get_hardware_address(const int sock, const char *iname, unsigned char *mac) { struct ifreq ifr; strncpy((char *)&ifr.ifr_name, iname, sizeof(ifr.ifr_name)-1); diff --git a/src/tools/dhcp-discover.h b/src/tools/dhcp-discover.h index 7d9bc7e8..efd07c11 100644 --- a/src/tools/dhcp-discover.h +++ b/src/tools/dhcp-discover.h @@ -12,5 +12,6 @@ #define DHCP_DISCOVER_H int run_dhcp_discover(void); +int get_hardware_address(const int sock, const char *iname, unsigned char *mac); #endif // DHCP_DISCOVER_H From 36bd2a56e4e804c82332caca8d30f354a1106fd5 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Sat, 13 May 2023 22:22:17 +0200 Subject: [PATCH 03/23] Unify warning Signed-off-by: DL6ER --- src/tools/arp-scan.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/tools/arp-scan.c b/src/tools/arp-scan.c index 63bbdc29..cf47cf6c 100644 --- a/src/tools/arp-scan.c +++ b/src/tools/arp-scan.c @@ -66,7 +66,7 @@ struct arp_result { }; // Sends multiple ARP who-has request on interface ifindex, using source mac src_mac and source ip src_ip. -// Interates over all IP addresses in the range of dst_ip/cidr. +// Iterates over all IP addresses in the range of dst_ip/cidr. static int send_arps(const int fd, const int ifindex, const char *iface, const unsigned char *src_mac, struct in_addr *src_ip, struct in_addr dst_ip, const int dst_cidr) { @@ -409,10 +409,8 @@ static void *arp_scan_iface(void *args) putc('\n', stdout); } - // Print warning if multiple MAC addresses replied - if(j > 1) - printf("WARNING: Multiple MAC addresses replied as %s\n", ipstr); - if(multiple_replies) + // Print warning if we received multiple replies + if(j > 1 || multiple_replies) printf("WARNING: Received multiple replies for %s\n", ipstr); } putc('\n', stdout); From 26396bd20b9a77b788e695b874c93172b4ec35e1 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Sun, 14 May 2023 07:26:06 +0200 Subject: [PATCH 04/23] Use dedicated counters per MAC for a more accurate per-device reply matrix Signed-off-by: DL6ER --- src/tools/arp-scan.c | 88 +++++++++++++++++++++++--------------------- 1 file changed, 47 insertions(+), 41 deletions(-) diff --git a/src/tools/arp-scan.c b/src/tools/arp-scan.c index cf47cf6c..09aace8c 100644 --- a/src/tools/arp-scan.c +++ b/src/tools/arp-scan.c @@ -61,8 +61,10 @@ struct arp_header { #pragma pack(pop) struct arp_result { - unsigned int replied[NUM_SCANS]; - unsigned char mac[MAX_MACS][MAC_LENGTH]; + struct device { + unsigned int replied[NUM_SCANS]; + unsigned char mac[MAC_LENGTH]; + } device[MAX_MACS]; }; // Sends multiple ARP who-has request on interface ifindex, using source mac src_mac and source ip src_ip. @@ -244,30 +246,32 @@ static ssize_t read_arp(const int fd, const char *iface, struct in_addr *dst_ip, continue; } - // Memorize that we have received a reply for this IP address - result[i].replied[scan_id]++; - // Save MAC address - for(unsigned int j = 0; j < MAX_MACS; j++) + unsigned int j = 0; + for(; j < MAX_MACS; j++) { - // Check if received MAC is already stored in result[i].mac[j] - if(memcmp(result[i].mac[j], arp_resp->sender_mac, MAC_LENGTH) == 0) + // Check if received MAC is already stored in result[i].device[j].mac + if(memcmp(result[i].device[j].mac, arp_resp->sender_mac, MAC_LENGTH) == 0) { break; } - // Check if result[i].mac[j] is all-zero - if(memcmp(result[i].mac[j], "\x00\x00\x00\x00\x00\x00", MAC_LENGTH) == 0) + // Check if result[i].device[j].mac is all-zero + if(memcmp(result[i].device[j].mac, "\x00\x00\x00\x00\x00\x00", MAC_LENGTH) == 0) { - // Copy MAC address to result[i].mac[j] - memcpy(result[i].mac[j], arp_resp->sender_mac, sizeof(arp_resp->sender_mac)); + // Copy MAC address to result[i].device[j].mac + memcpy(result[i].device[j].mac, arp_resp->sender_mac, sizeof(arp_resp->sender_mac)); break; } } + + // Memorize that we have received a reply for this IP address + result[i].device[j].replied[scan_id]++; } return ret; } + // Convert netmask to CIDR static int netmask_to_cidr(struct in_addr *addr) { @@ -349,8 +353,9 @@ static void *arp_scan_iface(void *args) // Check if there are any results unsigned int replies = 0; for(unsigned int i = 0; i < arp_result_len; i++) - for(unsigned int j = 0; j < NUM_SCANS; j++) - replies += result[i].replied[j]; + for(unsigned int j = 0; j < MAX_MACS; j++) + for(unsigned int k = 0; k < NUM_SCANS; k++) + replies += result[i].device[j].replied[k]; if(pthread_mutex_lock(&lock) != 0) return NULL; @@ -366,51 +371,52 @@ static void *arp_scan_iface(void *args) printf("%-20s %-16s %-17s Reply matrix\n", "IP address", "Interface", "MAC address"); for(unsigned int i = 0; i < arp_result_len; i++) { - // Check if IP address replied - bool replied = false, multiple_replies = false; - for(unsigned int j = 0; j < NUM_SCANS; j++) - { - if(result[i].replied[j] > 0) - { - replied = true; - multiple_replies |= result[i].replied[j] > 1; - } - } - if(!replied) - continue; - - // Convert IP address to string - struct in_addr ip = { 0 }; - ip.s_addr = htonl(ntohl(dst_addr.s_addr) + i); - inet_ntop(AF_INET, &ip, ipstr, INET_ADDRSTRLEN); + unsigned int j = 0, replied_devices = 0; + bool multiple_replies = false; // Print MAC addresses - unsigned int j = 0; for(j = 0; j < MAX_MACS; j++) { + // Check if IP address replied + bool replied = false; + for(unsigned int k = 0; k < NUM_SCANS; k++) + { + replied |= result[i].device[j].replied[k] > 0; + multiple_replies |= result[i].device[j].replied[k] > 1; + } + if(!replied) + continue; + + // Check if IP address replied multiple times from different MAC address + replied_devices++; + + // Convert IP address to string + struct in_addr ip = { 0 }; + ip.s_addr = htonl(ntohl(dst_addr.s_addr) + i); + inet_ntop(AF_INET, &ip, ipstr, INET_ADDRSTRLEN); // Check if result[i].mac[j] is all-zero - if(memcmp(result[i].mac[j], "\x00\x00\x00\x00\x00\x00", 6) == 0) + if(memcmp(result[i].device[j].mac, "\x00\x00\x00\x00\x00\x00", 6) == 0) break; // Print MAC address printf("%-20s %-16s %02x:%02x:%02x:%02x:%02x:%02x ", ipstr, iface, - result[i].mac[j][0], - result[i].mac[j][1], - result[i].mac[j][2], - result[i].mac[j][3], - result[i].mac[j][4], - result[i].mac[j][5]); + result[i].device[j].mac[0], + result[i].device[j].mac[1], + result[i].device[j].mac[2], + result[i].device[j].mac[3], + result[i].device[j].mac[4], + result[i].device[j].mac[5]); for(unsigned int k = 0; k < NUM_SCANS; k++) { - printf(" %s", result[i].replied[k] > 0 ? "X" : "-"); + printf(" %s", result[i].device[j].replied[k] > 0 ? "X" : "-"); } putc('\n', stdout); } // Print warning if we received multiple replies - if(j > 1 || multiple_replies) + if(replied_devices > 1 || multiple_replies) printf("WARNING: Received multiple replies for %s\n", ipstr); } putc('\n', stdout); From 65d5b121278cb57972f13c483f3a0a8f5b61b181 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Sun, 14 May 2023 07:34:18 +0200 Subject: [PATCH 05/23] Add our own address to the scan results so we can detect IP conflicts also here Signed-off-by: DL6ER --- src/tools/arp-scan.c | 75 ++++++++++++++++++++++++++------------------ 1 file changed, 44 insertions(+), 31 deletions(-) diff --git a/src/tools/arp-scan.c b/src/tools/arp-scan.c index 09aace8c..76430002 100644 --- a/src/tools/arp-scan.c +++ b/src/tools/arp-scan.c @@ -181,6 +181,40 @@ static int create_arp_socket(const int ifindex, const char *iface) return arp_socket; } +static void add_result(const char *iface, struct in_addr *rcv_ip, struct in_addr *dst_ip, unsigned char *sender_mac, + struct arp_result *result, const size_t result_len, const unsigned int scan_id) +{ + + // Check if we have already found this IP address + uint32_t i = ntohl(rcv_ip->s_addr) - ntohl(dst_ip->s_addr); + if(i >= result_len) + { + printf("Received IP address %s out of range\n", inet_ntoa(*rcv_ip)); + return; + } + + // Save MAC address + unsigned int j = 0; + for(; j < MAX_MACS; j++) + { + // Check if received MAC is already stored in result[i].device[j].mac + if(memcmp(result[i].device[j].mac, sender_mac, MAC_LENGTH) == 0) + { + break; + } + // Check if result[i].device[j].mac is all-zero + if(memcmp(result[i].device[j].mac, "\x00\x00\x00\x00\x00\x00", MAC_LENGTH) == 0) + { + // Copy MAC address to result[i].device[j].mac + memcpy(result[i].device[j].mac, sender_mac, MAC_LENGTH); + break; + } + } + + // Memorize that we have received a reply for this IP address + result[i].device[j].replied[scan_id]++; +} + // Read all ARP responses static ssize_t read_arp(const int fd, const char *iface, struct in_addr *dst_ip, struct arp_result *result, const size_t result_len, const unsigned int scan_id) @@ -237,41 +271,12 @@ static ssize_t read_arp(const int fd, const char *iface, struct in_addr *dst_ip, arp_resp->sender_mac[4], arp_resp->sender_mac[5]); #endif - - // Check if we have already found this IP address - uint32_t i = ntohl(sender_a.s_addr) - ntohl(dst_ip->s_addr); - if(i >= result_len) - { - printf("Received IP address %s out of range\n", inet_ntoa(sender_a)); - continue; - } - - // Save MAC address - unsigned int j = 0; - for(; j < MAX_MACS; j++) - { - // Check if received MAC is already stored in result[i].device[j].mac - if(memcmp(result[i].device[j].mac, arp_resp->sender_mac, MAC_LENGTH) == 0) - { - break; - } - // Check if result[i].device[j].mac is all-zero - if(memcmp(result[i].device[j].mac, "\x00\x00\x00\x00\x00\x00", MAC_LENGTH) == 0) - { - // Copy MAC address to result[i].device[j].mac - memcpy(result[i].device[j].mac, arp_resp->sender_mac, sizeof(arp_resp->sender_mac)); - break; - } - } - - // Memorize that we have received a reply for this IP address - result[i].device[j].replied[scan_id]++; + add_result(iface, &sender_a, dst_ip, arp_resp->sender_mac, result, result_len, scan_id); } return ret; } - // Convert netmask to CIDR static int netmask_to_cidr(struct in_addr *addr) { @@ -360,15 +365,23 @@ static void *arp_scan_iface(void *args) if(pthread_mutex_lock(&lock) != 0) return NULL; + // Exit early if there are no results if(replies == 0) { printf("No devices found on interface %s (%s/%i)\n", iface, ipstr, cidr); goto arp_scan_iface_end; } - // Print results + // If there is at least one result, print header printf("ARP scan on interface %s (%s/%i) finished\n", iface, ipstr, cidr); printf("%-20s %-16s %-17s Reply matrix\n", "IP address", "Interface", "MAC address"); + + // Add our own IP address to the results so IP conflicts can be detected + // (our own IP address is not included in the ARP scan) + for(unsigned int i = 0; i < NUM_SCANS; i++) + add_result(iface, &src_addr.sin_addr, &dst_addr, mac, result, arp_result_len, i); + + // Print results for(unsigned int i = 0; i < arp_result_len; i++) { unsigned int j = 0, replied_devices = 0; From 0e74485c15dfb5d2b60203e2286ff308c0373a88 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Sun, 14 May 2023 07:49:42 +0200 Subject: [PATCH 06/23] Include hostnames (if available) Signed-off-by: DL6ER --- src/tools/arp-scan.c | 34 +++++++++++++++++++++++++++------- 1 file changed, 27 insertions(+), 7 deletions(-) diff --git a/src/tools/arp-scan.c b/src/tools/arp-scan.c index 76430002..e549cfa2 100644 --- a/src/tools/arp-scan.c +++ b/src/tools/arp-scan.c @@ -25,8 +25,14 @@ // This is also the limit for interfaces // we scan for DHCP activity. #define MAXTHREADS 32 + +// How many MAC addresses do we store per IP address? #define MAX_MACS 3 + +// How many ARP requests do we send per IP address? #define NUM_SCANS 10 + +// How long do we wait for ARP replies in each scan [seconds]? #define ARP_TIMEOUT 1 // Global lock used by all threads @@ -34,6 +40,7 @@ static pthread_mutex_t lock; static bool arp_verbose = false; static bool arp_all = false; +// Protocol definitions #define PROTO_ARP 0x0806 #define ETH2_HEADER_LEN 14 #define HW_TYPE 1 @@ -43,10 +50,9 @@ static bool arp_all = false; #define ARP_REPLY 0x02 #define BUF_SIZE 60 -#pragma pack(push, 1) - // ARP header struct // See https://en.wikipedia.org/wiki/Address_Resolution_Protocol#Packet_structure +#pragma pack(push, 1) struct arp_header { unsigned short hardware_type; unsigned short protocol_type; @@ -284,6 +290,21 @@ static int netmask_to_cidr(struct in_addr *addr) return __builtin_popcount(addr->s_addr); } +static const char *get_hostname(const struct in_addr *addr) +{ + // Get hostname + struct hostent *he = gethostbyaddr(&addr->s_addr, sizeof(addr->s_addr), AF_INET); + if(he == NULL) + return "N/A"; + + // Allow at most 24 characters for the hostname + static char hostname[25] = { 0 }; + strncpy(hostname, he->h_name, 24); + + // Return hostname + return hostname; +} + static void *arp_scan_iface(void *args) { // Get interface details @@ -374,7 +395,7 @@ static void *arp_scan_iface(void *args) // If there is at least one result, print header printf("ARP scan on interface %s (%s/%i) finished\n", iface, ipstr, cidr); - printf("%-20s %-16s %-17s Reply matrix\n", "IP address", "Interface", "MAC address"); + printf("%-16s %-10s %-24s %-17s Reply matrix\n", "IP address", "Interface", "Hostname", "MAC address"); // Add our own IP address to the results so IP conflicts can be detected // (our own IP address is not included in the ARP scan) @@ -412,8 +433,8 @@ static void *arp_scan_iface(void *args) break; // Print MAC address - printf("%-20s %-16s %02x:%02x:%02x:%02x:%02x:%02x ", - ipstr, iface, + printf("%-16s %-10s %-24s %02x:%02x:%02x:%02x:%02x:%02x ", + ipstr, iface, get_hostname(&ip), result[i].device[j].mac[0], result[i].device[j].mac[1], result[i].device[j].mac[2], @@ -422,9 +443,8 @@ static void *arp_scan_iface(void *args) result[i].device[j].mac[5]); for(unsigned int k = 0; k < NUM_SCANS; k++) - { printf(" %s", result[i].device[j].replied[k] > 0 ? "X" : "-"); - } + putc('\n', stdout); } From 041092a6f4d033aa874a7520444f2adf952bd5a9 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Mon, 15 May 2023 19:50:46 +0200 Subject: [PATCH 07/23] Print progress in verbose arp-scanning mode Signed-off-by: DL6ER --- src/tools/arp-scan.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/tools/arp-scan.c b/src/tools/arp-scan.c index e549cfa2..3d7ccb8d 100644 --- a/src/tools/arp-scan.c +++ b/src/tools/arp-scan.c @@ -364,9 +364,9 @@ static void *arp_scan_iface(void *args) for(unsigned int scan_id = 0; scan_id < NUM_SCANS; scan_id++) { -#ifdef DEBUG - printf("Scanning interface %s (%s/%i) for the %i. time\n", iface, ipstr, cidr, scan_id + 1); -#endif + if(arp_verbose) + printf("Still scanning interface %s (%s/%i) %i%%...\n", iface, ipstr, cidr, 100*scan_id/NUM_SCANS); + // Send ARP requests to all IPs in subnet if(send_arps(arp_socket, ifindex, iface, mac, &src_addr.sin_addr, dst_addr, cidr) != 0) break; From 1e0f20b2bc7306561d06dcca0ea89746787deafc Mon Sep 17 00:00:00 2001 From: DL6ER Date: Mon, 15 May 2023 19:54:52 +0200 Subject: [PATCH 08/23] Print different warnings if we received multiple replies from (apparently) the same device or if we received replies for the same address from different MAC addresses Signed-off-by: DL6ER --- src/tools/arp-scan.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/tools/arp-scan.c b/src/tools/arp-scan.c index 3d7ccb8d..ffbf7378 100644 --- a/src/tools/arp-scan.c +++ b/src/tools/arp-scan.c @@ -406,7 +406,7 @@ static void *arp_scan_iface(void *args) for(unsigned int i = 0; i < arp_result_len; i++) { unsigned int j = 0, replied_devices = 0; - bool multiple_replies = false; + unsigned int multiple_replies = 0; // Print MAC addresses for(j = 0; j < MAX_MACS; j++) @@ -416,7 +416,7 @@ static void *arp_scan_iface(void *args) for(unsigned int k = 0; k < NUM_SCANS; k++) { replied |= result[i].device[j].replied[k] > 0; - multiple_replies |= result[i].device[j].replied[k] > 1; + multiple_replies += result[i].device[j].replied[k] > 1; } if(!replied) continue; @@ -449,8 +449,12 @@ static void *arp_scan_iface(void *args) } // Print warning if we received multiple replies - if(replied_devices > 1 || multiple_replies) - printf("WARNING: Received multiple replies for %s\n", ipstr); + if(replied_devices > 1) + printf("WARNING: Received replies for %s from %i devices\n", + ipstr, replied_devices); + if(multiple_replies > 0) + printf("WARNING: Received multiple replies for %s in %i scan%s\n", + ipstr, multiple_replies, multiple_replies > 1 ? "s" : ""); } putc('\n', stdout); From f1b4a338f04aa48536e91587b2e7e86b9885927a Mon Sep 17 00:00:00 2001 From: DL6ER Date: Mon, 15 May 2023 20:58:37 +0200 Subject: [PATCH 09/23] Consolidate output in main process Signed-off-by: DL6ER --- src/args.c | 6 +- src/tools/arp-scan.c | 248 +++++++++++++++++++++++++++++-------------- src/tools/arp-scan.h | 2 +- 3 files changed, 172 insertions(+), 84 deletions(-) diff --git a/src/args.c b/src/args.c index f86a19e3..14b4031d 100644 --- a/src/args.c +++ b/src/args.c @@ -178,9 +178,8 @@ void parse_args(int argc, char* argv[]) { // Enable stdout printing cli_mode = true; - const bool verbose = argc > 2 && strcmp(argv[2], "-v") == 0; const bool arp_all = argc > 2 && strcmp(argv[2], "-a") == 0; - exit(run_arp_scan(verbose, arp_all)); + exit(run_arp_scan(arp_all)); } // start from 1, as argv[0] is the executable name @@ -514,9 +513,8 @@ void parse_args(int argc, char* argv[]) printf("%sOther:%s\n", yellow, normal); printf("\t%sdhcp-discover%s Discover DHCP servers in the local\n", green, normal); printf("\t network\n"); - printf("\t%sarp-scan %s[{-v/-a}]%s Use ARP to scan local network for\n", green, cyan, normal); + printf("\t%sarp-scan %s[-a]%s Use ARP to scan local network for\n", green, cyan, normal); printf("\t possible IP conflicts\n"); - printf("\t Append %s-v%s for verbose output mode\n", cyan, normal); printf("\t Append %s-a%s to force scan on all\n", cyan, normal); printf("\t interfaces\n"); printf("\t%s-h%s, %shelp%s Display this help and exit\n\n", green, normal, green, normal); diff --git a/src/tools/arp-scan.c b/src/tools/arp-scan.c index ffbf7378..69e70241 100644 --- a/src/tools/arp-scan.c +++ b/src/tools/arp-scan.c @@ -14,6 +14,8 @@ #include "log.h" // get_hardware_address() #include "dhcp-discover.h" +// sleepms() +#include "timers.h" #include #include @@ -35,9 +37,7 @@ // How long do we wait for ARP replies in each scan [seconds]? #define ARP_TIMEOUT 1 -// Global lock used by all threads -static pthread_mutex_t lock; -static bool arp_verbose = false; +// Global constant static bool arp_all = false; // Protocol definitions @@ -73,6 +73,30 @@ struct arp_result { } device[MAX_MACS]; }; +enum status { + STATUS_INITIALIZING = 0, + STATUS_SKIPPED_CIDR_MISMATCH, + STATUS_SCANNING, + STATUS_ERROR, + STATUS_COMPLETE +}; + +struct thread_data { + int dst_cidr; + struct sockaddr_in src_addr; + struct sockaddr_in dst_addr; + struct sockaddr_in mask; + struct ifaddrs *ifa; + const char *iface; + struct arp_result *result; + size_t result_size; + enum status status; + char ipstr[INET_ADDRSTRLEN]; + unsigned char mac[16]; + unsigned int num_scans; + char *error; +}; + // Sends multiple ARP who-has request on interface ifindex, using source mac src_mac and source ip src_ip. // Iterates over all IP addresses in the range of dst_ip/cidr. static int send_arps(const int fd, const int ifindex, const char *iface, const unsigned char *src_mac, @@ -195,7 +219,7 @@ static void add_result(const char *iface, struct in_addr *rcv_ip, struct in_addr uint32_t i = ntohl(rcv_ip->s_addr) - ntohl(dst_ip->s_addr); if(i >= result_len) { - printf("Received IP address %s out of range\n", inet_ntoa(*rcv_ip)); + printf("Received IP address %s out of range for interface %s (%u >= %zu)\n", inet_ntoa(*rcv_ip), iface, i, result_len); return; } @@ -307,103 +331,132 @@ static const char *get_hostname(const struct in_addr *addr) static void *arp_scan_iface(void *args) { + // Get thread_data pointer + struct thread_data *thread_data = (struct thread_data*)args; + // Get interface details - struct ifaddrs *ifa = (struct ifaddrs*)args; + struct ifaddrs *ifa = thread_data->ifa; // Get interface name - const char *iface = ifa->ifa_name; + const char *iface = thread_data->iface; // Set interface name as thread name prctl(PR_SET_NAME, iface, 0, 0, 0); // Get interface IPv4 address - struct sockaddr_in src_addr = { 0 }; - memcpy(&src_addr, ((struct ifaddrs*)args)->ifa_addr, sizeof(src_addr)); - char ipstr[INET_ADDRSTRLEN] = { 0 }; - inet_ntop(AF_INET, &src_addr.sin_addr, ipstr, INET_ADDRSTRLEN); + memcpy(&thread_data->src_addr, ifa->ifa_addr, sizeof(thread_data->src_addr)); + inet_ntop(AF_INET, &thread_data->src_addr.sin_addr, thread_data->ipstr, INET_ADDRSTRLEN); // Get interface netmask - struct sockaddr_in mask = { 0 }; - memcpy(&mask, ((struct ifaddrs*)args)->ifa_netmask, sizeof(mask)); - // char netmask[INET_ADDRSTRLEN] = { 0 }; - // inet_ntop(AF_INET, &mask.sin_addr, netmask, INET_ADDRSTRLEN); + memcpy(&thread_data->mask, ifa->ifa_netmask, sizeof(thread_data->mask)); // Convert subnet to CIDR - const int cidr = netmask_to_cidr(&mask.sin_addr); + thread_data->dst_cidr = netmask_to_cidr(&thread_data->mask.sin_addr); // Get interface index const int ifindex = if_nametoindex(iface); // Scan only interfaces with CIDR >= 24 - if(cidr < 24 && !arp_all) + if(thread_data->dst_cidr < 24 && !arp_all) { - printf("Skipped interface %s (%s/%i)\n", iface, ipstr, cidr); + thread_data->status = STATUS_SKIPPED_CIDR_MISMATCH; + //printf("Skipped interface %s (%s/%i)\n", iface, thread_data->ipstr, thread_data->dst_cidr); pthread_exit(NULL); } - if(arp_verbose) - printf("Scanning interface %s (%s/%i)...\n", iface, ipstr, cidr); + //if(arp_verbose) + // printf("Scanning interface %s (%s/%i)...\n", iface, thread_data->ipstr, thread_data->dst_cidr); + thread_data->status = STATUS_SCANNING; // Create socket for ARP communications const int arp_socket = create_arp_socket(ifindex, iface); // Cannot create socket, likely a permission error if(arp_socket < 0) + { + thread_data->status = STATUS_ERROR; pthread_exit(NULL); + } // Get hardware address of client machine - unsigned char mac[16] = { 0 }; - get_hardware_address(arp_socket, iface, mac); + get_hardware_address(arp_socket, iface, thread_data->mac); // Define destination IP address by masking source IP with netmask - struct in_addr dst_addr = { 0 }; - dst_addr.s_addr = src_addr.sin_addr.s_addr & mask.sin_addr.s_addr; + thread_data->dst_addr.sin_addr.s_addr = thread_data->src_addr.sin_addr.s_addr & thread_data->mask.sin_addr.s_addr; // Allocate memory for ARP response buffer - const size_t arp_result_len = 1 << (32 - cidr); - struct arp_result *result = calloc(arp_result_len, sizeof(struct arp_result)); + const size_t arp_result_len = 1 << (32 - thread_data->dst_cidr); + thread_data->result_size = arp_result_len; + struct arp_result *result = calloc(thread_data->result_size, sizeof(struct arp_result)); + thread_data->result = result; - for(unsigned int scan_id = 0; scan_id < NUM_SCANS; scan_id++) + for(thread_data->num_scans = 0; thread_data->num_scans < NUM_SCANS; thread_data->num_scans++) { - if(arp_verbose) - printf("Still scanning interface %s (%s/%i) %i%%...\n", iface, ipstr, cidr, 100*scan_id/NUM_SCANS); + //if(arp_verbose) + // printf("Still scanning interface %s (%s/%i) %i%%...\n", iface, thread_data->ipstr, thread_data->dst_cidr, 100*scan_id/NUM_SCANS); // Send ARP requests to all IPs in subnet - if(send_arps(arp_socket, ifindex, iface, mac, &src_addr.sin_addr, dst_addr, cidr) != 0) + if(send_arps(arp_socket, ifindex, iface, thread_data->mac, &thread_data->src_addr.sin_addr, thread_data->dst_addr.sin_addr, thread_data->dst_cidr) != 0) + { + thread_data->status = STATUS_ERROR; break; + } // Read ARP responses - if(read_arp(arp_socket, iface, &dst_addr, result, arp_result_len, scan_id) != 0) + if(read_arp(arp_socket, iface, &thread_data->dst_addr.sin_addr, thread_data->result, thread_data->result_size, thread_data->num_scans) != 0) + { + thread_data->status = STATUS_ERROR; break; + } + } + + // Close socket + if(close(arp_socket) != 0) + thread_data->status = STATUS_ERROR; + + if(thread_data->status != STATUS_ERROR) + thread_data->status = STATUS_COMPLETE; + + pthread_exit(NULL); +} + +static void print_results(struct thread_data *thread_data) +{ + + if(thread_data->status == STATUS_SKIPPED_CIDR_MISMATCH) + { + printf("Skipped interface %s (%s/%i) because of too large network (use -a to force scanning this interface)\n\n", + thread_data->iface, thread_data->ipstr, thread_data->dst_cidr); + return; } // Check if there are any results unsigned int replies = 0; - for(unsigned int i = 0; i < arp_result_len; i++) + for(unsigned int i = 0; i < thread_data->result_size; i++) for(unsigned int j = 0; j < MAX_MACS; j++) for(unsigned int k = 0; k < NUM_SCANS; k++) - replies += result[i].device[j].replied[k]; - - if(pthread_mutex_lock(&lock) != 0) - return NULL; + replies += thread_data->result[i].device[j].replied[k]; // Exit early if there are no results if(replies == 0) { - printf("No devices found on interface %s (%s/%i)\n", iface, ipstr, cidr); - goto arp_scan_iface_end; + printf("No devices found on interface %s (%s/%i)\n\n", + thread_data->iface, thread_data->ipstr, thread_data->dst_cidr); + return; } // If there is at least one result, print header - printf("ARP scan on interface %s (%s/%i) finished\n", iface, ipstr, cidr); - printf("%-16s %-10s %-24s %-17s Reply matrix\n", "IP address", "Interface", "Hostname", "MAC address"); + printf("ARP scan on interface %s (%s/%i) finished\n", + thread_data->iface, thread_data->ipstr, thread_data->dst_cidr); + printf("%-16s %-10s %-24s %-17s Reply matrix\n", + "IP address", "Interface", "Hostname", "MAC address"); // Add our own IP address to the results so IP conflicts can be detected // (our own IP address is not included in the ARP scan) for(unsigned int i = 0; i < NUM_SCANS; i++) - add_result(iface, &src_addr.sin_addr, &dst_addr, mac, result, arp_result_len, i); + add_result(thread_data->iface, &thread_data->src_addr.sin_addr, &thread_data->dst_addr.sin_addr, thread_data->mac, thread_data->result, thread_data->result_size, i); // Print results - for(unsigned int i = 0; i < arp_result_len; i++) + for(unsigned int i = 0; i < thread_data->result_size; i++) { unsigned int j = 0, replied_devices = 0; unsigned int multiple_replies = 0; @@ -415,8 +468,8 @@ static void *arp_scan_iface(void *args) bool replied = false; for(unsigned int k = 0; k < NUM_SCANS; k++) { - replied |= result[i].device[j].replied[k] > 0; - multiple_replies += result[i].device[j].replied[k] > 1; + replied |= thread_data->result[i].device[j].replied[k] > 0; + multiple_replies += thread_data->result[i].device[j].replied[k] > 1; } if(!replied) continue; @@ -426,24 +479,25 @@ static void *arp_scan_iface(void *args) // Convert IP address to string struct in_addr ip = { 0 }; - ip.s_addr = htonl(ntohl(dst_addr.s_addr) + i); - inet_ntop(AF_INET, &ip, ipstr, INET_ADDRSTRLEN); + ip.s_addr = htonl(ntohl(thread_data->dst_addr.sin_addr.s_addr) + i); + inet_ntop(AF_INET, &ip, thread_data->ipstr, INET_ADDRSTRLEN); // Check if result[i].mac[j] is all-zero - if(memcmp(result[i].device[j].mac, "\x00\x00\x00\x00\x00\x00", 6) == 0) + if(memcmp(thread_data->result[i].device[j].mac, "\x00\x00\x00\x00\x00\x00", 6) == 0) break; // Print MAC address printf("%-16s %-10s %-24s %02x:%02x:%02x:%02x:%02x:%02x ", - ipstr, iface, get_hostname(&ip), - result[i].device[j].mac[0], - result[i].device[j].mac[1], - result[i].device[j].mac[2], - result[i].device[j].mac[3], - result[i].device[j].mac[4], - result[i].device[j].mac[5]); + thread_data->ipstr, thread_data->iface, + get_hostname(&ip), + thread_data->result[i].device[j].mac[0], + thread_data->result[i].device[j].mac[1], + thread_data->result[i].device[j].mac[2], + thread_data->result[i].device[j].mac[3], + thread_data->result[i].device[j].mac[4], + thread_data->result[i].device[j].mac[5]); for(unsigned int k = 0; k < NUM_SCANS; k++) - printf(" %s", result[i].device[j].replied[k] > 0 ? "X" : "-"); + printf(" %s", thread_data->result[i].device[j].replied[k] > 0 ? "X" : "-"); putc('\n', stdout); } @@ -451,25 +505,16 @@ static void *arp_scan_iface(void *args) // Print warning if we received multiple replies if(replied_devices > 1) printf("WARNING: Received replies for %s from %i devices\n", - ipstr, replied_devices); + thread_data->ipstr, replied_devices); if(multiple_replies > 0) printf("WARNING: Received multiple replies for %s in %i scan%s\n", - ipstr, multiple_replies, multiple_replies > 1 ? "s" : ""); + thread_data->ipstr, multiple_replies, multiple_replies > 1 ? "s" : ""); } putc('\n', stdout); - -arp_scan_iface_end: - if(pthread_mutex_unlock(&lock) != 0) - return NULL; - - // Close socket - close(arp_socket); - pthread_exit(NULL); } -int run_arp_scan(const bool verbose, const bool scan_all) +int run_arp_scan(const bool scan_all) { - arp_verbose = verbose; arp_all = scan_all; puts("Discovering IPv4 hosts on the network using the Address Resolution Protocol (ARP)...\n"); @@ -480,28 +525,25 @@ int run_arp_scan(const bool verbose, const bool scan_all) // Initialize thread attributes object with default attribute values pthread_attr_init(&attr); - // Create processing/logging lock - pthread_mutexattr_t lock_attr = {}; - // Initialize the lock attributes - pthread_mutexattr_init(&lock_attr); - // Initialize the lock - pthread_mutex_init(&lock, &lock_attr); - // Destroy the lock attributes since we're done with it - pthread_mutexattr_destroy(&lock_attr); - struct ifaddrs *addrs, *tmp; getifaddrs(&addrs); tmp = addrs; // Loop until there are no more interfaces available // or we reached the maximum number of threads - int tid = 0; + unsigned int tid = 0; + + struct thread_data thread_data[MAXTHREADS] = {0}; + while(tmp != NULL && tid < MAXTHREADS) { // Create a thread for interfaces of type AF_INET if(tmp->ifa_addr && tmp->ifa_addr->sa_family == AF_INET) { - if(pthread_create(&scanthread[tid], &attr, arp_scan_iface, tmp ) != 0) + thread_data[tid].ifa = tmp; + thread_data[tid].iface = tmp->ifa_name; + // Create thread + if(pthread_create(&scanthread[tid], &attr, arp_scan_iface, &thread_data[tid] ) != 0) { printf("Unable to launch thread for interface %s, skipping...\n", tmp->ifa_name); @@ -516,12 +558,60 @@ int run_arp_scan(const bool verbose, const bool scan_all) tmp = tmp->ifa_next; } + // Wait for all threads to finish scanning + bool all_done = false; + while(!all_done) + { + all_done = true; + unsigned int num_scans = 0, total_scans = 0; + for(unsigned int i = 0; i < tid; i++) + { + if(thread_data[i].status == STATUS_SCANNING) + { + // At least one thread is still scanning + all_done = false; + num_scans += thread_data[i].num_scans; + total_scans += NUM_SCANS; + } + if(thread_data[i].status == STATUS_COMPLETE) + { + // Also add up scans for completed threads + num_scans += thread_data[i].num_scans; + total_scans += NUM_SCANS; + } + } + if(!all_done) + { + // Print progress + printf("%i%%... ", 100*num_scans/total_scans); + // Flush stdout + fflush(stdout); + // Sleep for 1 second + sleepms(1000); + } + } + puts("100%%\n\n"); + // Wait for all threads to join back with us - for(tid--; tid > -1; tid--) - pthread_join(scanthread[tid], NULL); + for(unsigned int i = 0; i < tid; i++) + pthread_join(scanthread[i], NULL); + + // Destroy the thread attributes object, since we are done with it + pthread_attr_destroy(&attr); // Free linked-list of interfaces on this client freeifaddrs(addrs); + // Loop over thread results and print them + for(unsigned int i = 0; i < tid; i++) + { + // Print results + print_results(&thread_data[i]); + + // Free allocated memory + if(thread_data[i].result != NULL) + free(thread_data[i].result); + } + return EXIT_SUCCESS; } diff --git a/src/tools/arp-scan.h b/src/tools/arp-scan.h index 90b5fec7..0a5b5671 100644 --- a/src/tools/arp-scan.h +++ b/src/tools/arp-scan.h @@ -11,6 +11,6 @@ #ifndef ARP_SCAN_H #define ARP_SCAN_H -int run_arp_scan(const bool verbose, const bool scan_all); +int run_arp_scan(const bool scan_all); #endif // ARP_SCAN_H From 5dcdbd7264b98e8d69269d60af1789779eb6477e Mon Sep 17 00:00:00 2001 From: DL6ER Date: Tue, 16 May 2023 04:40:11 +0200 Subject: [PATCH 10/23] Scale progress percentage according to number of addresses to be scanned by the individual threads Signed-off-by: DL6ER --- src/tools/arp-scan.c | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/tools/arp-scan.c b/src/tools/arp-scan.c index 69e70241..b84ac4dd 100644 --- a/src/tools/arp-scan.c +++ b/src/tools/arp-scan.c @@ -563,34 +563,35 @@ int run_arp_scan(const bool scan_all) while(!all_done) { all_done = true; - unsigned int num_scans = 0, total_scans = 0; + uint64_t num_scans = 0, total_scans = 0; for(unsigned int i = 0; i < tid; i++) { - if(thread_data[i].status == STATUS_SCANNING) + const uint32_t num_addresses = 1 << (32 - thread_data[i].dst_cidr); + if(thread_data[i].status == STATUS_INITIALIZING || + thread_data[i].status == STATUS_SCANNING) { // At least one thread is still scanning all_done = false; - num_scans += thread_data[i].num_scans; - total_scans += NUM_SCANS; } - if(thread_data[i].status == STATUS_COMPLETE) + if(thread_data[i].status == STATUS_SCANNING || + thread_data[i].status == STATUS_COMPLETE) { // Also add up scans for completed threads - num_scans += thread_data[i].num_scans; - total_scans += NUM_SCANS; + num_scans += thread_data[i].num_scans * num_addresses; + total_scans += NUM_SCANS * num_addresses; } } if(!all_done) { // Print progress - printf("%i%%... ", 100*num_scans/total_scans); + printf("%i%%... ", (int)(100*num_scans/total_scans)); // Flush stdout fflush(stdout); // Sleep for 1 second sleepms(1000); } } - puts("100%%\n\n"); + puts("100%\n\n"); // Wait for all threads to join back with us for(unsigned int i = 0; i < tid; i++) From 1bc05b9affc82c3ff9f6a7a054bc48576de36270 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Tue, 16 May 2023 04:45:39 +0200 Subject: [PATCH 11/23] Only print progress if it has changed. Otherwise, print "." as hearthbeat Signed-off-by: DL6ER --- src/tools/arp-scan.c | 34 ++++++++++++++++++++++++++-------- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/src/tools/arp-scan.c b/src/tools/arp-scan.c index b84ac4dd..8fb5351e 100644 --- a/src/tools/arp-scan.c +++ b/src/tools/arp-scan.c @@ -94,13 +94,14 @@ struct thread_data { char ipstr[INET_ADDRSTRLEN]; unsigned char mac[16]; unsigned int num_scans; + uint32_t scanned_addresses; char *error; }; // Sends multiple ARP who-has request on interface ifindex, using source mac src_mac and source ip src_ip. // Iterates over all IP addresses in the range of dst_ip/cidr. static int send_arps(const int fd, const int ifindex, const char *iface, const unsigned char *src_mac, - struct in_addr *src_ip, struct in_addr dst_ip, const int dst_cidr) + struct in_addr *src_ip, struct in_addr dst_ip, const int dst_cidr, uint32_t *scanned_addresses) { int err = -1; unsigned char buffer[BUF_SIZE]; @@ -168,6 +169,8 @@ static int send_arps(const int fd, const int ifindex, const char *iface, const u // Increment IP address dst_ip.s_addr = htonl(ntohl(dst_ip.s_addr) + 1); + + (*scanned_addresses)++; } err = 0; @@ -395,14 +398,16 @@ static void *arp_scan_iface(void *args) // printf("Still scanning interface %s (%s/%i) %i%%...\n", iface, thread_data->ipstr, thread_data->dst_cidr, 100*scan_id/NUM_SCANS); // Send ARP requests to all IPs in subnet - if(send_arps(arp_socket, ifindex, iface, thread_data->mac, &thread_data->src_addr.sin_addr, thread_data->dst_addr.sin_addr, thread_data->dst_cidr) != 0) + if(send_arps(arp_socket, ifindex, iface, thread_data->mac, &thread_data->src_addr.sin_addr, + thread_data->dst_addr.sin_addr, thread_data->dst_cidr, &thread_data->scanned_addresses) != 0) { thread_data->status = STATUS_ERROR; break; } // Read ARP responses - if(read_arp(arp_socket, iface, &thread_data->dst_addr.sin_addr, thread_data->result, thread_data->result_size, thread_data->num_scans) != 0) + if(read_arp(arp_socket, iface, &thread_data->dst_addr.sin_addr, thread_data->result, thread_data->result_size, + thread_data->num_scans) != 0) { thread_data->status = STATUS_ERROR; break; @@ -560,13 +565,13 @@ int run_arp_scan(const bool scan_all) // Wait for all threads to finish scanning bool all_done = false; + unsigned int progress = 0; while(!all_done) { all_done = true; uint64_t num_scans = 0, total_scans = 0; for(unsigned int i = 0; i < tid; i++) { - const uint32_t num_addresses = 1 << (32 - thread_data[i].dst_cidr); if(thread_data[i].status == STATUS_INITIALIZING || thread_data[i].status == STATUS_SCANNING) { @@ -577,16 +582,29 @@ int run_arp_scan(const bool scan_all) thread_data[i].status == STATUS_COMPLETE) { // Also add up scans for completed threads - num_scans += thread_data[i].num_scans * num_addresses; - total_scans += NUM_SCANS * num_addresses; + num_scans += thread_data[i].scanned_addresses; + total_scans += NUM_SCANS * thread_data[i].result_size; } } if(!all_done) { - // Print progress - printf("%i%%... ", (int)(100*num_scans/total_scans)); + // Calculate progress (total number of scans / total number of addresses) + // We add 1 to total_scans to avoid division by zero + const unsigned int new_progress = 100 * num_scans / (total_scans + 1); + if(new_progress > progress) + { + // Print progress + printf(" %i%%", new_progress); + + // Update progress + progress = new_progress; + } + + putc('.', stdout); + // Flush stdout fflush(stdout); + // Sleep for 1 second sleepms(1000); } From 4387ff21ee8bf15f65adaf29f98b5b187354e370 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Tue, 16 May 2023 04:49:19 +0200 Subject: [PATCH 12/23] Always skip the loopback interface, also in "-a" mode Signed-off-by: DL6ER --- src/tools/arp-scan.c | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/src/tools/arp-scan.c b/src/tools/arp-scan.c index 8fb5351e..1a038b3a 100644 --- a/src/tools/arp-scan.c +++ b/src/tools/arp-scan.c @@ -346,10 +346,6 @@ static void *arp_scan_iface(void *args) // Set interface name as thread name prctl(PR_SET_NAME, iface, 0, 0, 0); - // Get interface IPv4 address - memcpy(&thread_data->src_addr, ifa->ifa_addr, sizeof(thread_data->src_addr)); - inet_ntop(AF_INET, &thread_data->src_addr.sin_addr, thread_data->ipstr, INET_ADDRSTRLEN); - // Get interface netmask memcpy(&thread_data->mask, ifa->ifa_netmask, sizeof(thread_data->mask)); @@ -547,16 +543,24 @@ int run_arp_scan(const bool scan_all) { thread_data[tid].ifa = tmp; thread_data[tid].iface = tmp->ifa_name; - // Create thread - if(pthread_create(&scanthread[tid], &attr, arp_scan_iface, &thread_data[tid] ) != 0) - { - printf("Unable to launch thread for interface %s, skipping...\n", - tmp->ifa_name); - continue; - } - // Increase thread ID - tid++; + // Get interface IPv4 address + memcpy(&thread_data[tid].src_addr, tmp->ifa_addr, sizeof(thread_data[tid].src_addr)); + inet_ntop(AF_INET, &thread_data[tid].src_addr.sin_addr, thread_data[tid].ipstr, INET_ADDRSTRLEN); + + // Always skip the loopback interface + if(thread_data[tid].src_addr.sin_addr.s_addr != htonl(INADDR_LOOPBACK)) + { + // Create thread + if(pthread_create(&scanthread[tid], &attr, arp_scan_iface, &thread_data[tid] ) != 0) + { + printf("Unable to launch thread for interface %s, skipping...\n", + tmp->ifa_name); + } + + // Increase thread ID + tid++; + } } // Advance to the next interface From d45a7c46fc23c32bed0906207be7efc7dced8a0b Mon Sep 17 00:00:00 2001 From: DL6ER Date: Tue, 16 May 2023 04:51:01 +0200 Subject: [PATCH 13/23] Interface names can be up to 16 bytes long. Docker bridge interfaces actually use this space so we need to reserve enough space here Signed-off-by: DL6ER --- src/tools/arp-scan.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tools/arp-scan.c b/src/tools/arp-scan.c index 1a038b3a..1c446840 100644 --- a/src/tools/arp-scan.c +++ b/src/tools/arp-scan.c @@ -448,7 +448,7 @@ static void print_results(struct thread_data *thread_data) // If there is at least one result, print header printf("ARP scan on interface %s (%s/%i) finished\n", thread_data->iface, thread_data->ipstr, thread_data->dst_cidr); - printf("%-16s %-10s %-24s %-17s Reply matrix\n", + printf("%-16s %-16s %-24s %-17s Reply matrix\n", "IP address", "Interface", "Hostname", "MAC address"); // Add our own IP address to the results so IP conflicts can be detected @@ -487,7 +487,7 @@ static void print_results(struct thread_data *thread_data) break; // Print MAC address - printf("%-16s %-10s %-24s %02x:%02x:%02x:%02x:%02x:%02x ", + printf("%-16s %-16s %-24s %02x:%02x:%02x:%02x:%02x:%02x ", thread_data->ipstr, thread_data->iface, get_hostname(&ip), thread_data->result[i].device[j].mac[0], From 2d6a3619a07fb5a41ba95d232912ded1d0149c6b Mon Sep 17 00:00:00 2001 From: DL6ER Date: Tue, 16 May 2023 11:49:11 +0200 Subject: [PATCH 14/23] Clearly log when scanning interfaces failed Signed-off-by: DL6ER --- src/tools/arp-scan.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/tools/arp-scan.c b/src/tools/arp-scan.c index 1c446840..60b60d70 100644 --- a/src/tools/arp-scan.c +++ b/src/tools/arp-scan.c @@ -430,6 +430,13 @@ static void print_results(struct thread_data *thread_data) return; } + if(thread_data->status == STATUS_ERROR) + { + printf("Error scanning interface %s (%s/%i)\n\n", + thread_data->iface, thread_data->ipstr, thread_data->dst_cidr); + return; + } + // Check if there are any results unsigned int replies = 0; for(unsigned int i = 0; i < thread_data->result_size; i++) @@ -440,7 +447,7 @@ static void print_results(struct thread_data *thread_data) // Exit early if there are no results if(replies == 0) { - printf("No devices found on interface %s (%s/%i)\n\n", + printf("No devices replied on interface %s (%s/%i)\n\n", thread_data->iface, thread_data->ipstr, thread_data->dst_cidr); return; } From 8a299112b83a9527dac385fc1106aaf41814ca4c Mon Sep 17 00:00:00 2001 From: DL6ER Date: Tue, 16 May 2023 20:54:51 +0200 Subject: [PATCH 15/23] Log more verbose human-readable error string if available Signed-off-by: DL6ER --- src/tools/arp-scan.c | 53 +++++++++++++++++++++++++------------------- 1 file changed, 30 insertions(+), 23 deletions(-) diff --git a/src/tools/arp-scan.c b/src/tools/arp-scan.c index 60b60d70..c3eef0e8 100644 --- a/src/tools/arp-scan.c +++ b/src/tools/arp-scan.c @@ -95,12 +95,12 @@ struct thread_data { unsigned char mac[16]; unsigned int num_scans; uint32_t scanned_addresses; - char *error; + const char *error; }; // Sends multiple ARP who-has request on interface ifindex, using source mac src_mac and source ip src_ip. // Iterates over all IP addresses in the range of dst_ip/cidr. -static int send_arps(const int fd, const int ifindex, const char *iface, const unsigned char *src_mac, +static int send_arps(const int fd, const int ifindex, const char *iface, const unsigned char *src_mac, const char **error, struct in_addr *src_ip, struct in_addr dst_ip, const int dst_cidr, uint32_t *scanned_addresses) { int err = -1; @@ -161,9 +161,8 @@ static int send_arps(const int fd, const int ifindex, const char *iface, const u ret = sendto(fd, buffer, 42, 0, (struct sockaddr *) &socket_address, sizeof(socket_address)); if (ret == -1) { - if(errno != EPROTONOSUPPORT) - printf("Unable to send ARP request for %s@%s: %s\n", - inet_ntoa(dst_ip), iface, strerror(errno)); + err = errno; + *error = strerror(err); goto out; } @@ -178,13 +177,14 @@ out: return err; } -static int create_arp_socket(const int ifindex, const char *iface) +static int create_arp_socket(const int ifindex, const char *iface, const char **error) { // Create socket for ARP communications const int arp_socket = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ARP)); if(arp_socket < 0) { - printf("Unable to create socket for ARP communications on interface %s: %s\n", iface, strerror(errno)); + *error = strerror(errno); + printf("Unable to create socket for ARP communications on interface %s: %s\n", iface, *error); return -1; } @@ -195,7 +195,8 @@ static int create_arp_socket(const int ifindex, const char *iface) sll.sll_ifindex = ifindex; if (bind(arp_socket, (struct sockaddr*) &sll, sizeof(struct sockaddr_ll)) < 0) { - printf("Unable to bind socket for ARP communications on interface %s: %s\n", iface, strerror(errno)); + *error = strerror(errno); + printf("Unable to bind socket for ARP communications on interface %s: %s\n", iface, *error); close(arp_socket); return -1; } @@ -206,7 +207,8 @@ static int create_arp_socket(const int ifindex, const char *iface) tv.tv_usec = 0; if (setsockopt(arp_socket, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) < 0) { - printf("Unable to set timeout for ARP communications on interface %s: %s\n", iface, strerror(errno)); + *error = strerror(errno); + printf("Unable to set timeout for ARP communications on interface %s: %s\n", iface, *error); close(arp_socket); return -1; } @@ -249,7 +251,7 @@ static void add_result(const char *iface, struct in_addr *rcv_ip, struct in_addr } // Read all ARP responses -static ssize_t read_arp(const int fd, const char *iface, struct in_addr *dst_ip, +static ssize_t read_arp(const int fd, const char *iface, struct in_addr *dst_ip, const char **error, struct arp_result *result, const size_t result_len, const unsigned int scan_id) { ssize_t ret = 0; @@ -269,7 +271,8 @@ static ssize_t read_arp(const int fd, const char *iface, struct in_addr *dst_ip, } // Error - printf("recvfrom(): %s", strerror(errno)); + *error = strerror(errno); + printf("recvfrom(): %s", *error); break; } struct ethhdr *rcv_resp = (struct ethhdr *) buffer; @@ -359,15 +362,18 @@ static void *arp_scan_iface(void *args) if(thread_data->dst_cidr < 24 && !arp_all) { thread_data->status = STATUS_SKIPPED_CIDR_MISMATCH; - //printf("Skipped interface %s (%s/%i)\n", iface, thread_data->ipstr, thread_data->dst_cidr); +#ifdef DEBUG + printf("Skipped interface %s (%s/%i)\n", iface, thread_data->ipstr, thread_data->dst_cidr); +#endif pthread_exit(NULL); } - //if(arp_verbose) - // printf("Scanning interface %s (%s/%i)...\n", iface, thread_data->ipstr, thread_data->dst_cidr); +#ifdef DEBUG + printf("Scanning interface %s (%s/%i)...\n", iface, thread_data->ipstr, thread_data->dst_cidr); +#endif thread_data->status = STATUS_SCANNING; // Create socket for ARP communications - const int arp_socket = create_arp_socket(ifindex, iface); + const int arp_socket = create_arp_socket(ifindex, iface, &thread_data->error); // Cannot create socket, likely a permission error if(arp_socket < 0) @@ -390,11 +396,11 @@ static void *arp_scan_iface(void *args) for(thread_data->num_scans = 0; thread_data->num_scans < NUM_SCANS; thread_data->num_scans++) { - //if(arp_verbose) - // printf("Still scanning interface %s (%s/%i) %i%%...\n", iface, thread_data->ipstr, thread_data->dst_cidr, 100*scan_id/NUM_SCANS); - +#ifdef DEBUG + printf("Still scanning interface %s (%s/%i) %i%%...\n", iface, thread_data->ipstr, thread_data->dst_cidr, 100*scan_id/NUM_SCANS); +#endif // Send ARP requests to all IPs in subnet - if(send_arps(arp_socket, ifindex, iface, thread_data->mac, &thread_data->src_addr.sin_addr, + if(send_arps(arp_socket, ifindex, iface, thread_data->mac, &thread_data->error, &thread_data->src_addr.sin_addr, thread_data->dst_addr.sin_addr, thread_data->dst_cidr, &thread_data->scanned_addresses) != 0) { thread_data->status = STATUS_ERROR; @@ -402,8 +408,8 @@ static void *arp_scan_iface(void *args) } // Read ARP responses - if(read_arp(arp_socket, iface, &thread_data->dst_addr.sin_addr, thread_data->result, thread_data->result_size, - thread_data->num_scans) != 0) + if(read_arp(arp_socket, iface, &thread_data->dst_addr.sin_addr, &thread_data->error, + thread_data->result, thread_data->result_size, thread_data->num_scans) != 0) { thread_data->status = STATUS_ERROR; break; @@ -432,8 +438,9 @@ static void print_results(struct thread_data *thread_data) if(thread_data->status == STATUS_ERROR) { - printf("Error scanning interface %s (%s/%i)\n\n", - thread_data->iface, thread_data->ipstr, thread_data->dst_cidr); + printf("Error scanning interface %s (%s/%i)%s%s\n\n", + thread_data->iface, thread_data->ipstr, thread_data->dst_cidr, + thread_data->error ? ": " : "", thread_data->error ? thread_data->error : ""); return; } From e3550c1a472d1bcdf624277edf66e1dcce86b94f Mon Sep 17 00:00:00 2001 From: DL6ER Date: Tue, 16 May 2023 21:02:29 +0200 Subject: [PATCH 16/23] Add capabilities check for CAP_NET_RAW (root always has it) Signed-off-by: DL6ER --- src/capabilities.c | 41 +++++++++++++++++++++++++++++++++++++++++ src/capabilities.h | 1 + src/tools/arp-scan.c | 16 ++++++++++++++++ 3 files changed, 58 insertions(+) diff --git a/src/capabilities.c b/src/capabilities.c index 603cb4f7..49e5d5d9 100644 --- a/src/capabilities.c +++ b/src/capabilities.c @@ -21,6 +21,47 @@ static const unsigned int capabilityIDs[] = { CAP_CHOWN , CAP_DAC_OVERRIDE , static const char* capabilityNames[] = {"CAP_CHOWN", "CAP_DAC_OVERRIDE", "CAP_DAC_READ_SEARCH", "CAP_FOWNER", "CAP_FSETID", "CAP_KILL", "CAP_SETGID", "CAP_SETUID", "CAP_SETPCAP", "CAP_LINUX_IMMUTABLE", "CAP_NET_BIND_SERVICE", "CAP_NET_BROADCAST", "CAP_NET_ADMIN", "CAP_NET_RAW", "CAP_IPC_LOCK", "CAP_IPC_OWNER", "CAP_SYS_MODULE", "CAP_SYS_RAWIO", "CAP_SYS_CHROOT", "CAP_SYS_PTRACE", "CAP_SYS_PACCT", "CAP_SYS_ADMIN", "CAP_SYS_BOOT", "CAP_SYS_NICE", "CAP_SYS_RESOURCE", "CAP_SYS_TIME", "CAP_SYS_TTY_CONFIG", "CAP_MKNOD", "CAP_LEASE", "CAP_AUDIT_WRITE", "CAP_AUDIT_CONTROL", "CAP_SETFCAP"}; static const unsigned int numCaps = sizeof(capabilityIDs) / sizeof(*capabilityIDs); +bool check_capability(const unsigned int cap) +{ + // First assume header version 1 + int capsize = 1; // VFS_CAP_U32_1 + cap_user_data_t data = NULL; + cap_user_header_t hdr = calloc(sizeof(*hdr), capsize); + + // Determine capabilities version used by the current kernel + capget(hdr, NULL); + + // Check version + if (hdr->version != LINUX_CAPABILITY_VERSION_1) + { + // If unknown version, use largest supported version (3) + // Version 2 is deprecated according to linux/capability.h + if (hdr->version != LINUX_CAPABILITY_VERSION_2) + { + hdr->version = LINUX_CAPABILITY_VERSION_3; + capsize = 2; // VFS_CAP_U32_3 + } + else + { + // Use version 2 + capsize = 2; // VFS_CAP_U32_2 + } + } + + // Get current capabilities + data = calloc(sizeof(*data), capsize); + capget(hdr, data); + + // Check if the capability is available + const bool available = ((data->permitted & (1 << cap)) && (data->effective & (1 << cap))); + + // Free memory + free(hdr); + free(data); + + return available; +} + bool check_capabilities(void) { // First assume header version 1 diff --git a/src/capabilities.h b/src/capabilities.h index a6511382..6f811301 100644 --- a/src/capabilities.h +++ b/src/capabilities.h @@ -10,6 +10,7 @@ #ifndef CAPABILITIES_H #define CAPABILITIES_H +bool check_capability(const unsigned int cap); bool check_capabilities(void); #endif //CAPABILITIES_H diff --git a/src/tools/arp-scan.c b/src/tools/arp-scan.c index c3eef0e8..964c07db 100644 --- a/src/tools/arp-scan.c +++ b/src/tools/arp-scan.c @@ -16,6 +16,9 @@ #include "dhcp-discover.h" // sleepms() #include "timers.h" +// check_capability() +#include "capabilities.h" +#include #include #include @@ -184,7 +187,9 @@ static int create_arp_socket(const int ifindex, const char *iface, const char ** if(arp_socket < 0) { *error = strerror(errno); +#ifdef DEBUG printf("Unable to create socket for ARP communications on interface %s: %s\n", iface, *error); +#endif return -1; } @@ -196,7 +201,9 @@ static int create_arp_socket(const int ifindex, const char *iface, const char ** if (bind(arp_socket, (struct sockaddr*) &sll, sizeof(struct sockaddr_ll)) < 0) { *error = strerror(errno); +#ifdef DEBUG printf("Unable to bind socket for ARP communications on interface %s: %s\n", iface, *error); +#endif close(arp_socket); return -1; } @@ -208,7 +215,9 @@ static int create_arp_socket(const int ifindex, const char *iface, const char ** if (setsockopt(arp_socket, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) < 0) { *error = strerror(errno); +#ifdef DEBUG printf("Unable to set timeout for ARP communications on interface %s: %s\n", iface, *error); +#endif close(arp_socket); return -1; } @@ -530,6 +539,13 @@ static void print_results(struct thread_data *thread_data) int run_arp_scan(const bool scan_all) { + // Check if we are capable of sending ARP packets + if(!check_capability(CAP_NET_RAW)) + { + puts("Error: Insufficient permissions or capabilities (needs CAP_NET_RAW). Try running as root (sudo)"); + return EXIT_FAILURE; + } + arp_all = scan_all; puts("Discovering IPv4 hosts on the network using the Address Resolution Protocol (ARP)...\n"); From 73e3d8840864270bf4d3fcd3244c965045d9ad91 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Wed, 17 May 2023 16:53:51 +0200 Subject: [PATCH 17/23] Optimize thread_data structure and store a thread-local copy of the interface name Signed-off-by: DL6ER --- src/tools/arp-scan.c | 37 ++++++++++++++++++++----------------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/src/tools/arp-scan.c b/src/tools/arp-scan.c index 964c07db..3a81c1eb 100644 --- a/src/tools/arp-scan.c +++ b/src/tools/arp-scan.c @@ -82,23 +82,23 @@ enum status { STATUS_SCANNING, STATUS_ERROR, STATUS_COMPLETE -}; +} __attribute__ ((packed)); struct thread_data { + char iface[IF_NAMESIZE + 1]; + char ipstr[INET_ADDRSTRLEN]; + unsigned char mac[MAC_LENGTH]; + enum status status; int dst_cidr; + unsigned int num_scans; + size_t result_size; + uint32_t scanned_addresses; + const char *error; + struct ifaddrs *ifa; + struct arp_result *result; struct sockaddr_in src_addr; struct sockaddr_in dst_addr; struct sockaddr_in mask; - struct ifaddrs *ifa; - const char *iface; - struct arp_result *result; - size_t result_size; - enum status status; - char ipstr[INET_ADDRSTRLEN]; - unsigned char mac[16]; - unsigned int num_scans; - uint32_t scanned_addresses; - const char *error; }; // Sends multiple ARP who-has request on interface ifindex, using source mac src_mac and source ip src_ip. @@ -488,11 +488,17 @@ static void print_results(struct thread_data *thread_data) // Print MAC addresses for(j = 0; j < MAX_MACS; j++) { + // Check if result[i].mac[j] is all-zero, if so, skip this entry + if(memcmp(thread_data->result[i].device[j].mac, "\x00\x00\x00\x00\x00\x00", 6) == 0) + break; + // Check if IP address replied + replies = 0u; bool replied = false; for(unsigned int k = 0; k < NUM_SCANS; k++) { replied |= thread_data->result[i].device[j].replied[k] > 0; + replies += thread_data->result[i].device[j].replied[k] > 0 ? 1 : 0; multiple_replies += thread_data->result[i].device[j].replied[k] > 1; } if(!replied) @@ -505,9 +511,6 @@ static void print_results(struct thread_data *thread_data) struct in_addr ip = { 0 }; ip.s_addr = htonl(ntohl(thread_data->dst_addr.sin_addr.s_addr) + i); inet_ntop(AF_INET, &ip, thread_data->ipstr, INET_ADDRSTRLEN); - // Check if result[i].mac[j] is all-zero - if(memcmp(thread_data->result[i].device[j].mac, "\x00\x00\x00\x00\x00\x00", 6) == 0) - break; // Print MAC address printf("%-16s %-16s %-24s %02x:%02x:%02x:%02x:%02x:%02x ", @@ -523,7 +526,7 @@ static void print_results(struct thread_data *thread_data) for(unsigned int k = 0; k < NUM_SCANS; k++) printf(" %s", thread_data->result[i].device[j].replied[k] > 0 ? "X" : "-"); - putc('\n', stdout); + printf(" (%u%%)\n", replies * 100 / NUM_SCANS); } // Print warning if we received multiple replies @@ -572,7 +575,7 @@ int run_arp_scan(const bool scan_all) if(tmp->ifa_addr && tmp->ifa_addr->sa_family == AF_INET) { thread_data[tid].ifa = tmp; - thread_data[tid].iface = tmp->ifa_name; + strncpy(thread_data[tid].iface, tmp->ifa_name, sizeof(thread_data[tid].iface) - 1); // Get interface IPv4 address memcpy(&thread_data[tid].src_addr, tmp->ifa_addr, sizeof(thread_data[tid].src_addr)); @@ -643,7 +646,7 @@ int run_arp_scan(const bool scan_all) sleepms(1000); } } - puts("100%\n\n"); + puts("100%\n"); // Wait for all threads to join back with us for(unsigned int i = 0; i < tid; i++) From 6ce6eaeda512ccde3325b3e572667080ba0987aa Mon Sep 17 00:00:00 2001 From: DL6ER Date: Wed, 17 May 2023 17:02:15 +0200 Subject: [PATCH 18/23] Add arp-scan -xtreme mode for very unreliable connections Signed-off-by: DL6ER --- src/args.c | 9 ++++++--- src/tools/arp-scan.c | 34 ++++++++++++++++++---------------- src/tools/arp-scan.h | 2 +- 3 files changed, 25 insertions(+), 20 deletions(-) diff --git a/src/args.c b/src/args.c index 14b4031d..16391ff4 100644 --- a/src/args.c +++ b/src/args.c @@ -178,8 +178,9 @@ void parse_args(int argc, char* argv[]) { // Enable stdout printing cli_mode = true; - const bool arp_all = argc > 2 && strcmp(argv[2], "-a") == 0; - exit(run_arp_scan(arp_all)); + const bool scan_all = argc > 2 && strcmp(argv[2], "-a") == 0; + const bool extreme_mode = argc > 2 && strcmp(argv[2], "-x") == 0; + exit(run_arp_scan(scan_all, extreme_mode)); } // start from 1, as argv[0] is the executable name @@ -513,10 +514,12 @@ void parse_args(int argc, char* argv[]) printf("%sOther:%s\n", yellow, normal); printf("\t%sdhcp-discover%s Discover DHCP servers in the local\n", green, normal); printf("\t network\n"); - printf("\t%sarp-scan %s[-a]%s Use ARP to scan local network for\n", green, cyan, normal); + printf("\t%sarp-scan %s[-a/-x]%s Use ARP to scan local network for\n", green, cyan, normal); printf("\t possible IP conflicts\n"); printf("\t Append %s-a%s to force scan on all\n", cyan, normal); printf("\t interfaces\n"); + printf("\t Append %s-x%s to force scan on all\n", cyan, normal); + printf("\t interfaces and scan 10x more often\n"); printf("\t%s-h%s, %shelp%s Display this help and exit\n\n", green, normal, green, normal); exit(EXIT_SUCCESS); } diff --git a/src/tools/arp-scan.c b/src/tools/arp-scan.c index 3a81c1eb..408ae639 100644 --- a/src/tools/arp-scan.c +++ b/src/tools/arp-scan.c @@ -40,9 +40,6 @@ // How long do we wait for ARP replies in each scan [seconds]? #define ARP_TIMEOUT 1 -// Global constant -static bool arp_all = false; - // Protocol definitions #define PROTO_ARP 0x0806 #define ETH2_HEADER_LEN 14 @@ -71,7 +68,8 @@ struct arp_header { struct arp_result { struct device { - unsigned int replied[NUM_SCANS]; + // In extreme mode, we can scan up to 10x more often + unsigned int replied[10*NUM_SCANS]; unsigned char mac[MAC_LENGTH]; } device[MAX_MACS]; }; @@ -85,12 +83,14 @@ enum status { } __attribute__ ((packed)); struct thread_data { + bool scan_all :1; char iface[IF_NAMESIZE + 1]; char ipstr[INET_ADDRSTRLEN]; unsigned char mac[MAC_LENGTH]; enum status status; int dst_cidr; unsigned int num_scans; + unsigned int total_scans; size_t result_size; uint32_t scanned_addresses; const char *error; @@ -368,7 +368,7 @@ static void *arp_scan_iface(void *args) const int ifindex = if_nametoindex(iface); // Scan only interfaces with CIDR >= 24 - if(thread_data->dst_cidr < 24 && !arp_all) + if(thread_data->dst_cidr < 24 && !thread_data->scan_all) { thread_data->status = STATUS_SKIPPED_CIDR_MISMATCH; #ifdef DEBUG @@ -403,10 +403,10 @@ static void *arp_scan_iface(void *args) struct arp_result *result = calloc(thread_data->result_size, sizeof(struct arp_result)); thread_data->result = result; - for(thread_data->num_scans = 0; thread_data->num_scans < NUM_SCANS; thread_data->num_scans++) + for(thread_data->num_scans = 0; thread_data->num_scans < thread_data->total_scans; thread_data->num_scans++) { #ifdef DEBUG - printf("Still scanning interface %s (%s/%i) %i%%...\n", iface, thread_data->ipstr, thread_data->dst_cidr, 100*scan_id/NUM_SCANS); + printf("Still scanning interface %s (%s/%i) %i%%...\n", iface, thread_data->ipstr, thread_data->dst_cidr, 100*scan_id/thread_data->total_scans); #endif // Send ARP requests to all IPs in subnet if(send_arps(arp_socket, ifindex, iface, thread_data->mac, &thread_data->error, &thread_data->src_addr.sin_addr, @@ -440,7 +440,7 @@ static void print_results(struct thread_data *thread_data) if(thread_data->status == STATUS_SKIPPED_CIDR_MISMATCH) { - printf("Skipped interface %s (%s/%i) because of too large network (use -a to force scanning this interface)\n\n", + printf("Skipped interface %s (%s/%i) because of too large network (use -a or -x to force scanning this interface)\n\n", thread_data->iface, thread_data->ipstr, thread_data->dst_cidr); return; } @@ -457,7 +457,7 @@ static void print_results(struct thread_data *thread_data) unsigned int replies = 0; for(unsigned int i = 0; i < thread_data->result_size; i++) for(unsigned int j = 0; j < MAX_MACS; j++) - for(unsigned int k = 0; k < NUM_SCANS; k++) + for(unsigned int k = 0; k < thread_data->total_scans; k++) replies += thread_data->result[i].device[j].replied[k]; // Exit early if there are no results @@ -476,7 +476,7 @@ static void print_results(struct thread_data *thread_data) // Add our own IP address to the results so IP conflicts can be detected // (our own IP address is not included in the ARP scan) - for(unsigned int i = 0; i < NUM_SCANS; i++) + for(unsigned int i = 0; i < thread_data->total_scans; i++) add_result(thread_data->iface, &thread_data->src_addr.sin_addr, &thread_data->dst_addr.sin_addr, thread_data->mac, thread_data->result, thread_data->result_size, i); // Print results @@ -495,7 +495,7 @@ static void print_results(struct thread_data *thread_data) // Check if IP address replied replies = 0u; bool replied = false; - for(unsigned int k = 0; k < NUM_SCANS; k++) + for(unsigned int k = 0; k < thread_data->total_scans; k++) { replied |= thread_data->result[i].device[j].replied[k] > 0; replies += thread_data->result[i].device[j].replied[k] > 0 ? 1 : 0; @@ -523,10 +523,10 @@ static void print_results(struct thread_data *thread_data) thread_data->result[i].device[j].mac[4], thread_data->result[i].device[j].mac[5]); - for(unsigned int k = 0; k < NUM_SCANS; k++) + for(unsigned int k = 0; k < thread_data->total_scans; k++) printf(" %s", thread_data->result[i].device[j].replied[k] > 0 ? "X" : "-"); - printf(" (%u%%)\n", replies * 100 / NUM_SCANS); + printf(" (%u%%)\n", replies * 100 / thread_data->total_scans); } // Print warning if we received multiple replies @@ -540,7 +540,7 @@ static void print_results(struct thread_data *thread_data) putc('\n', stdout); } -int run_arp_scan(const bool scan_all) +int run_arp_scan(const bool scan_all, const bool extreme_mode) { // Check if we are capable of sending ARP packets if(!check_capability(CAP_NET_RAW)) @@ -549,7 +549,6 @@ int run_arp_scan(const bool scan_all) return EXIT_FAILURE; } - arp_all = scan_all; puts("Discovering IPv4 hosts on the network using the Address Resolution Protocol (ARP)...\n"); // Get interface names for available interfaces on this machine @@ -581,6 +580,9 @@ int run_arp_scan(const bool scan_all) memcpy(&thread_data[tid].src_addr, tmp->ifa_addr, sizeof(thread_data[tid].src_addr)); inet_ntop(AF_INET, &thread_data[tid].src_addr.sin_addr, thread_data[tid].ipstr, INET_ADDRSTRLEN); + thread_data[tid].scan_all = scan_all || extreme_mode; + thread_data[tid].total_scans = extreme_mode ? 10*NUM_SCANS : NUM_SCANS; + // Always skip the loopback interface if(thread_data[tid].src_addr.sin_addr.s_addr != htonl(INADDR_LOOPBACK)) { @@ -620,7 +622,7 @@ int run_arp_scan(const bool scan_all) { // Also add up scans for completed threads num_scans += thread_data[i].scanned_addresses; - total_scans += NUM_SCANS * thread_data[i].result_size; + total_scans += thread_data[i].total_scans * thread_data[i].result_size; } } if(!all_done) diff --git a/src/tools/arp-scan.h b/src/tools/arp-scan.h index 0a5b5671..c22eafd4 100644 --- a/src/tools/arp-scan.h +++ b/src/tools/arp-scan.h @@ -11,6 +11,6 @@ #ifndef ARP_SCAN_H #define ARP_SCAN_H -int run_arp_scan(const bool scan_all); +int run_arp_scan(const bool scan_all, const bool extreme_mode); #endif // ARP_SCAN_H From ba8807cc6e4e61c3de8794e661bbebaed852b5bc Mon Sep 17 00:00:00 2001 From: DL6ER Date: Wed, 17 May 2023 22:24:53 +0200 Subject: [PATCH 19/23] Give reply rate in percent instead of showing the reply matrix Signed-off-by: DL6ER --- src/tools/arp-scan.c | 30 +++++++++++++++++++----------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/src/tools/arp-scan.c b/src/tools/arp-scan.c index 408ae639..3fa9ec2e 100644 --- a/src/tools/arp-scan.c +++ b/src/tools/arp-scan.c @@ -471,8 +471,8 @@ static void print_results(struct thread_data *thread_data) // If there is at least one result, print header printf("ARP scan on interface %s (%s/%i) finished\n", thread_data->iface, thread_data->ipstr, thread_data->dst_cidr); - printf("%-16s %-16s %-24s %-17s Reply matrix\n", - "IP address", "Interface", "Hostname", "MAC address"); + printf("%-16s %-16s %-24s %-17s %s\n", + "IP address", "Interface", "Hostname", "MAC address", "Reply rate"); // Add our own IP address to the results so IP conflicts can be detected // (our own IP address is not included in the ARP scan) @@ -483,7 +483,6 @@ static void print_results(struct thread_data *thread_data) for(unsigned int i = 0; i < thread_data->result_size; i++) { unsigned int j = 0, replied_devices = 0; - unsigned int multiple_replies = 0; // Print MAC addresses for(j = 0; j < MAX_MACS; j++) @@ -495,6 +494,7 @@ static void print_results(struct thread_data *thread_data) // Check if IP address replied replies = 0u; bool replied = false; + unsigned int multiple_replies = 0; for(unsigned int k = 0; k < thread_data->total_scans; k++) { replied |= thread_data->result[i].device[j].replied[k] > 0; @@ -513,7 +513,7 @@ static void print_results(struct thread_data *thread_data) inet_ntop(AF_INET, &ip, thread_data->ipstr, INET_ADDRSTRLEN); // Print MAC address - printf("%-16s %-16s %-24s %02x:%02x:%02x:%02x:%02x:%02x ", + printf("%-16s %-16s %-24s %02x:%02x:%02x:%02x:%02x:%02x %u %%\n", thread_data->ipstr, thread_data->iface, get_hostname(&ip), thread_data->result[i].device[j].mac[0], @@ -521,21 +521,29 @@ static void print_results(struct thread_data *thread_data) thread_data->result[i].device[j].mac[2], thread_data->result[i].device[j].mac[3], thread_data->result[i].device[j].mac[4], - thread_data->result[i].device[j].mac[5]); + thread_data->result[i].device[j].mac[5], + replies * 100 / thread_data->total_scans); +#ifdef DEBUG for(unsigned int k = 0; k < thread_data->total_scans; k++) printf(" %s", thread_data->result[i].device[j].replied[k] > 0 ? "X" : "-"); - - printf(" (%u%%)\n", replies * 100 / thread_data->total_scans); +#endif + if(multiple_replies > 0) + printf("WARNING: Received multiple replies from %02x:%02x:%02x:%02x:%02x:%02x for %s in %i scan%s\n", + thread_data->result[i].device[j].mac[0], + thread_data->result[i].device[j].mac[1], + thread_data->result[i].device[j].mac[2], + thread_data->result[i].device[j].mac[3], + thread_data->result[i].device[j].mac[4], + thread_data->result[i].device[j].mac[5], + thread_data->ipstr, + multiple_replies, multiple_replies > 1 ? "s" : ""); } // Print warning if we received multiple replies if(replied_devices > 1) printf("WARNING: Received replies for %s from %i devices\n", thread_data->ipstr, replied_devices); - if(multiple_replies > 0) - printf("WARNING: Received multiple replies for %s in %i scan%s\n", - thread_data->ipstr, multiple_replies, multiple_replies > 1 ? "s" : ""); } putc('\n', stdout); } @@ -633,7 +641,7 @@ int run_arp_scan(const bool scan_all, const bool extreme_mode) if(new_progress > progress) { // Print progress - printf(" %i%%", new_progress); + printf(" %i%% ", new_progress); // Update progress progress = new_progress; From 615be9c2c9ba60e95378c6c727996fe48687e3c6 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Thu, 18 May 2023 09:04:26 +0200 Subject: [PATCH 20/23] Exit early if insufficient memory is available, perform as many interface scans as possible under these conditions Signed-off-by: DL6ER --- src/tools/arp-scan.c | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/tools/arp-scan.c b/src/tools/arp-scan.c index 3fa9ec2e..57bd648b 100644 --- a/src/tools/arp-scan.c +++ b/src/tools/arp-scan.c @@ -399,8 +399,18 @@ static void *arp_scan_iface(void *args) // Allocate memory for ARP response buffer const size_t arp_result_len = 1 << (32 - thread_data->dst_cidr); + struct arp_result *result = calloc(arp_result_len, sizeof(struct arp_result)); + if(result == NULL) + { + // Memory allocation failed due to insufficient memory being + // available + thread_data->status = STATUS_ERROR; + thread_data->error = strerror(ENOMEM); + pthread_exit(NULL); + } + + // Store ARP response buffer in thread_data thread_data->result_size = arp_result_len; - struct arp_result *result = calloc(thread_data->result_size, sizeof(struct arp_result)); thread_data->result = result; for(thread_data->num_scans = 0; thread_data->num_scans < thread_data->total_scans; thread_data->num_scans++) From 4d7640c9bed607dd2888c41a19ff2e9867328e27 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Thu, 18 May 2023 09:08:26 +0200 Subject: [PATCH 21/23] Reduce memory requirements by factor 4x Signed-off-by: DL6ER --- src/tools/arp-scan.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/tools/arp-scan.c b/src/tools/arp-scan.c index 57bd648b..4442e822 100644 --- a/src/tools/arp-scan.c +++ b/src/tools/arp-scan.c @@ -69,11 +69,13 @@ struct arp_header { struct arp_result { struct device { // In extreme mode, we can scan up to 10x more often - unsigned int replied[10*NUM_SCANS]; + unsigned char replied[10*NUM_SCANS]; unsigned char mac[MAC_LENGTH]; } device[MAX_MACS]; }; +const size_t arp_result_size = sizeof(struct arp_result); + enum status { STATUS_INITIALIZING = 0, STATUS_SKIPPED_CIDR_MISMATCH, From d1f70d7d6c1b89266f11a0ac666f8e09ae2b4b33 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Thu, 18 May 2023 09:45:24 +0200 Subject: [PATCH 22/23] Further reduce memory requirements by factor 10x (if not in -x mode) Signed-off-by: DL6ER --- src/tools/arp-scan.c | 184 ++++++++++++++++++++++++++----------------- 1 file changed, 112 insertions(+), 72 deletions(-) diff --git a/src/tools/arp-scan.c b/src/tools/arp-scan.c index 4442e822..14f9c9f0 100644 --- a/src/tools/arp-scan.c +++ b/src/tools/arp-scan.c @@ -68,14 +68,19 @@ struct arp_header { struct arp_result { struct device { + unsigned char replied[NUM_SCANS]; + unsigned char mac[MAC_LENGTH]; + } device[MAX_MACS]; +}; + +struct arp_result_extreme { + struct device_extreme { // In extreme mode, we can scan up to 10x more often unsigned char replied[10*NUM_SCANS]; unsigned char mac[MAC_LENGTH]; } device[MAX_MACS]; }; -const size_t arp_result_size = sizeof(struct arp_result); - enum status { STATUS_INITIALIZING = 0, STATUS_SKIPPED_CIDR_MISMATCH, @@ -86,6 +91,7 @@ enum status { struct thread_data { bool scan_all :1; + bool extreme :1; char iface[IF_NAMESIZE + 1]; char ipstr[INET_ADDRSTRLEN]; unsigned char mac[MAC_LENGTH]; @@ -97,7 +103,10 @@ struct thread_data { uint32_t scanned_addresses; const char *error; struct ifaddrs *ifa; - struct arp_result *result; + union { + struct arp_result_extreme *result_extreme; + struct arp_result *result; + }; struct sockaddr_in src_addr; struct sockaddr_in dst_addr; struct sockaddr_in mask; @@ -105,8 +114,7 @@ struct thread_data { // Sends multiple ARP who-has request on interface ifindex, using source mac src_mac and source ip src_ip. // Iterates over all IP addresses in the range of dst_ip/cidr. -static int send_arps(const int fd, const int ifindex, const char *iface, const unsigned char *src_mac, const char **error, - struct in_addr *src_ip, struct in_addr dst_ip, const int dst_cidr, uint32_t *scanned_addresses) +static int send_arps(const int fd, const int ifindex, struct thread_data *thread_data) { int err = -1; unsigned char buffer[BUF_SIZE]; @@ -134,9 +142,9 @@ static int send_arps(const int fd, const int ifindex, const char *iface, const u memset(arp_req->target_mac, 0x00, MAC_LENGTH); // Source MAC to our own MAC address - memcpy(send_req->h_source, src_mac, MAC_LENGTH); - memcpy(arp_req->sender_mac, src_mac, MAC_LENGTH); - memcpy(socket_address.sll_addr, src_mac, MAC_LENGTH); + memcpy(send_req->h_source, thread_data->mac, MAC_LENGTH); + memcpy(arp_req->sender_mac, thread_data->mac, MAC_LENGTH); + memcpy(socket_address.sll_addr, thread_data->mac, MAC_LENGTH); // Protocol type is ARP send_req->h_proto = htons(ETH_P_ARP); @@ -149,11 +157,12 @@ static int send_arps(const int fd, const int ifindex, const char *iface, const u arp_req->opcode = htons(ARP_REQUEST); // Copy IP address to arp_req - memcpy(arp_req->sender_ip, &src_ip->s_addr, sizeof(src_ip->s_addr)); + memcpy(arp_req->sender_ip, &thread_data->src_addr.sin_addr.s_addr, sizeof(thread_data->src_addr.sin_addr.s_addr)); // Loop over all possible IP addresses in the range dst_ip/cidr // We start at 1 because the first IP address has already been set above - for(unsigned int i = 0; i < (1u << (32 - dst_cidr)); i++) + struct in_addr dst_ip = thread_data->dst_addr.sin_addr; + for(unsigned int i = 0; i < (1u << (32 - thread_data->dst_cidr)); i++) { // Fill in target IP address memcpy(arp_req->target_ip, &dst_ip.s_addr, sizeof(dst_ip.s_addr)); @@ -167,14 +176,14 @@ static int send_arps(const int fd, const int ifindex, const char *iface, const u if (ret == -1) { err = errno; - *error = strerror(err); + thread_data->error = strerror(err); goto out; } // Increment IP address dst_ip.s_addr = htonl(ntohl(dst_ip.s_addr) + 1); - (*scanned_addresses)++; + thread_data->scanned_addresses++; } err = 0; @@ -227,15 +236,16 @@ static int create_arp_socket(const int ifindex, const char *iface, const char ** return arp_socket; } -static void add_result(const char *iface, struct in_addr *rcv_ip, struct in_addr *dst_ip, unsigned char *sender_mac, - struct arp_result *result, const size_t result_len, const unsigned int scan_id) +static void add_result(struct in_addr *rcv_ip, unsigned char *sender_mac, + struct thread_data *thread_data, const unsigned int scan_id) { // Check if we have already found this IP address - uint32_t i = ntohl(rcv_ip->s_addr) - ntohl(dst_ip->s_addr); - if(i >= result_len) + uint32_t i = ntohl(rcv_ip->s_addr) - ntohl(thread_data->dst_addr.sin_addr.s_addr); + if(i >= thread_data->result_size) { - printf("Received IP address %s out of range for interface %s (%u >= %zu)\n", inet_ntoa(*rcv_ip), iface, i, result_len); + printf("Received IP address %s out of range for interface %s (%u >= %zu)\n", + inet_ntoa(*rcv_ip), thread_data->iface, i, thread_data->result_size); return; } @@ -243,27 +253,31 @@ static void add_result(const char *iface, struct in_addr *rcv_ip, struct in_addr unsigned int j = 0; for(; j < MAX_MACS; j++) { + unsigned char *mac = thread_data->extreme ? + thread_data->result_extreme[i].device[j].mac : + thread_data->result[i].device[j].mac; // Check if received MAC is already stored in result[i].device[j].mac - if(memcmp(result[i].device[j].mac, sender_mac, MAC_LENGTH) == 0) + if(memcmp(mac, sender_mac, MAC_LENGTH) == 0) { break; } - // Check if result[i].device[j].mac is all-zero - if(memcmp(result[i].device[j].mac, "\x00\x00\x00\x00\x00\x00", MAC_LENGTH) == 0) + // Check if mac is all-zero + if(memcmp(mac, "\x00\x00\x00\x00\x00\x00", MAC_LENGTH) == 0) { - // Copy MAC address to result[i].device[j].mac - memcpy(result[i].device[j].mac, sender_mac, MAC_LENGTH); + // Copy MAC address to mac + memcpy(mac, sender_mac, MAC_LENGTH); break; } } // Memorize that we have received a reply for this IP address - result[i].device[j].replied[scan_id]++; + thread_data->extreme ? + thread_data->result_extreme[i].device[j].replied[scan_id]++ : + thread_data->result[i].device[j].replied[scan_id]++; } // Read all ARP responses -static ssize_t read_arp(const int fd, const char *iface, struct in_addr *dst_ip, const char **error, - struct arp_result *result, const size_t result_len, const unsigned int scan_id) +static ssize_t read_arp(const int fd, struct thread_data *thread_data) { ssize_t ret = 0; unsigned char buffer[BUF_SIZE]; @@ -282,8 +296,8 @@ static ssize_t read_arp(const int fd, const char *iface, struct in_addr *dst_ip, } // Error - *error = strerror(errno); - printf("recvfrom(): %s", *error); + thread_data->error = strerror(errno); + printf("recvfrom(): %s", thread_data->error); break; } struct ethhdr *rcv_resp = (struct ethhdr *) buffer; @@ -310,7 +324,7 @@ static ssize_t read_arp(const int fd, const char *iface, struct in_addr *dst_ip, #ifdef DEBUG printf("%-16s %-20s\t%02x:%02x:%02x:%02x:%02x:%02x", - iface, inet_ntoa(sender_a), + thread_data->iface, inet_ntoa(sender_a), arp_resp->sender_mac[0], arp_resp->sender_mac[1], arp_resp->sender_mac[2], @@ -318,7 +332,7 @@ static ssize_t read_arp(const int fd, const char *iface, struct in_addr *dst_ip, arp_resp->sender_mac[4], arp_resp->sender_mac[5]); #endif - add_result(iface, &sender_a, dst_ip, arp_resp->sender_mac, result, result_len, scan_id); + add_result(&sender_a, arp_resp->sender_mac, thread_data, thread_data->num_scans); } return ret; @@ -401,19 +415,35 @@ static void *arp_scan_iface(void *args) // Allocate memory for ARP response buffer const size_t arp_result_len = 1 << (32 - thread_data->dst_cidr); - struct arp_result *result = calloc(arp_result_len, sizeof(struct arp_result)); - if(result == NULL) - { - // Memory allocation failed due to insufficient memory being - // available - thread_data->status = STATUS_ERROR; - thread_data->error = strerror(ENOMEM); - pthread_exit(NULL); - } - - // Store ARP response buffer in thread_data thread_data->result_size = arp_result_len; - thread_data->result = result; + if(thread_data->extreme) + { + // Allocate extreme memory for ARP response buffer + struct arp_result_extreme *result = calloc(arp_result_len, sizeof(struct arp_result_extreme)); + if(result == NULL) + { + // Memory allocation failed due to insufficient memory being + // available + thread_data->status = STATUS_ERROR; + thread_data->error = strerror(ENOMEM); + pthread_exit(NULL); + } + thread_data->result_extreme = result; + } + else + { + // Allocate memory for ARP response buffer + struct arp_result *result = calloc(arp_result_len, sizeof(struct arp_result)); + if(result == NULL) + { + // Memory allocation failed due to insufficient memory being + // available + thread_data->status = STATUS_ERROR; + thread_data->error = strerror(ENOMEM); + pthread_exit(NULL); + } + thread_data->result = result; + } for(thread_data->num_scans = 0; thread_data->num_scans < thread_data->total_scans; thread_data->num_scans++) { @@ -421,16 +451,14 @@ static void *arp_scan_iface(void *args) printf("Still scanning interface %s (%s/%i) %i%%...\n", iface, thread_data->ipstr, thread_data->dst_cidr, 100*scan_id/thread_data->total_scans); #endif // Send ARP requests to all IPs in subnet - if(send_arps(arp_socket, ifindex, iface, thread_data->mac, &thread_data->error, &thread_data->src_addr.sin_addr, - thread_data->dst_addr.sin_addr, thread_data->dst_cidr, &thread_data->scanned_addresses) != 0) + if(send_arps(arp_socket, ifindex, thread_data) != 0) { thread_data->status = STATUS_ERROR; break; } // Read ARP responses - if(read_arp(arp_socket, iface, &thread_data->dst_addr.sin_addr, &thread_data->error, - thread_data->result, thread_data->result_size, thread_data->num_scans) != 0) + if(read_arp(arp_socket, thread_data) != 0) { thread_data->status = STATUS_ERROR; break; @@ -466,14 +494,29 @@ static void print_results(struct thread_data *thread_data) } // Check if there are any results - unsigned int replies = 0; + bool any_replies = false; for(unsigned int i = 0; i < thread_data->result_size; i++) for(unsigned int j = 0; j < MAX_MACS; j++) for(unsigned int k = 0; k < thread_data->total_scans; k++) - replies += thread_data->result[i].device[j].replied[k]; + if(thread_data->extreme) + { + if(thread_data->result_extreme[i].device[j].replied[k]) + { + any_replies = true; + break; + } + } + else + { + if(thread_data->result[i].device[j].replied[k]) + { + any_replies = true; + break; + } + } // Exit early if there are no results - if(replies == 0) + if(!any_replies) { printf("No devices replied on interface %s (%s/%i)\n\n", thread_data->iface, thread_data->ipstr, thread_data->dst_cidr); @@ -489,7 +532,7 @@ static void print_results(struct thread_data *thread_data) // Add our own IP address to the results so IP conflicts can be detected // (our own IP address is not included in the ARP scan) for(unsigned int i = 0; i < thread_data->total_scans; i++) - add_result(thread_data->iface, &thread_data->src_addr.sin_addr, &thread_data->dst_addr.sin_addr, thread_data->mac, thread_data->result, thread_data->result_size, i); + add_result(&thread_data->src_addr.sin_addr, thread_data->mac, thread_data, i); // Print results for(unsigned int i = 0; i < thread_data->result_size; i++) @@ -500,18 +543,25 @@ static void print_results(struct thread_data *thread_data) for(j = 0; j < MAX_MACS; j++) { // Check if result[i].mac[j] is all-zero, if so, skip this entry - if(memcmp(thread_data->result[i].device[j].mac, "\x00\x00\x00\x00\x00\x00", 6) == 0) + unsigned char *mac = thread_data->extreme ? + thread_data->result_extreme[i].device[j].mac : + thread_data->result[i].device[j].mac; + if(memcmp(mac, "\x00\x00\x00\x00\x00\x00", 6) == 0) break; - // Check if IP address replied - replies = 0u; bool replied = false; - unsigned int multiple_replies = 0; + unsigned char replies = 0u; + unsigned char multiple_replies = 0; + const unsigned char *rp = thread_data->extreme ? + thread_data->result_extreme[i].device[j].replied : + thread_data->result[i].device[j].replied; + + // Check if IP address replied for(unsigned int k = 0; k < thread_data->total_scans; k++) { - replied |= thread_data->result[i].device[j].replied[k] > 0; - replies += thread_data->result[i].device[j].replied[k] > 0 ? 1 : 0; - multiple_replies += thread_data->result[i].device[j].replied[k] > 1; + replied |= rp[k] > 0; + replies += rp[k] > 0 ? 1 : 0; + multiple_replies += rp[k] > 1; } if(!replied) continue; @@ -528,28 +578,17 @@ static void print_results(struct thread_data *thread_data) printf("%-16s %-16s %-24s %02x:%02x:%02x:%02x:%02x:%02x %u %%\n", thread_data->ipstr, thread_data->iface, get_hostname(&ip), - thread_data->result[i].device[j].mac[0], - thread_data->result[i].device[j].mac[1], - thread_data->result[i].device[j].mac[2], - thread_data->result[i].device[j].mac[3], - thread_data->result[i].device[j].mac[4], - thread_data->result[i].device[j].mac[5], + mac[0], mac[1], mac[2], mac[3], mac[4], mac[5], replies * 100 / thread_data->total_scans); #ifdef DEBUG for(unsigned int k = 0; k < thread_data->total_scans; k++) - printf(" %s", thread_data->result[i].device[j].replied[k] > 0 ? "X" : "-"); + printf(" %s", rp[k] > 0 ? "X" : "-"); #endif if(multiple_replies > 0) - printf("WARNING: Received multiple replies from %02x:%02x:%02x:%02x:%02x:%02x for %s in %i scan%s\n", - thread_data->result[i].device[j].mac[0], - thread_data->result[i].device[j].mac[1], - thread_data->result[i].device[j].mac[2], - thread_data->result[i].device[j].mac[3], - thread_data->result[i].device[j].mac[4], - thread_data->result[i].device[j].mac[5], - thread_data->ipstr, - multiple_replies, multiple_replies > 1 ? "s" : ""); + printf("INFO: Received multiple replies from %02x:%02x:%02x:%02x:%02x:%02x for %s in %i scan%s\n", + mac[0], mac[1], mac[2], mac[3], mac[4], mac[5], + thread_data->ipstr, multiple_replies, multiple_replies > 1 ? "s" : ""); } // Print warning if we received multiple replies @@ -600,6 +639,7 @@ int run_arp_scan(const bool scan_all, const bool extreme_mode) memcpy(&thread_data[tid].src_addr, tmp->ifa_addr, sizeof(thread_data[tid].src_addr)); inet_ntop(AF_INET, &thread_data[tid].src_addr.sin_addr, thread_data[tid].ipstr, INET_ADDRSTRLEN); + thread_data[tid].extreme = extreme_mode; thread_data[tid].scan_all = scan_all || extreme_mode; thread_data[tid].total_scans = extreme_mode ? 10*NUM_SCANS : NUM_SCANS; From 31a90da5ca7464611b5e406613414488c9457ce3 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Sat, 20 May 2023 13:57:20 +0200 Subject: [PATCH 23/23] Align % in reply rate column Signed-off-by: DL6ER --- src/tools/arp-scan.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/arp-scan.c b/src/tools/arp-scan.c index 14f9c9f0..46e3496f 100644 --- a/src/tools/arp-scan.c +++ b/src/tools/arp-scan.c @@ -575,7 +575,7 @@ static void print_results(struct thread_data *thread_data) inet_ntop(AF_INET, &ip, thread_data->ipstr, INET_ADDRSTRLEN); // Print MAC address - printf("%-16s %-16s %-24s %02x:%02x:%02x:%02x:%02x:%02x %u %%\n", + printf("%-16s %-16s %-24s %02x:%02x:%02x:%02x:%02x:%02x %3u %%\n", thread_data->ipstr, thread_data->iface, get_hostname(&ip), mac[0], mac[1], mac[2], mac[3], mac[4], mac[5],