Merge pull request #1557 from pi-hole/new/arp-scan

Add local network scanning feature (ARP)
This commit is contained in:
DL6ER
2023-05-20 14:22:08 +02:00
committed by GitHub
11 changed files with 855 additions and 20 deletions
+2 -2
View File
@@ -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
$<TARGET_OBJECTS:lua>
$<TARGET_OBJECTS:tre-regex>
$<TARGET_OBJECTS:syscalls>
$<TARGET_OBJECTS:tools>
)
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)
+29 -11
View File
@@ -33,7 +33,9 @@
// LUA dependencies
#include "lua/ftl_lua.h"
// run_dhcp_discover()
#include "dhcp-discover.h"
#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 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
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,19 @@ 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[-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);
}
+41
View File
@@ -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
+1
View File
@@ -10,6 +10,7 @@
#ifndef CAPABILITIES_H
#define CAPABILITIES_H
bool check_capability(const unsigned int cap);
bool check_capabilities(void);
#endif //CAPABILITIES_H
+7 -4
View File
@@ -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;
+2 -2
View File
@@ -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));
+20
View File
@@ -0,0 +1,20 @@
# 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
arp-scan.c
arp-scan.h
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)
+735
View File
@@ -0,0 +1,735 @@
/* 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"
// sleepms()
#include "timers.h"
// check_capability()
#include "capabilities.h"
#include <linux/capability.h>
#include <linux/if_packet.h>
#include <linux/if_ether.h>
#include <linux/if_arp.h>
//htons etc
#include <arpa/inet.h>
// How many threads do we spawn at maximum?
// 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
// Protocol definitions
#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
// 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;
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 {
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];
};
enum status {
STATUS_INITIALIZING = 0,
STATUS_SKIPPED_CIDR_MISMATCH,
STATUS_SCANNING,
STATUS_ERROR,
STATUS_COMPLETE
} __attribute__ ((packed));
struct thread_data {
bool scan_all :1;
bool extreme :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;
struct ifaddrs *ifa;
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;
};
// 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, struct thread_data *thread_data)
{
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, 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);
// 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, &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
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));
#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)
{
err = errno;
thread_data->error = strerror(err);
goto out;
}
// Increment IP address
dst_ip.s_addr = htonl(ntohl(dst_ip.s_addr) + 1);
thread_data->scanned_addresses++;
}
err = 0;
out:
return err;
}
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)
{
*error = strerror(errno);
#ifdef DEBUG
printf("Unable to create socket for ARP communications on interface %s: %s\n", iface, *error);
#endif
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)
{
*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;
}
// 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)
{
*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;
}
return arp_socket;
}
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(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), thread_data->iface, i, thread_data->result_size);
return;
}
// Save MAC address
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(mac, sender_mac, MAC_LENGTH) == 0)
{
break;
}
// Check if mac is all-zero
if(memcmp(mac, "\x00\x00\x00\x00\x00\x00", MAC_LENGTH) == 0)
{
// Copy MAC address to mac
memcpy(mac, sender_mac, MAC_LENGTH);
break;
}
}
// Memorize that we have received a reply for this IP address
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, struct thread_data *thread_data)
{
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
thread_data->error = strerror(errno);
printf("recvfrom(): %s", thread_data->error);
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",
thread_data->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
add_result(&sender_a, arp_resp->sender_mac, thread_data, thread_data->num_scans);
}
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 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 thread_data pointer
struct thread_data *thread_data = (struct thread_data*)args;
// Get interface details
struct ifaddrs *ifa = thread_data->ifa;
// Get interface name
const char *iface = thread_data->iface;
// Set interface name as thread name
prctl(PR_SET_NAME, iface, 0, 0, 0);
// Get interface netmask
memcpy(&thread_data->mask, ifa->ifa_netmask, sizeof(thread_data->mask));
// Convert subnet to CIDR
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(thread_data->dst_cidr < 24 && !thread_data->scan_all)
{
thread_data->status = STATUS_SKIPPED_CIDR_MISMATCH;
#ifdef DEBUG
printf("Skipped interface %s (%s/%i)\n", iface, thread_data->ipstr, thread_data->dst_cidr);
#endif
pthread_exit(NULL);
}
#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, &thread_data->error);
// 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
get_hardware_address(arp_socket, iface, thread_data->mac);
// Define destination IP address by masking source IP with netmask
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 - thread_data->dst_cidr);
thread_data->result_size = arp_result_len;
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++)
{
#ifdef DEBUG
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, thread_data) != 0)
{
thread_data->status = STATUS_ERROR;
break;
}
// Read ARP responses
if(read_arp(arp_socket, thread_data) != 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 or -x to force scanning this interface)\n\n",
thread_data->iface, thread_data->ipstr, thread_data->dst_cidr);
return;
}
if(thread_data->status == STATUS_ERROR)
{
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;
}
// Check if there are any results
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++)
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(!any_replies)
{
printf("No devices replied 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",
thread_data->iface, thread_data->ipstr, thread_data->dst_cidr);
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)
for(unsigned int i = 0; i < thread_data->total_scans; 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++)
{
unsigned int j = 0, replied_devices = 0;
// Print MAC addresses
for(j = 0; j < MAX_MACS; j++)
{
// Check if result[i].mac[j] is all-zero, if so, skip this entry
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;
bool replied = false;
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 |= rp[k] > 0;
replies += rp[k] > 0 ? 1 : 0;
multiple_replies += rp[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(thread_data->dst_addr.sin_addr.s_addr) + i);
inet_ntop(AF_INET, &ip, thread_data->ipstr, INET_ADDRSTRLEN);
// Print MAC address
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],
replies * 100 / thread_data->total_scans);
#ifdef DEBUG
for(unsigned int k = 0; k < thread_data->total_scans; k++)
printf(" %s", rp[k] > 0 ? "X" : "-");
#endif
if(multiple_replies > 0)
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
if(replied_devices > 1)
printf("WARNING: Received replies for %s from %i devices\n",
thread_data->ipstr, replied_devices);
}
putc('\n', stdout);
}
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))
{
puts("Error: Insufficient permissions or capabilities (needs CAP_NET_RAW). Try running as root (sudo)");
return EXIT_FAILURE;
}
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);
struct ifaddrs *addrs, *tmp;
getifaddrs(&addrs);
tmp = addrs;
// Loop until there are no more interfaces available
// or we reached the maximum number of threads
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)
{
thread_data[tid].ifa = tmp;
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));
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;
// 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
tmp = tmp->ifa_next;
}
// 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++)
{
if(thread_data[i].status == STATUS_INITIALIZING ||
thread_data[i].status == STATUS_SCANNING)
{
// At least one thread is still scanning
all_done = false;
}
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].scanned_addresses;
total_scans += thread_data[i].total_scans * thread_data[i].result_size;
}
}
if(!all_done)
{
// 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);
}
}
puts("100%\n");
// Wait for all threads to join back with us
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;
}
+16
View File
@@ -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 scan_all, const bool extreme_mode);
#endif // ARP_SCAN_H
@@ -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);
@@ -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