mirror of
https://github.com/pi-hole/FTL.git
synced 2025-12-19 23:08:23 +00:00
Merge remote-tracking branch 'origin/development' into new/http
Signed-off-by: DL6ER <dl6er@dl6er.de>
This commit is contained in:
6
.github/workflows/build.yml
vendored
6
.github/workflows/build.yml
vendored
@@ -24,7 +24,7 @@ jobs:
|
||||
steps:
|
||||
-
|
||||
name: Checkout code
|
||||
uses: actions/checkout@v3.5.0
|
||||
uses: actions/checkout@v3.5.2
|
||||
-
|
||||
name: "Calculate required variables"
|
||||
id: variables
|
||||
@@ -90,7 +90,7 @@ jobs:
|
||||
steps:
|
||||
-
|
||||
name: Checkout code
|
||||
uses: actions/checkout@v3.5.0
|
||||
uses: actions/checkout@v3.5.2
|
||||
-
|
||||
name: "Fix ownership of repository"
|
||||
run: chown -R root .
|
||||
@@ -144,7 +144,7 @@ jobs:
|
||||
steps:
|
||||
-
|
||||
name: Checkout code
|
||||
uses: actions/checkout@v3.5.0
|
||||
uses: actions/checkout@v3.5.2
|
||||
-
|
||||
name: Get Binaries and documentation built in previous jobs
|
||||
uses: actions/download-artifact@v3.0.2
|
||||
|
||||
2
.github/workflows/codespell.yml
vendored
2
.github/workflows/codespell.yml
vendored
@@ -10,7 +10,7 @@ jobs:
|
||||
steps:
|
||||
-
|
||||
name: Checkout repository
|
||||
uses: actions/checkout@v3.5.0
|
||||
uses: actions/checkout@v3.5.2
|
||||
-
|
||||
name: Spell-Checking
|
||||
uses: codespell-project/actions-codespell@master
|
||||
|
||||
27
.github/workflows/stale.yml
vendored
27
.github/workflows/stale.yml
vendored
@@ -2,12 +2,16 @@ name: Mark stale issues
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 8 * * *'
|
||||
- cron: '0 8 * * *'
|
||||
workflow_dispatch:
|
||||
issue_comment:
|
||||
|
||||
env:
|
||||
stale_label: stale
|
||||
|
||||
jobs:
|
||||
stale:
|
||||
|
||||
stale_action:
|
||||
if: github.event_name != 'issue_comment'
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
issues: write
|
||||
@@ -19,8 +23,23 @@ jobs:
|
||||
days-before-stale: 30
|
||||
days-before-close: 5
|
||||
stale-issue-message: 'This issue is stale because it has been open 30 days with no activity. Please comment or update this issue or it will be closed in 5 days.'
|
||||
stale-issue-label: 'stale'
|
||||
stale-issue-label: $stale_label
|
||||
exempt-issue-labels: 'Fixed in next release, Bug, Bug:Confirmed, Bugfix in progress, documentation needed, internal'
|
||||
exempt-all-issue-assignees: true
|
||||
operations-per-run: 300
|
||||
close-issue-reason: 'not_planned'
|
||||
|
||||
remove_stale: # trigger "stale" removal immediately when stale issues are commented on
|
||||
if: github.event_name == 'issue_comment'
|
||||
permissions:
|
||||
contents: read # for actions/checkout
|
||||
issues: write # to edit issues label
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3.4.0
|
||||
- name: Remove 'stale' label
|
||||
run: gh issue edit ${{ github.event.issue.number }} --remove-label $stale_label
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
|
||||
2
.github/workflows/sync-back-to-dev.yml
vendored
2
.github/workflows/sync-back-to-dev.yml
vendored
@@ -11,7 +11,7 @@ jobs:
|
||||
name: Syncing branches
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3.5.0
|
||||
uses: actions/checkout@v3.5.2
|
||||
- name: Opening pull request
|
||||
run: gh pr create -B development -H master --title 'Sync master back into development' --body 'Created by Github action' --label 'internal'
|
||||
env:
|
||||
|
||||
17
deploy.sh
17
deploy.sh
@@ -42,11 +42,18 @@ for dir in "${path[@]}"; do
|
||||
ls -1"
|
||||
)"
|
||||
|
||||
# Only try to create the subdir if does not already exist
|
||||
if [[ "${dir_content[*]}" =~ "${dir}" ]]; then
|
||||
echo "Dir: ${old_path}/${dir} already exists"
|
||||
else
|
||||
echo "Creating dir: ${old_path}/${dir}"
|
||||
# Loop over the dir content and check if this exact dir already exists
|
||||
path_exists=0
|
||||
for content in "${dir_content[@]}"; do
|
||||
if [[ "${content}" == "${dir}" ]]; then
|
||||
echo "Dir: ${old_path}/${dir} already exists"
|
||||
path_exists=1
|
||||
fi
|
||||
done
|
||||
|
||||
# If the dir does not exist, create it
|
||||
if [[ "${path_exists}" -eq 0 ]]; then
|
||||
echo "Dir: ${old_path}/${dir} does not exist. Creating it."
|
||||
sftp -b - "${USER}"@"${HOST}" <<< "cd ${old_path}
|
||||
-mkdir ${dir}"
|
||||
fi
|
||||
|
||||
@@ -194,8 +194,6 @@ set(sources
|
||||
daemon.h
|
||||
datastructure.c
|
||||
datastructure.h
|
||||
dhcp-discover.c
|
||||
dhcp-discover.h
|
||||
dnsmasq_interface.c
|
||||
dnsmasq_interface.h
|
||||
edns0.c
|
||||
@@ -208,6 +206,8 @@ set(sources
|
||||
FTL.h
|
||||
gc.c
|
||||
gc.h
|
||||
gravity-tools.c
|
||||
gravity-tools.h
|
||||
log.c
|
||||
log.h
|
||||
main.c
|
||||
@@ -267,6 +267,7 @@ add_executable(pihole-FTL
|
||||
$<TARGET_OBJECTS:syscalls>
|
||||
$<TARGET_OBJECTS:tomlc99>
|
||||
$<TARGET_OBJECTS:config>
|
||||
$<TARGET_OBJECTS:tools>
|
||||
)
|
||||
if(STATIC)
|
||||
set_target_properties(pihole-FTL PROPERTIES LINK_SEARCH_START_STATIC ON)
|
||||
@@ -352,3 +353,4 @@ add_subdirectory(lua/scripts)
|
||||
add_subdirectory(tre-regex)
|
||||
add_subdirectory(syscalls)
|
||||
add_subdirectory(config)
|
||||
add_subdirectory(tools)
|
||||
|
||||
69
src/args.c
69
src/args.c
@@ -36,8 +36,10 @@
|
||||
#include "shmem.h"
|
||||
// LUA dependencies
|
||||
#include "lua/ftl_lua.h"
|
||||
// gravity_parseList()
|
||||
#include "gravity-tools.h"
|
||||
// run_dhcp_discover()
|
||||
#include "dhcp-discover.h"
|
||||
#include "tools/dhcp-discover.h"
|
||||
// mg_version()
|
||||
#include "webserver/civetweb/civetweb.h"
|
||||
// cJSON_Version()
|
||||
@@ -52,6 +54,10 @@
|
||||
#include "api/api.h"
|
||||
// generate_certificate()
|
||||
#include "webserver/x509.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);
|
||||
@@ -143,7 +149,15 @@ static const char __attribute__ ((pure)) *cli_color(const char *color)
|
||||
return is_term() ? color : "";
|
||||
}
|
||||
|
||||
static inline bool strEndsWith(const char *input, const char *end){
|
||||
// Go back to beginning of line and erase to end of line if STDOUT is a terminal
|
||||
const char __attribute__ ((pure)) *cli_over(void)
|
||||
{
|
||||
// \x1b[K is the ANSI escape sequence for "erase to end of line"
|
||||
return is_term() ? "\r\x1b[K" : "\r";
|
||||
}
|
||||
|
||||
static inline bool strEndsWith(const char *input, const char *end)
|
||||
{
|
||||
return strcmp(input + strlen(input) - strlen(end), end) == 0;
|
||||
}
|
||||
|
||||
@@ -307,6 +321,39 @@ void parse_args(int argc, char* argv[])
|
||||
exit(generate_certificate(argv[2], rsa) ? EXIT_SUCCESS : EXIT_FAILURE);
|
||||
}
|
||||
|
||||
// If the first argument is "gravity" (e.g., /usr/bin/pihole-FTL gravity),
|
||||
// we offer some specialized gravity tools
|
||||
if(argc > 1 && strcmp(argv[1], "gravity") == 0)
|
||||
{
|
||||
// pihole-FTL gravity parseList <infile> <outfile> <adlistID>
|
||||
if(argc == 6 && strcmp(argv[2], "parseList") == 0)
|
||||
{
|
||||
// Parse the given list and write the result to the given file
|
||||
exit(gravity_parseList(argv[3], argv[4], argv[5]));
|
||||
}
|
||||
|
||||
printf("Incorrect usage of pihole-FTL gravity subcommand\n");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
// 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++)
|
||||
{
|
||||
@@ -639,14 +686,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)
|
||||
{
|
||||
@@ -719,8 +758,8 @@ 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("%sConfig options:%s\n", yellow, normal);
|
||||
@@ -751,6 +790,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/-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--totp%s Generate valid TOTP token for 2FA\n", green, normal);
|
||||
printf("\t authentication (if enabled)\n");
|
||||
printf("\t%s-h%s, %shelp%s Display this help and exit\n\n", green, normal, green, normal);
|
||||
|
||||
@@ -23,6 +23,7 @@ const char *cli_qst(void) __attribute__ ((const));
|
||||
const char *cli_done(void) __attribute__ ((pure));
|
||||
const char *cli_bold(void) __attribute__ ((pure));
|
||||
const char *cli_normal(void) __attribute__ ((pure));
|
||||
const char *cli_over(void) __attribute__ ((pure));
|
||||
|
||||
void test_dnsmasq_options(int argc, const char *argv[]);
|
||||
|
||||
|
||||
@@ -20,6 +20,47 @@
|
||||
static const unsigned int capabilityIDs[] = { 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 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"};
|
||||
|
||||
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
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#ifndef CAPABILITIES_H
|
||||
#define CAPABILITIES_H
|
||||
|
||||
bool check_capability(const unsigned int cap);
|
||||
bool check_capabilities(void);
|
||||
|
||||
#endif //CAPABILITIES_H
|
||||
|
||||
1026
src/database/shell.c
1026
src/database/shell.c
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -146,9 +146,9 @@ extern "C" {
|
||||
** [sqlite3_libversion_number()], [sqlite3_sourceid()],
|
||||
** [sqlite_version()] and [sqlite_source_id()].
|
||||
*/
|
||||
#define SQLITE_VERSION "3.41.1"
|
||||
#define SQLITE_VERSION_NUMBER 3041001
|
||||
#define SQLITE_SOURCE_ID "2023-03-10 12:13:52 20399f3eda5ec249d147ba9e48da6e87f969d7966a9a896764ca437ff7e737ff"
|
||||
#define SQLITE_VERSION "3.42.0"
|
||||
#define SQLITE_VERSION_NUMBER 3042000
|
||||
#define SQLITE_SOURCE_ID "2023-05-16 12:36:15 831d0fb2836b71c9bc51067c49fee4b8f18047814f2ff22d817d25195cf350b0"
|
||||
|
||||
/*
|
||||
** CAPI3REF: Run-Time Library Version Numbers
|
||||
@@ -1655,20 +1655,23 @@ SQLITE_API int sqlite3_os_end(void);
|
||||
** must ensure that no other SQLite interfaces are invoked by other
|
||||
** threads while sqlite3_config() is running.</b>
|
||||
**
|
||||
** The sqlite3_config() interface
|
||||
** may only be invoked prior to library initialization using
|
||||
** [sqlite3_initialize()] or after shutdown by [sqlite3_shutdown()].
|
||||
** ^If sqlite3_config() is called after [sqlite3_initialize()] and before
|
||||
** [sqlite3_shutdown()] then it will return SQLITE_MISUSE.
|
||||
** Note, however, that ^sqlite3_config() can be called as part of the
|
||||
** implementation of an application-defined [sqlite3_os_init()].
|
||||
**
|
||||
** The first argument to sqlite3_config() is an integer
|
||||
** [configuration option] that determines
|
||||
** what property of SQLite is to be configured. Subsequent arguments
|
||||
** vary depending on the [configuration option]
|
||||
** in the first argument.
|
||||
**
|
||||
** For most configuration options, the sqlite3_config() interface
|
||||
** may only be invoked prior to library initialization using
|
||||
** [sqlite3_initialize()] or after shutdown by [sqlite3_shutdown()].
|
||||
** The exceptional configuration options that may be invoked at any time
|
||||
** are called "anytime configuration options".
|
||||
** ^If sqlite3_config() is called after [sqlite3_initialize()] and before
|
||||
** [sqlite3_shutdown()] with a first argument that is not an anytime
|
||||
** configuration option, then the sqlite3_config() call will return SQLITE_MISUSE.
|
||||
** Note, however, that ^sqlite3_config() can be called as part of the
|
||||
** implementation of an application-defined [sqlite3_os_init()].
|
||||
**
|
||||
** ^When a configuration option is set, sqlite3_config() returns [SQLITE_OK].
|
||||
** ^If the option is unknown or SQLite is unable to set the option
|
||||
** then this routine returns a non-zero [error code].
|
||||
@@ -1776,6 +1779,23 @@ struct sqlite3_mem_methods {
|
||||
** These constants are the available integer configuration options that
|
||||
** can be passed as the first argument to the [sqlite3_config()] interface.
|
||||
**
|
||||
** Most of the configuration options for sqlite3_config()
|
||||
** will only work if invoked prior to [sqlite3_initialize()] or after
|
||||
** [sqlite3_shutdown()]. The few exceptions to this rule are called
|
||||
** "anytime configuration options".
|
||||
** ^Calling [sqlite3_config()] with a first argument that is not an
|
||||
** anytime configuration option in between calls to [sqlite3_initialize()] and
|
||||
** [sqlite3_shutdown()] is a no-op that returns SQLITE_MISUSE.
|
||||
**
|
||||
** The set of anytime configuration options can change (by insertions
|
||||
** and/or deletions) from one release of SQLite to the next.
|
||||
** As of SQLite version 3.42.0, the complete set of anytime configuration
|
||||
** options is:
|
||||
** <ul>
|
||||
** <li> SQLITE_CONFIG_LOG
|
||||
** <li> SQLITE_CONFIG_PCACHE_HDRSZ
|
||||
** </ul>
|
||||
**
|
||||
** New configuration options may be added in future releases of SQLite.
|
||||
** Existing configuration options might be discontinued. Applications
|
||||
** should check the return code from [sqlite3_config()] to make sure that
|
||||
@@ -2122,28 +2142,28 @@ struct sqlite3_mem_methods {
|
||||
** compile-time option is not set, then the default maximum is 1073741824.
|
||||
** </dl>
|
||||
*/
|
||||
#define SQLITE_CONFIG_SINGLETHREAD 1 /* nil */
|
||||
#define SQLITE_CONFIG_MULTITHREAD 2 /* nil */
|
||||
#define SQLITE_CONFIG_SERIALIZED 3 /* nil */
|
||||
#define SQLITE_CONFIG_MALLOC 4 /* sqlite3_mem_methods* */
|
||||
#define SQLITE_CONFIG_GETMALLOC 5 /* sqlite3_mem_methods* */
|
||||
#define SQLITE_CONFIG_SCRATCH 6 /* No longer used */
|
||||
#define SQLITE_CONFIG_PAGECACHE 7 /* void*, int sz, int N */
|
||||
#define SQLITE_CONFIG_HEAP 8 /* void*, int nByte, int min */
|
||||
#define SQLITE_CONFIG_MEMSTATUS 9 /* boolean */
|
||||
#define SQLITE_CONFIG_MUTEX 10 /* sqlite3_mutex_methods* */
|
||||
#define SQLITE_CONFIG_GETMUTEX 11 /* sqlite3_mutex_methods* */
|
||||
/* previously SQLITE_CONFIG_CHUNKALLOC 12 which is now unused. */
|
||||
#define SQLITE_CONFIG_LOOKASIDE 13 /* int int */
|
||||
#define SQLITE_CONFIG_PCACHE 14 /* no-op */
|
||||
#define SQLITE_CONFIG_GETPCACHE 15 /* no-op */
|
||||
#define SQLITE_CONFIG_LOG 16 /* xFunc, void* */
|
||||
#define SQLITE_CONFIG_URI 17 /* int */
|
||||
#define SQLITE_CONFIG_PCACHE2 18 /* sqlite3_pcache_methods2* */
|
||||
#define SQLITE_CONFIG_GETPCACHE2 19 /* sqlite3_pcache_methods2* */
|
||||
#define SQLITE_CONFIG_SINGLETHREAD 1 /* nil */
|
||||
#define SQLITE_CONFIG_MULTITHREAD 2 /* nil */
|
||||
#define SQLITE_CONFIG_SERIALIZED 3 /* nil */
|
||||
#define SQLITE_CONFIG_MALLOC 4 /* sqlite3_mem_methods* */
|
||||
#define SQLITE_CONFIG_GETMALLOC 5 /* sqlite3_mem_methods* */
|
||||
#define SQLITE_CONFIG_SCRATCH 6 /* No longer used */
|
||||
#define SQLITE_CONFIG_PAGECACHE 7 /* void*, int sz, int N */
|
||||
#define SQLITE_CONFIG_HEAP 8 /* void*, int nByte, int min */
|
||||
#define SQLITE_CONFIG_MEMSTATUS 9 /* boolean */
|
||||
#define SQLITE_CONFIG_MUTEX 10 /* sqlite3_mutex_methods* */
|
||||
#define SQLITE_CONFIG_GETMUTEX 11 /* sqlite3_mutex_methods* */
|
||||
/* previously SQLITE_CONFIG_CHUNKALLOC 12 which is now unused. */
|
||||
#define SQLITE_CONFIG_LOOKASIDE 13 /* int int */
|
||||
#define SQLITE_CONFIG_PCACHE 14 /* no-op */
|
||||
#define SQLITE_CONFIG_GETPCACHE 15 /* no-op */
|
||||
#define SQLITE_CONFIG_LOG 16 /* xFunc, void* */
|
||||
#define SQLITE_CONFIG_URI 17 /* int */
|
||||
#define SQLITE_CONFIG_PCACHE2 18 /* sqlite3_pcache_methods2* */
|
||||
#define SQLITE_CONFIG_GETPCACHE2 19 /* sqlite3_pcache_methods2* */
|
||||
#define SQLITE_CONFIG_COVERING_INDEX_SCAN 20 /* int */
|
||||
#define SQLITE_CONFIG_SQLLOG 21 /* xSqllog, void* */
|
||||
#define SQLITE_CONFIG_MMAP_SIZE 22 /* sqlite3_int64, sqlite3_int64 */
|
||||
#define SQLITE_CONFIG_SQLLOG 21 /* xSqllog, void* */
|
||||
#define SQLITE_CONFIG_MMAP_SIZE 22 /* sqlite3_int64, sqlite3_int64 */
|
||||
#define SQLITE_CONFIG_WIN32_HEAPSIZE 23 /* int nByte */
|
||||
#define SQLITE_CONFIG_PCACHE_HDRSZ 24 /* int *psz */
|
||||
#define SQLITE_CONFIG_PMASZ 25 /* unsigned int szPma */
|
||||
@@ -2378,7 +2398,7 @@ struct sqlite3_mem_methods {
|
||||
** </dd>
|
||||
**
|
||||
** [[SQLITE_DBCONFIG_DQS_DML]]
|
||||
** <dt>SQLITE_DBCONFIG_DQS_DML</td>
|
||||
** <dt>SQLITE_DBCONFIG_DQS_DML</dt>
|
||||
** <dd>The SQLITE_DBCONFIG_DQS_DML option activates or deactivates
|
||||
** the legacy [double-quoted string literal] misfeature for DML statements
|
||||
** only, that is DELETE, INSERT, SELECT, and UPDATE statements. The
|
||||
@@ -2387,7 +2407,7 @@ struct sqlite3_mem_methods {
|
||||
** </dd>
|
||||
**
|
||||
** [[SQLITE_DBCONFIG_DQS_DDL]]
|
||||
** <dt>SQLITE_DBCONFIG_DQS_DDL</td>
|
||||
** <dt>SQLITE_DBCONFIG_DQS_DDL</dt>
|
||||
** <dd>The SQLITE_DBCONFIG_DQS option activates or deactivates
|
||||
** the legacy [double-quoted string literal] misfeature for DDL statements,
|
||||
** such as CREATE TABLE and CREATE INDEX. The
|
||||
@@ -2396,7 +2416,7 @@ struct sqlite3_mem_methods {
|
||||
** </dd>
|
||||
**
|
||||
** [[SQLITE_DBCONFIG_TRUSTED_SCHEMA]]
|
||||
** <dt>SQLITE_DBCONFIG_TRUSTED_SCHEMA</td>
|
||||
** <dt>SQLITE_DBCONFIG_TRUSTED_SCHEMA</dt>
|
||||
** <dd>The SQLITE_DBCONFIG_TRUSTED_SCHEMA option tells SQLite to
|
||||
** assume that database schemas are untainted by malicious content.
|
||||
** When the SQLITE_DBCONFIG_TRUSTED_SCHEMA option is disabled, SQLite
|
||||
@@ -2416,7 +2436,7 @@ struct sqlite3_mem_methods {
|
||||
** </dd>
|
||||
**
|
||||
** [[SQLITE_DBCONFIG_LEGACY_FILE_FORMAT]]
|
||||
** <dt>SQLITE_DBCONFIG_LEGACY_FILE_FORMAT</td>
|
||||
** <dt>SQLITE_DBCONFIG_LEGACY_FILE_FORMAT</dt>
|
||||
** <dd>The SQLITE_DBCONFIG_LEGACY_FILE_FORMAT option activates or deactivates
|
||||
** the legacy file format flag. When activated, this flag causes all newly
|
||||
** created database file to have a schema format version number (the 4-byte
|
||||
@@ -2425,7 +2445,7 @@ struct sqlite3_mem_methods {
|
||||
** any SQLite version back to 3.0.0 ([dateof:3.0.0]). Without this setting,
|
||||
** newly created databases are generally not understandable by SQLite versions
|
||||
** prior to 3.3.0 ([dateof:3.3.0]). As these words are written, there
|
||||
** is now scarcely any need to generated database files that are compatible
|
||||
** is now scarcely any need to generate database files that are compatible
|
||||
** all the way back to version 3.0.0, and so this setting is of little
|
||||
** practical use, but is provided so that SQLite can continue to claim the
|
||||
** ability to generate new database files that are compatible with version
|
||||
@@ -2436,6 +2456,38 @@ struct sqlite3_mem_methods {
|
||||
** not considered a bug since SQLite versions 3.3.0 and earlier do not support
|
||||
** either generated columns or decending indexes.
|
||||
** </dd>
|
||||
**
|
||||
** [[SQLITE_DBCONFIG_STMT_SCANSTATUS]]
|
||||
** <dt>SQLITE_DBCONFIG_STMT_SCANSTATUS</dt>
|
||||
** <dd>The SQLITE_DBCONFIG_STMT_SCANSTATUS option is only useful in
|
||||
** SQLITE_ENABLE_STMT_SCANSTATUS builds. In this case, it sets or clears
|
||||
** a flag that enables collection of the sqlite3_stmt_scanstatus_v2()
|
||||
** statistics. For statistics to be collected, the flag must be set on
|
||||
** the database handle both when the SQL statement is prepared and when it
|
||||
** is stepped. The flag is set (collection of statistics is enabled)
|
||||
** by default. This option takes two arguments: an integer and a pointer to
|
||||
** an integer.. The first argument is 1, 0, or -1 to enable, disable, or
|
||||
** leave unchanged the statement scanstatus option. If the second argument
|
||||
** is not NULL, then the value of the statement scanstatus setting after
|
||||
** processing the first argument is written into the integer that the second
|
||||
** argument points to.
|
||||
** </dd>
|
||||
**
|
||||
** [[SQLITE_DBCONFIG_REVERSE_SCANORDER]]
|
||||
** <dt>SQLITE_DBCONFIG_REVERSE_SCANORDER</dt>
|
||||
** <dd>The SQLITE_DBCONFIG_REVERSE_SCANORDER option changes the default order
|
||||
** in which tables and indexes are scanned so that the scans start at the end
|
||||
** and work toward the beginning rather than starting at the beginning and
|
||||
** working toward the end. Setting SQLITE_DBCONFIG_REVERSE_SCANORDER is the
|
||||
** same as setting [PRAGMA reverse_unordered_selects]. This option takes
|
||||
** two arguments which are an integer and a pointer to an integer. The first
|
||||
** argument is 1, 0, or -1 to enable, disable, or leave unchanged the
|
||||
** reverse scan order flag, respectively. If the second argument is not NULL,
|
||||
** then 0 or 1 is written into the integer that the second argument points to
|
||||
** depending on if the reverse scan order flag is set after processing the
|
||||
** first argument.
|
||||
** </dd>
|
||||
**
|
||||
** </dl>
|
||||
*/
|
||||
#define SQLITE_DBCONFIG_MAINDBNAME 1000 /* const char* */
|
||||
@@ -2456,7 +2508,9 @@ struct sqlite3_mem_methods {
|
||||
#define SQLITE_DBCONFIG_ENABLE_VIEW 1015 /* int int* */
|
||||
#define SQLITE_DBCONFIG_LEGACY_FILE_FORMAT 1016 /* int int* */
|
||||
#define SQLITE_DBCONFIG_TRUSTED_SCHEMA 1017 /* int int* */
|
||||
#define SQLITE_DBCONFIG_MAX 1017 /* Largest DBCONFIG */
|
||||
#define SQLITE_DBCONFIG_STMT_SCANSTATUS 1018 /* int int* */
|
||||
#define SQLITE_DBCONFIG_REVERSE_SCANORDER 1019 /* int int* */
|
||||
#define SQLITE_DBCONFIG_MAX 1019 /* Largest DBCONFIG */
|
||||
|
||||
/*
|
||||
** CAPI3REF: Enable Or Disable Extended Result Codes
|
||||
@@ -6201,6 +6255,13 @@ SQLITE_API void sqlite3_activate_cerod(
|
||||
** of the default VFS is not implemented correctly, or not implemented at
|
||||
** all, then the behavior of sqlite3_sleep() may deviate from the description
|
||||
** in the previous paragraphs.
|
||||
**
|
||||
** If a negative argument is passed to sqlite3_sleep() the results vary by
|
||||
** VFS and operating system. Some system treat a negative argument as an
|
||||
** instruction to sleep forever. Others understand it to mean do not sleep
|
||||
** at all. ^In SQLite version 3.42.0 and later, a negative
|
||||
** argument passed into sqlite3_sleep() is changed to zero before it is relayed
|
||||
** down into the xSleep method of the VFS.
|
||||
*/
|
||||
SQLITE_API int sqlite3_sleep(int);
|
||||
|
||||
@@ -7828,9 +7889,9 @@ SQLITE_API int sqlite3_vfs_unregister(sqlite3_vfs*);
|
||||
** is undefined if the mutex is not currently entered by the
|
||||
** calling thread or is not currently allocated.
|
||||
**
|
||||
** ^If the argument to sqlite3_mutex_enter(), sqlite3_mutex_try(), or
|
||||
** sqlite3_mutex_leave() is a NULL pointer, then all three routines
|
||||
** behave as no-ops.
|
||||
** ^If the argument to sqlite3_mutex_enter(), sqlite3_mutex_try(),
|
||||
** sqlite3_mutex_leave(), or sqlite3_mutex_free() is a NULL pointer,
|
||||
** then any of the four routines behaves as a no-op.
|
||||
**
|
||||
** See also: [sqlite3_mutex_held()] and [sqlite3_mutex_notheld()].
|
||||
*/
|
||||
@@ -9564,18 +9625,28 @@ SQLITE_API int sqlite3_vtab_config(sqlite3*, int op, ...);
|
||||
** [[SQLITE_VTAB_INNOCUOUS]]<dt>SQLITE_VTAB_INNOCUOUS</dt>
|
||||
** <dd>Calls of the form
|
||||
** [sqlite3_vtab_config](db,SQLITE_VTAB_INNOCUOUS) from within the
|
||||
** the [xConnect] or [xCreate] methods of a [virtual table] implmentation
|
||||
** the [xConnect] or [xCreate] methods of a [virtual table] implementation
|
||||
** identify that virtual table as being safe to use from within triggers
|
||||
** and views. Conceptually, the SQLITE_VTAB_INNOCUOUS tag means that the
|
||||
** virtual table can do no serious harm even if it is controlled by a
|
||||
** malicious hacker. Developers should avoid setting the SQLITE_VTAB_INNOCUOUS
|
||||
** flag unless absolutely necessary.
|
||||
** </dd>
|
||||
**
|
||||
** [[SQLITE_VTAB_USES_ALL_SCHEMAS]]<dt>SQLITE_VTAB_USES_ALL_SCHEMAS</dt>
|
||||
** <dd>Calls of the form
|
||||
** [sqlite3_vtab_config](db,SQLITE_VTAB_USES_ALL_SCHEMA) from within the
|
||||
** the [xConnect] or [xCreate] methods of a [virtual table] implementation
|
||||
** instruct the query planner to begin at least a read transaction on
|
||||
** all schemas ("main", "temp", and any ATTACH-ed databases) whenever the
|
||||
** virtual table is used.
|
||||
** </dd>
|
||||
** </dl>
|
||||
*/
|
||||
#define SQLITE_VTAB_CONSTRAINT_SUPPORT 1
|
||||
#define SQLITE_VTAB_INNOCUOUS 2
|
||||
#define SQLITE_VTAB_DIRECTONLY 3
|
||||
#define SQLITE_VTAB_USES_ALL_SCHEMAS 4
|
||||
|
||||
/*
|
||||
** CAPI3REF: Determine The Virtual Table Conflict Policy
|
||||
@@ -10750,16 +10821,20 @@ SQLITE_API int sqlite3session_create(
|
||||
SQLITE_API void sqlite3session_delete(sqlite3_session *pSession);
|
||||
|
||||
/*
|
||||
** CAPIREF: Conigure a Session Object
|
||||
** CAPI3REF: Configure a Session Object
|
||||
** METHOD: sqlite3_session
|
||||
**
|
||||
** This method is used to configure a session object after it has been
|
||||
** created. At present the only valid value for the second parameter is
|
||||
** [SQLITE_SESSION_OBJCONFIG_SIZE].
|
||||
** created. At present the only valid values for the second parameter are
|
||||
** [SQLITE_SESSION_OBJCONFIG_SIZE] and [SQLITE_SESSION_OBJCONFIG_ROWID].
|
||||
**
|
||||
** Arguments for sqlite3session_object_config()
|
||||
*/
|
||||
SQLITE_API int sqlite3session_object_config(sqlite3_session*, int op, void *pArg);
|
||||
|
||||
/*
|
||||
** CAPI3REF: Options for sqlite3session_object_config
|
||||
**
|
||||
** The following values may passed as the the 4th parameter to
|
||||
** The following values may passed as the the 2nd parameter to
|
||||
** sqlite3session_object_config().
|
||||
**
|
||||
** <dt>SQLITE_SESSION_OBJCONFIG_SIZE <dd>
|
||||
@@ -10775,12 +10850,21 @@ SQLITE_API void sqlite3session_delete(sqlite3_session *pSession);
|
||||
**
|
||||
** It is an error (SQLITE_MISUSE) to attempt to modify this setting after
|
||||
** the first table has been attached to the session object.
|
||||
**
|
||||
** <dt>SQLITE_SESSION_OBJCONFIG_ROWID <dd>
|
||||
** This option is used to set, clear or query the flag that enables
|
||||
** collection of data for tables with no explicit PRIMARY KEY.
|
||||
**
|
||||
** Normally, tables with no explicit PRIMARY KEY are simply ignored
|
||||
** by the sessions module. However, if this flag is set, it behaves
|
||||
** as if such tables have a column "_rowid_ INTEGER PRIMARY KEY" inserted
|
||||
** as their leftmost columns.
|
||||
**
|
||||
** It is an error (SQLITE_MISUSE) to attempt to modify this setting after
|
||||
** the first table has been attached to the session object.
|
||||
*/
|
||||
SQLITE_API int sqlite3session_object_config(sqlite3_session*, int op, void *pArg);
|
||||
|
||||
/*
|
||||
*/
|
||||
#define SQLITE_SESSION_OBJCONFIG_SIZE 1
|
||||
#define SQLITE_SESSION_OBJCONFIG_SIZE 1
|
||||
#define SQLITE_SESSION_OBJCONFIG_ROWID 2
|
||||
|
||||
/*
|
||||
** CAPI3REF: Enable Or Disable A Session Object
|
||||
@@ -11913,9 +11997,23 @@ SQLITE_API int sqlite3changeset_apply_v2(
|
||||
** Invert the changeset before applying it. This is equivalent to inverting
|
||||
** a changeset using sqlite3changeset_invert() before applying it. It is
|
||||
** an error to specify this flag with a patchset.
|
||||
**
|
||||
** <dt>SQLITE_CHANGESETAPPLY_IGNORENOOP <dd>
|
||||
** Do not invoke the conflict handler callback for any changes that
|
||||
** would not actually modify the database even if they were applied.
|
||||
** Specifically, this means that the conflict handler is not invoked
|
||||
** for:
|
||||
** <ul>
|
||||
** <li>a delete change if the row being deleted cannot be found,
|
||||
** <li>an update change if the modified fields are already set to
|
||||
** their new values in the conflicting row, or
|
||||
** <li>an insert change if all fields of the conflicting row match
|
||||
** the row being inserted.
|
||||
** </ul>
|
||||
*/
|
||||
#define SQLITE_CHANGESETAPPLY_NOSAVEPOINT 0x0001
|
||||
#define SQLITE_CHANGESETAPPLY_INVERT 0x0002
|
||||
#define SQLITE_CHANGESETAPPLY_IGNORENOOP 0x0004
|
||||
|
||||
/*
|
||||
** CAPI3REF: Constants Passed To The Conflict Handler
|
||||
|
||||
4
src/gc.c
4
src/gc.c
@@ -26,7 +26,7 @@
|
||||
#include "database/message-table.h"
|
||||
// get_nprocs()
|
||||
#include <sys/sysinfo.h>
|
||||
// get_filepath_usage()
|
||||
// get_path_usage()
|
||||
#include "files.h"
|
||||
// void calc_cpu_usage()
|
||||
#include "daemon.h"
|
||||
@@ -95,7 +95,7 @@ static int check_space(const char *file, unsigned int LastUsage)
|
||||
log_debug(DEBUG_GC, "Checking free space at %s: %u%% %s %u%%", file, perc,
|
||||
perc > config.misc.check.disk.v.ui ? ">" : "<=",
|
||||
config.misc.check.disk.v.ui);
|
||||
if(perc > config.misc.check.disk.v.ui && perc > LastUsage )
|
||||
if(perc > config.misc.check.disk.v.ui && perc > LastUsage && perc <= 100.0)
|
||||
log_resource_shortage(-1.0, 0, -1, perc, file, buffer);
|
||||
|
||||
return perc;
|
||||
|
||||
402
src/gravity-tools.c
Normal file
402
src/gravity-tools.c
Normal file
@@ -0,0 +1,402 @@
|
||||
/* 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
|
||||
* Gravity tools collection routines
|
||||
*
|
||||
* This file is copyright under the latest version of the EUPL.
|
||||
* Please see LICENSE file for your rights under this license. */
|
||||
|
||||
#include "gravity-tools.h"
|
||||
#include "args.h"
|
||||
#include <regex.h>
|
||||
#include "database/sqlite3.h"
|
||||
|
||||
// Define valid domain patterns
|
||||
// No need to include uppercase letters, as we convert to lowercase in gravity_ParseFileIntoDomains() already
|
||||
// Adapted from https://stackoverflow.com/a/30007882
|
||||
// - Added "(?:...)" to form non-capturing groups (slightly faster)
|
||||
#define TLD_PATTERN "[a-z0-9][a-z0-9-]{0,61}[a-z0-9]"
|
||||
#define SUBDOMAIN_PATTERN "([a-z0-9_-]{0,63}\\.)"
|
||||
|
||||
// supported exact style: subdomain.domain.tld
|
||||
// SUBDOMAIN_PATTERN is mandatory for exact style, disallowing TLD blocking
|
||||
#define VALID_DOMAIN_REXEX SUBDOMAIN_PATTERN"+"TLD_PATTERN
|
||||
|
||||
// supported ABP style: ||subdomain.domain.tlp^
|
||||
// SUBDOMAIN_PATTERN is optional for ABP style, allowing TLD blocking: ||tld^
|
||||
// See https://github.com/pi-hole/pi-hole/pull/5240
|
||||
#define ABP_DOMAIN_REXEX "\\|\\|"SUBDOMAIN_PATTERN"*"TLD_PATTERN"\\^"
|
||||
|
||||
// Detects ABP extended CSS selectors ("##", "#!#", "#@#", "#?#") preceded by a letter
|
||||
#define ABP_CSS_SELECTORS "[a-z]#[$?@]{0,1}#"
|
||||
|
||||
// A list of items of common local hostnames not to report as unusable
|
||||
// Some lists (i.e StevenBlack's) contain these as they are supposed to be used as HOST files
|
||||
// but flagging them as unusable causes more confusion than it's worth - so we suppress them from the output
|
||||
#define FALSE_POSITIVES "localhost|localhost.localdomain|local|broadcasthost|localhost|ip6-localhost|ip6-loopback|lo0 localhost|ip6-localnet|ip6-mcastprefix|ip6-allnodes|ip6-allrouters|ip6-allhosts"
|
||||
|
||||
// Print progress for files larger than 10 MB
|
||||
// This is to avoid printing progress for small files
|
||||
// which would be printed too often as affect performance
|
||||
#define PRINT_PROGRESS_THRESHOLD 10*1000*1000
|
||||
|
||||
// Number of invalid domains to print before skipping the rest
|
||||
#define MAX_INVALID_DOMAINS 5
|
||||
|
||||
int gravity_parseList(const char *infile, const char *outfile, const char *adlistIDstr)
|
||||
{
|
||||
const char *info = cli_info();
|
||||
const char *tick = cli_tick();
|
||||
const char *cross = cli_cross();
|
||||
const char *over = cli_over();
|
||||
|
||||
// Open input file
|
||||
FILE *fpin = fopen(infile, "r");
|
||||
if(fpin == NULL)
|
||||
{
|
||||
printf("%s %s Unable to open %s for reading\n", over, cross, infile);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
// Open output file
|
||||
sqlite3 *db = NULL;
|
||||
sqlite3_stmt *stmt = NULL;
|
||||
if(sqlite3_open_v2(outfile, &db, SQLITE_OPEN_READWRITE, NULL) != SQLITE_OK)
|
||||
{
|
||||
printf("%s %s Unable to open database file %s for writing\n", over, cross, outfile);
|
||||
fclose(fpin);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
// Get size of input file
|
||||
fseek(fpin, 0L, SEEK_END);
|
||||
const size_t fsize = ftell(fpin);
|
||||
rewind(fpin);
|
||||
|
||||
// Compile regular expression to validate domains
|
||||
regex_t exact_regex, abp_regex, false_positives_regex, abp_css_regex;
|
||||
if(regcomp(&exact_regex, VALID_DOMAIN_REXEX, REG_EXTENDED) != 0)
|
||||
{
|
||||
printf("%s %s Unable to compile regular expression to validate exact domains\n",
|
||||
over, cross);
|
||||
fclose(fpin);
|
||||
sqlite3_close(db);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
if(regcomp(&abp_regex, ABP_DOMAIN_REXEX, REG_EXTENDED) != 0)
|
||||
{
|
||||
printf("%s %s Unable to compile regular expression to validate ABP-style domains\n",
|
||||
over, cross);
|
||||
fclose(fpin);
|
||||
sqlite3_close(db);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
if(regcomp(&false_positives_regex, FALSE_POSITIVES, REG_EXTENDED | REG_NOSUB) != 0)
|
||||
{
|
||||
printf("%s %s Unable to compile regular expression to identify false positives\n",
|
||||
over, cross);
|
||||
fclose(fpin);
|
||||
sqlite3_close(db);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
if(regcomp(&abp_css_regex, ABP_CSS_SELECTORS, REG_EXTENDED) != 0)
|
||||
{
|
||||
printf("%s %s Unable to compile regular expression to validate ABP-style domains\n",
|
||||
over, cross);
|
||||
fclose(fpin);
|
||||
sqlite3_close(db);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
// Begin transaction
|
||||
if(sqlite3_exec(db, "BEGIN TRANSACTION;", NULL, NULL, NULL) != SQLITE_OK)
|
||||
{
|
||||
printf("%s %s Unable to begin transaction to insert domains into database file %s\n",
|
||||
over, cross, outfile);
|
||||
fclose(fpin);
|
||||
sqlite3_close(db);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
// Prepare SQL statement
|
||||
const char *sql = "INSERT INTO gravity (domain, adlist_id) VALUES (?, ?);";
|
||||
if(sqlite3_prepare_v2(db, sql, -1, &stmt, NULL) != SQLITE_OK)
|
||||
{
|
||||
printf("%s %s Unable to prepare SQL statement to insert domains into database file %s\n",
|
||||
over, cross, outfile);
|
||||
fclose(fpin);
|
||||
sqlite3_close(db);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
// Bind adlistID
|
||||
const int adlistID = atoi(adlistIDstr);
|
||||
if(sqlite3_bind_int(stmt, 2, adlistID) != SQLITE_OK)
|
||||
{
|
||||
printf("%s %s Unable to bind adlistID to SQL statement to insert domains into database file %s\n",
|
||||
over, cross, outfile);
|
||||
fclose(fpin);
|
||||
sqlite3_close(db);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
// Parse list file line by line
|
||||
char *line = NULL;
|
||||
size_t lineno = 0;
|
||||
size_t len = 0;
|
||||
ssize_t read = 0;
|
||||
size_t total_read = 0;
|
||||
int last_progress = 0;
|
||||
char *invalid_domains_list[MAX_INVALID_DOMAINS] = { NULL };
|
||||
unsigned int invalid_domains_list_len = 0;
|
||||
unsigned int exact_domains = 0, abp_domains = 0, invalid_domains = 0;
|
||||
while((read = getline(&line, &len, fpin)) != -1)
|
||||
{
|
||||
// Update total read bytes
|
||||
total_read += read;
|
||||
lineno++;
|
||||
|
||||
// Remove leading whitespace
|
||||
// isspace() matches spaces, tabs, form feeds, line breaks,
|
||||
// carriage returns, and vertical tabs
|
||||
while(isspace(line[0]))
|
||||
line++;
|
||||
|
||||
// Skip comments
|
||||
// # is used for comments in HOSTS files
|
||||
// ! is used for comments in AdBlock-style files
|
||||
// [ is used for comments in AdGuard-style files and ABP headers
|
||||
if(line[0] == '#' || line[0] == '!' || line[0] == '[')
|
||||
continue;
|
||||
|
||||
// Remove trailing newline
|
||||
if(line[read-1] == '\n')
|
||||
line[--read] = '\0';
|
||||
|
||||
// Remove trailing carriage return (Windows)
|
||||
if(line[read-1] == '\r')
|
||||
line[--read] = '\0';
|
||||
|
||||
// Remove trailing dot (convert FQDN to domain)
|
||||
if(line[read-1] == '.')
|
||||
line[--read] = '\0';
|
||||
|
||||
// Skip empty lines
|
||||
if(line[0] == '\0')
|
||||
continue;
|
||||
|
||||
regmatch_t match = { 0 };
|
||||
// Validate line
|
||||
if(line[0] != '|' && // <- Not an ABP-style match
|
||||
regexec(&exact_regex, line, 1, &match, 0) == 0 && // <- Regex match
|
||||
match.rm_so == 0 && match.rm_eo == read) // <- Match covers entire line
|
||||
{
|
||||
// Exact match found
|
||||
// Append domain to database using prepared statement
|
||||
if(sqlite3_bind_text(stmt, 1, line, -1, SQLITE_STATIC) != SQLITE_OK)
|
||||
{
|
||||
printf("%s %s Unable to bind domain to SQL statement to insert domains into database file %s\n",
|
||||
over, cross, outfile);
|
||||
fclose(fpin);
|
||||
sqlite3_close(db);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
if(sqlite3_step(stmt) != SQLITE_DONE)
|
||||
{
|
||||
printf("%s %s Unable to insert domain into database file %s\n", over, cross, outfile);
|
||||
fclose(fpin);
|
||||
sqlite3_close(db);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
sqlite3_reset(stmt);
|
||||
// Increment counter
|
||||
exact_domains++;
|
||||
}
|
||||
else if(line[0] == '|' && // <- ABP-style match
|
||||
regexec(&abp_regex, line, 1, &match, 0) == 0 && // <- Regex match
|
||||
match.rm_so == 0 && match.rm_eo == read) // <- Match covers entire line
|
||||
{
|
||||
// ABP-style match (see comments above)
|
||||
|
||||
// Skip lines containing ABP extended CSS selectors
|
||||
// ("##", "#!#", "#@#", "#?#") preceded by a letter
|
||||
// See https://github.com/pi-hole/pi-hole/pull/5247 for
|
||||
// further information on why this is necessary
|
||||
if(regexec(&abp_css_regex, line, 1, &match, 0) == 0)
|
||||
continue;
|
||||
|
||||
// Append pattern to database using prepared statement
|
||||
if(sqlite3_bind_text(stmt, 1, line, -1, SQLITE_STATIC) != SQLITE_OK)
|
||||
{
|
||||
printf("%s %s Unable to bind domain to SQL statement to insert domains into database file %s\n",
|
||||
over, cross, outfile);
|
||||
fclose(fpin);
|
||||
sqlite3_close(db);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
if(sqlite3_step(stmt) != SQLITE_DONE)
|
||||
{
|
||||
printf("%s %s Unable to insert domain into database file %s\n", over, cross, outfile);
|
||||
fclose(fpin);
|
||||
sqlite3_close(db);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
sqlite3_reset(stmt);
|
||||
abp_domains++;
|
||||
}
|
||||
else
|
||||
{
|
||||
// No match - add to list of invalid domains
|
||||
if(invalid_domains_list_len < MAX_INVALID_DOMAINS)
|
||||
{
|
||||
// Check if we have this domain already
|
||||
bool found = false;
|
||||
for(unsigned int i = 0; i < invalid_domains_list_len; i++)
|
||||
{
|
||||
if(strcmp(invalid_domains_list[i], line) == 0)
|
||||
{
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If not found, check if this is a false
|
||||
// positive and add it to the list if it is not
|
||||
if(!found && regexec(&false_positives_regex, line, 0, NULL, 0) != 0)
|
||||
invalid_domains_list[invalid_domains_list_len++] = strdup(line);
|
||||
}
|
||||
invalid_domains++;
|
||||
}
|
||||
|
||||
// Print progress if the file is large enough every 100 lines
|
||||
if(fsize > PRINT_PROGRESS_THRESHOLD && lineno % 100 == 1)
|
||||
{
|
||||
// Calculate progress
|
||||
const int progress = (int)(100.0*total_read/fsize);
|
||||
// Print progress if it has changed
|
||||
if(progress > last_progress)
|
||||
{
|
||||
printf("%s %s Processed %i%% of downloaded list", over, info, progress);
|
||||
fflush(stdout);
|
||||
last_progress = progress;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Finalize SQL statement
|
||||
if(sqlite3_finalize(stmt) != SQLITE_OK)
|
||||
{
|
||||
printf("%s %s Unable to finalize SQL statement to insert domains into database file %s\n",
|
||||
over, cross, outfile);
|
||||
fclose(fpin);
|
||||
sqlite3_close(db);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
// Update database properties
|
||||
// Are ABP patterns used?
|
||||
if(abp_domains > 0)
|
||||
{
|
||||
sql = "INSERT OR REPLACE INTO info (property,value) VALUES ('abp_domains',1);";
|
||||
if(sqlite3_exec(db, sql, NULL, NULL, NULL) != SQLITE_OK)
|
||||
{
|
||||
printf("%s %s Unable to update database properties in database file %s\n",
|
||||
over, cross, outfile);
|
||||
fclose(fpin);
|
||||
sqlite3_close(db);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
// Update number of domains on this list
|
||||
sql = "UPDATE adlist SET number = ?, invalid_domains = ? WHERE id = ?;";
|
||||
if(sqlite3_prepare_v2(db, sql, -1, &stmt, NULL) != SQLITE_OK)
|
||||
{
|
||||
printf("%s %s Unable to prepare SQL statement to update adlist properties in database file %s\n",
|
||||
over, cross, outfile);
|
||||
fclose(fpin);
|
||||
sqlite3_close(db);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
// Update date
|
||||
if(sqlite3_bind_int(stmt, 1, exact_domains) != SQLITE_OK)
|
||||
{
|
||||
printf("%s %s Unable to bind number of domains to SQL statement to update adlist properties in database file %s\n",
|
||||
over, cross, outfile);
|
||||
fclose(fpin);
|
||||
sqlite3_close(db);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
if(sqlite3_bind_int(stmt, 2, invalid_domains) != SQLITE_OK)
|
||||
{
|
||||
printf("%s %s Unable to bind number of invalid domains to SQL statement to update adlist properties in database file %s\n",
|
||||
over, cross, outfile);
|
||||
fclose(fpin);
|
||||
sqlite3_close(db);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
if(sqlite3_bind_int(stmt, 3, adlistID) != SQLITE_OK)
|
||||
{
|
||||
printf("%s %s Unable to bind adlist ID to SQL statement to update adlist properties in database file %s\n",
|
||||
over, cross, outfile);
|
||||
fclose(fpin);
|
||||
sqlite3_close(db);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
if(sqlite3_step(stmt) != SQLITE_DONE)
|
||||
{
|
||||
printf("%s %s Unable to update adlist properties in database file %s\n",
|
||||
over, cross, outfile);
|
||||
fclose(fpin);
|
||||
sqlite3_close(db);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
if(sqlite3_finalize(stmt) != SQLITE_OK)
|
||||
{
|
||||
printf("%s %s Unable to finalize SQL statement to update adlist properties in database file %s\n",
|
||||
over, cross, outfile);
|
||||
fclose(fpin);
|
||||
sqlite3_close(db);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
// End transaction
|
||||
if(sqlite3_exec(db, "END TRANSACTION", NULL, NULL, NULL) != SQLITE_OK)
|
||||
{
|
||||
printf("%s %s Unable to end transaction to insert domains into database file %s (database file may be corrupted)\n",
|
||||
over, cross, outfile);
|
||||
fclose(fpin);
|
||||
sqlite3_close(db);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
// Print summary
|
||||
printf("%s %s Parsed %u exact domains and %u ABP-style domains (ignored %u non-domain entries)\n",
|
||||
over, tick, exact_domains, abp_domains, invalid_domains);
|
||||
if(invalid_domains_list_len > 0)
|
||||
{
|
||||
puts(" Sample of non-domain entries:");
|
||||
for(unsigned int i = 0; i < invalid_domains_list_len; i++)
|
||||
printf(" - \"%s\"\n", invalid_domains_list[i]);
|
||||
puts("");
|
||||
}
|
||||
|
||||
// Free memory
|
||||
free(line);
|
||||
regfree(&exact_regex);
|
||||
regfree(&abp_regex);
|
||||
regfree(&false_positives_regex);
|
||||
regfree(&abp_css_regex);
|
||||
for(unsigned int i = 0; i < invalid_domains_list_len; i++)
|
||||
if(invalid_domains_list[i] != NULL)
|
||||
free(invalid_domains_list[i]);
|
||||
|
||||
// Close files
|
||||
fclose(fpin);
|
||||
sqlite3_close(db);
|
||||
|
||||
// Return success
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
13
src/gravity-tools.h
Normal file
13
src/gravity-tools.h
Normal file
@@ -0,0 +1,13 @@
|
||||
/* 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
|
||||
* Gravity tools collection prototypes
|
||||
*
|
||||
* This file is copyright under the latest version of the EUPL.
|
||||
* Please see LICENSE file for your rights under this license. */
|
||||
|
||||
#include "FTL.h"
|
||||
|
||||
int gravity_parseList(const char *infile, const char *outfile, const char *adlistID);
|
||||
@@ -76,7 +76,7 @@ static int luaB_auxwrap (lua_State *L) {
|
||||
if (l_unlikely(r < 0)) { /* error? */
|
||||
int stat = lua_status(co);
|
||||
if (stat != LUA_OK && stat != LUA_YIELD) { /* error in the coroutine? */
|
||||
stat = lua_resetthread(co, L); /* close its tbc variables */
|
||||
stat = lua_closethread(co, L); /* close its tbc variables */
|
||||
lua_assert(stat != LUA_OK);
|
||||
lua_xmove(co, L, 1); /* move error message to the caller */
|
||||
}
|
||||
@@ -172,7 +172,7 @@ static int luaB_close (lua_State *L) {
|
||||
int status = auxstatus(L, co);
|
||||
switch (status) {
|
||||
case COS_DEAD: case COS_YIELD: {
|
||||
status = lua_resetthread(co, L);
|
||||
status = lua_closethread(co, L);
|
||||
if (status == LUA_OK) {
|
||||
lua_pushboolean(L, 1);
|
||||
return 1;
|
||||
|
||||
@@ -339,7 +339,7 @@ int luaE_resetthread (lua_State *L, int status) {
|
||||
}
|
||||
|
||||
|
||||
LUA_API int lua_resetthread (lua_State *L, lua_State *from) {
|
||||
LUA_API int lua_closethread (lua_State *L, lua_State *from) {
|
||||
int status;
|
||||
lua_lock(L);
|
||||
L->nCcalls = (from) ? getCcalls(from) : 0;
|
||||
@@ -349,6 +349,14 @@ LUA_API int lua_resetthread (lua_State *L, lua_State *from) {
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Deprecated! Use 'lua_closethread' instead.
|
||||
*/
|
||||
LUA_API int lua_resetthread (lua_State *L) {
|
||||
return lua_closethread(L, NULL);
|
||||
}
|
||||
|
||||
|
||||
LUA_API lua_State *lua_newstate (lua_Alloc f, void *ud) {
|
||||
int i;
|
||||
lua_State *L;
|
||||
|
||||
@@ -18,10 +18,10 @@
|
||||
|
||||
#define LUA_VERSION_MAJOR "5"
|
||||
#define LUA_VERSION_MINOR "4"
|
||||
#define LUA_VERSION_RELEASE "5"
|
||||
#define LUA_VERSION_RELEASE "6"
|
||||
|
||||
#define LUA_VERSION_NUM 504
|
||||
#define LUA_VERSION_RELEASE_NUM (LUA_VERSION_NUM * 100 + 5)
|
||||
#define LUA_VERSION_RELEASE_NUM (LUA_VERSION_NUM * 100 + 6)
|
||||
|
||||
#define LUA_VERSION "Lua " LUA_VERSION_MAJOR "." LUA_VERSION_MINOR
|
||||
#define LUA_RELEASE LUA_VERSION "." LUA_VERSION_RELEASE
|
||||
@@ -163,7 +163,8 @@ extern const char lua_ident[];
|
||||
LUA_API lua_State *(lua_newstate) (lua_Alloc f, void *ud);
|
||||
LUA_API void (lua_close) (lua_State *L);
|
||||
LUA_API lua_State *(lua_newthread) (lua_State *L);
|
||||
LUA_API int (lua_resetthread) (lua_State *L, lua_State *from);
|
||||
LUA_API int (lua_closethread) (lua_State *L, lua_State *from);
|
||||
LUA_API int (lua_resetthread) (lua_State *L); /* Deprecated! */
|
||||
|
||||
LUA_API lua_CFunction (lua_atpanic) (lua_State *L, lua_CFunction panicf);
|
||||
|
||||
|
||||
@@ -32,9 +32,12 @@ 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)
|
||||
log_warn("Could not recvfrom() in %s() (%s:%i): %s",
|
||||
func, file, line, strerror(errno));
|
||||
|
||||
|
||||
@@ -33,8 +33,9 @@ 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)
|
||||
// and EPERM + ENOKEY (DHCP probing)
|
||||
if(ret < 0 && errno != EPROTONOSUPPORT && errno != EPERM && errno != ENOKEY)
|
||||
log_warn("Could not sendto() in %s() (%s:%i): %s",
|
||||
func, file, line, strerror(errno));
|
||||
|
||||
|
||||
20
src/tools/CMakeLists.txt
Normal file
20
src/tools/CMakeLists.txt
Normal 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)
|
||||
747
src/tools/arp-scan.c
Normal file
747
src/tools/arp-scan.c
Normal file
@@ -0,0 +1,747 @@
|
||||
/* 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 %u 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)
|
||||
{
|
||||
// Skip interface scan if ...
|
||||
// - interface is not up
|
||||
// - ARP is not supported
|
||||
// - interface is loopback net
|
||||
if(!(tmp->ifa_flags & IFF_UP) ||
|
||||
(tmp->ifa_flags & IFF_NOARP) ||
|
||||
(tmp->ifa_flags & IFF_LOOPBACK))
|
||||
{
|
||||
tmp = tmp->ifa_next;
|
||||
continue;
|
||||
}
|
||||
|
||||
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(" %u%% ", 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
src/tools/arp-scan.h
Normal file
16
src/tools/arp-scan.h
Normal 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
|
||||
@@ -19,6 +19,8 @@
|
||||
#include "config/config.h"
|
||||
// cli_bold(), etc.
|
||||
#include "args.h"
|
||||
// check_capability()
|
||||
#include "capabilities.h"
|
||||
|
||||
#include <sys/time.h>
|
||||
// SIOCGIFHWADDR
|
||||
@@ -60,6 +62,15 @@
|
||||
|
||||
// Global lock used by all threads
|
||||
static pthread_mutex_t lock;
|
||||
static void __attribute__((format(gnu_printf, 1, 2))) printf_locked(const char *format, ...)
|
||||
{
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
pthread_mutex_lock(&lock);
|
||||
vprintf(format, args);
|
||||
pthread_mutex_unlock(&lock);
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
extern const struct opttab_t {
|
||||
char *name;
|
||||
@@ -84,17 +95,17 @@ static int create_dhcp_socket(const char *iname)
|
||||
const int sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
|
||||
if(sock < 0)
|
||||
{
|
||||
printf("Error: Could not create socket for interface %s!!\n", iname);
|
||||
printf_locked("Error: Could not create socket for interface %s!\n", iname);
|
||||
return -1;
|
||||
}
|
||||
|
||||
#ifdef DEBUG
|
||||
printf("DHCP socket: %d\n", sock);
|
||||
printf_locked("DHCP socket: %d\n", sock);
|
||||
#endif
|
||||
// set the reuse address flag so we don't get errors when restarting
|
||||
if(setsockopt(sock,SOL_SOCKET, SO_REUSEADDR, (char *)&flag, sizeof(flag))<0)
|
||||
{
|
||||
printf("Error: Could not set reuse address option on DHCP socket (%s)!\n", iname);
|
||||
printf_locked("Error: Could not set reuse address option on DHCP socket (%s)!\n", iname);
|
||||
close(sock);
|
||||
return -1;
|
||||
}
|
||||
@@ -102,7 +113,7 @@ static int create_dhcp_socket(const char *iname)
|
||||
// set the broadcast option - we need this to listen to DHCP broadcast messages
|
||||
if(setsockopt(sock, SOL_SOCKET,SO_BROADCAST, (char *)&flag, sizeof flag) < 0)
|
||||
{
|
||||
printf("Error: Could not set broadcast option on DHCP socket (%s)!\n", iname);
|
||||
printf_locked("Error: Could not set broadcast option on DHCP socket (%s)!\n", iname);
|
||||
close(sock);
|
||||
return -1;
|
||||
}
|
||||
@@ -111,8 +122,8 @@ static int create_dhcp_socket(const char *iname)
|
||||
strncpy(interface.ifr_ifrn.ifrn_name, iname, IFNAMSIZ-1);
|
||||
if(setsockopt(sock,SOL_SOCKET, SO_BINDTODEVICE, (char *)&interface, sizeof(interface)) < 0)
|
||||
{
|
||||
printf("Error: Could not bind socket to interface %s (%s)\n ---> Check your privileges (run with sudo)!\n",
|
||||
iname, strerror(errno));
|
||||
printf_locked("Error: Could not bind socket to interface %s (%s)\n",
|
||||
iname, strerror(errno));
|
||||
close(sock);
|
||||
return -1;
|
||||
}
|
||||
@@ -120,8 +131,8 @@ static int create_dhcp_socket(const char *iname)
|
||||
// bind the socket
|
||||
if(bind(sock, (struct sockaddr *)&dhcp_socket, sizeof(dhcp_socket)) < 0)
|
||||
{
|
||||
printf("Error: Could not bind to DHCP socket (interface %s, port %d, %s)\n ---> Check your privileges (run with sudo)!\n",
|
||||
iname, DHCP_CLIENT_PORT, strerror(errno));
|
||||
printf_locked("Error: Could not bind to DHCP socket (interface %s, port %d, %s)\n",
|
||||
iname, DHCP_CLIENT_PORT, strerror(errno));
|
||||
close(sock);
|
||||
return -1;
|
||||
}
|
||||
@@ -130,120 +141,118 @@ 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);
|
||||
|
||||
// try and grab hardware address of requested interface
|
||||
int ret = 0;
|
||||
if((ret = ioctl(sock, SIOCGIFHWADDR, &ifr)) < 0){
|
||||
printf(" Error: Could not get hardware address of interface '%s' (socket %d, error: %s)",
|
||||
iname, sock, strerror(errno));
|
||||
if((ret = ioctl(sock, SIOCGIFHWADDR, &ifr)) < 0)
|
||||
{
|
||||
printf_locked(" Error: Could not get hardware address of interface %s: %s\n", iname, strerror(errno));
|
||||
return false;
|
||||
}
|
||||
memcpy(&mac[0], &ifr.ifr_hwaddr.sa_data, 6);
|
||||
#ifdef DEBUG
|
||||
printf("Hardware address of this interface: ");
|
||||
printf_locked("Hardware address of this interface: ");
|
||||
for (uint8_t i = 0; i < 6; ++i)
|
||||
printf("%02x%s", mac[i], i < 5 ? ":" : "");
|
||||
printf("\n");
|
||||
printf_locked("%02x%s", mac[i], i < 5 ? ":" : "");
|
||||
printf_locked("\n");
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
typedef struct dhcp_packet_struct
|
||||
struct dhcp_packet_data
|
||||
{
|
||||
u_int8_t op; // packet type
|
||||
u_int8_t htype; // type of hardware address for this machine (Ethernet, etc)
|
||||
u_int8_t hlen; // length of hardware address (of this machine)
|
||||
u_int8_t hops; // hops
|
||||
u_int32_t xid; // random transaction id number - chosen by this machine
|
||||
u_int16_t secs; // seconds used in timing
|
||||
u_int16_t flags; // flags
|
||||
struct in_addr ciaddr; // IP address of this machine (if we already have one)
|
||||
struct in_addr yiaddr; // IP address of this machine (offered by the DHCP server)
|
||||
struct in_addr siaddr; // IP address of DHCP server
|
||||
struct in_addr giaddr; // IP address of DHCP relay
|
||||
unsigned char chaddr [MAX_DHCP_CHADDR_LENGTH]; // hardware address of this machine
|
||||
char sname [MAX_DHCP_SNAME_LENGTH]; // name of DHCP server
|
||||
char file [MAX_DHCP_FILE_LENGTH]; // boot file name (used for diskless booting?)
|
||||
char options[MAX_DHCP_OPTIONS_LENGTH]; // options
|
||||
} dhcp_packet_data;
|
||||
|
||||
#define BOOTREQUEST 1
|
||||
#define BOOTREPLY 2
|
||||
u_int8_t op; // packet type
|
||||
u_int8_t htype; // type of hardware address for this machine (Ethernet, etc)
|
||||
u_int8_t hlen; // length of hardware address (of this machine)
|
||||
u_int8_t hops; // hops
|
||||
u_int32_t xid; // random transaction id number - chosen by this machine
|
||||
u_int16_t secs; // seconds used in timing
|
||||
u_int16_t flags; // flags
|
||||
struct in_addr ciaddr; // IP address of this machine (if we already have one)
|
||||
struct in_addr yiaddr; // IP address of this machine (offered by the DHCP server)
|
||||
struct in_addr siaddr; // IP address of DHCP server
|
||||
struct in_addr giaddr; // IP address of DHCP relay
|
||||
unsigned char chaddr [MAX_DHCP_CHADDR_LENGTH]; // hardware address of this machine
|
||||
char sname [MAX_DHCP_SNAME_LENGTH]; // name of DHCP server
|
||||
char file [MAX_DHCP_FILE_LENGTH]; // boot file name (used for diskless booting?)
|
||||
char options[MAX_DHCP_OPTIONS_LENGTH]; // options
|
||||
};
|
||||
|
||||
// sends a DHCPDISCOVER message to the specified in an attempt to find DHCP servers
|
||||
static bool send_dhcp_discover(const int sock, const uint32_t xid, const char *iface, unsigned char *mac, const in_addr_t addr)
|
||||
static bool send_dhcp_discover(const int sock, const uint32_t xid, const char *iface, unsigned char *mac)
|
||||
{
|
||||
dhcp_packet_data discover_packet;
|
||||
struct dhcp_packet_data discover_packet = { 0 };
|
||||
|
||||
// clear the packet data structure
|
||||
memset(&discover_packet, 0, sizeof(discover_packet));
|
||||
// Boot request flag (backward compatible with BOOTP servers)
|
||||
discover_packet.op = 1; // BOOTREQUEST
|
||||
|
||||
// boot request flag (backward compatible with BOOTP servers)
|
||||
discover_packet.op = BOOTREQUEST;
|
||||
// Hardware address type
|
||||
discover_packet.htype = 1; // ETHERNET_HARDWARE_ADDRESS
|
||||
|
||||
// hardware address type
|
||||
discover_packet.htype = 1; // ETHERNET_HARDWARE_ADDRESS;
|
||||
|
||||
// length of our hardware address
|
||||
discover_packet.hlen = 6; // ETHERNET_HARDWARE_ADDRESS_LENGTH;
|
||||
// Length of our hardware address
|
||||
discover_packet.hlen = 6; // ETHERNET_HARDWARE_ADDRESS_LENGTH
|
||||
discover_packet.hops = 0;
|
||||
|
||||
// transaction id is supposed to be random
|
||||
// Transaction id is supposed to be random
|
||||
discover_packet.xid = htonl(xid);
|
||||
//ntohl(discover_packet.xid);
|
||||
discover_packet.secs = 0x00;
|
||||
|
||||
// tell server it should broadcast its response
|
||||
// Tell server it should broadcast its response
|
||||
discover_packet.flags = htons(32768); // DHCP_BROADCAST_FLAG
|
||||
|
||||
// our hardware address
|
||||
// Our hardware address
|
||||
memcpy(discover_packet.chaddr, mac, 6);
|
||||
|
||||
// first four bytes of options field is magic cookie (as per RFC 2132)
|
||||
// First four bytes of options field are the magic cookie (as per RFC 2132)
|
||||
discover_packet.options[0] = '\x63';
|
||||
discover_packet.options[1] = '\x82';
|
||||
discover_packet.options[2] = '\x53';
|
||||
discover_packet.options[3] = '\x63';
|
||||
|
||||
// DHCP message type is embedded in options field
|
||||
discover_packet.options[4] = 53; // DHCP message type option identifier
|
||||
discover_packet.options[5] = '\x01'; // DHCP message option length in bytes
|
||||
discover_packet.options[6] = 1; // DHCP message type code for DHCPDISCOVER
|
||||
discover_packet.options[4] = 53; // DHCP message type option identifier
|
||||
discover_packet.options[5] = 1; // DHCP message option length in bytes
|
||||
discover_packet.options[6] = 1; // DHCP message type code for DHCPDISCOVER
|
||||
|
||||
// Place end option at the end of the options
|
||||
discover_packet.options[7] = 255;
|
||||
|
||||
// send the DHCPDISCOVER packet to the specified address
|
||||
struct sockaddr_in target;
|
||||
// Send the DHCPDISCOVER packet to the specified address
|
||||
struct sockaddr_in target = { 0 };
|
||||
target.sin_family = AF_INET;
|
||||
target.sin_port = htons(DHCP_SERVER_PORT);
|
||||
target.sin_addr.s_addr = addr;
|
||||
memset(&target.sin_zero, 0, sizeof(target.sin_zero));
|
||||
target.sin_addr.s_addr = INADDR_BROADCAST;
|
||||
|
||||
#ifdef DEBUG
|
||||
printf("Sending DHCPDISCOVER on interface %s:%s ...\n", iface, inet_ntoa(target.sin_addr));
|
||||
printf("DHCPDISCOVER XID: %lu (0x%X)\n", (unsigned long) ntohl(discover_packet.xid), ntohl(discover_packet.xid));
|
||||
printf("DHCDISCOVER ciaddr: %s\n", inet_ntoa(discover_packet.ciaddr));
|
||||
printf("DHCDISCOVER yiaddr: %s\n", inet_ntoa(discover_packet.yiaddr));
|
||||
printf("DHCDISCOVER siaddr: %s\n", inet_ntoa(discover_packet.siaddr));
|
||||
printf("DHCDISCOVER giaddr: %s\n", inet_ntoa(discover_packet.giaddr));
|
||||
printf_locked("Sending DHCPDISCOVER on interface %s@%s ... \n", inet_ntoa(target.sin_addr), iface);
|
||||
printf_locked("DHCPDISCOVER XID: %lu (0x%X)\n", (unsigned long) ntohl(discover_packet.xid), ntohl(discover_packet.xid));
|
||||
printf_locked("DHCDISCOVER ciaddr: %s\n", inet_ntoa(discover_packet.ciaddr));
|
||||
printf_locked("DHCDISCOVER yiaddr: %s\n", inet_ntoa(discover_packet.yiaddr));
|
||||
printf_locked("DHCDISCOVER siaddr: %s\n", inet_ntoa(discover_packet.siaddr));
|
||||
printf_locked("DHCDISCOVER giaddr: %s\n", inet_ntoa(discover_packet.giaddr));
|
||||
#endif
|
||||
// send the DHCPDISCOVER packet
|
||||
const int bytes = sendto(sock, (char *)&discover_packet, sizeof(discover_packet), 0, (struct sockaddr *)&target,sizeof(target));
|
||||
if (bytes < 0)
|
||||
const int bytes = sendto(sock, (char *)&discover_packet, sizeof(discover_packet), 0, (struct sockaddr *)&target, sizeof(target));
|
||||
if(bytes < 0)
|
||||
{
|
||||
printf("Error: Could not send DHCPDISCOVER packet on interface %s (error: %s)\n\n", iface, strerror(errno));
|
||||
// strerror() returns "Required key not available" for ENOKEY
|
||||
// which is not helpful at all so we substitute a more
|
||||
// meaningful error message for ENOKEY returned by wireguard interfaces
|
||||
// (see https://www.wireguard.com/papers/wireguard.pdf, page 5)
|
||||
const char *error = errno == ENOKEY ? "No route to host (no such peer available)" : strerror(errno);
|
||||
printf_locked("Error: Could not send DHCPDISCOVER to %s@%s: %s\n",
|
||||
inet_ntoa(target.sin_addr), iface, error);
|
||||
return false;
|
||||
}
|
||||
#ifdef DEBUG
|
||||
printf("Sent %d bytes\n", bytes);
|
||||
#endif
|
||||
|
||||
return bytes > 0;
|
||||
#ifdef DEBUG
|
||||
printf_locked("Sent %d bytes\n", bytes);
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
#ifdef TEST_OPT_249
|
||||
@@ -259,7 +268,7 @@ static void gen_249_test_data(dhcp_packet_data *offer_packet)
|
||||
#endif
|
||||
|
||||
// adds a DHCP OFFER to list in memory
|
||||
static void print_dhcp_offer(struct in_addr source, dhcp_packet_data *offer_packet)
|
||||
static void print_dhcp_offer(struct in_addr source, struct dhcp_packet_data *offer_packet)
|
||||
{
|
||||
if(offer_packet == NULL)
|
||||
return;
|
||||
@@ -329,8 +338,9 @@ static void print_dhcp_offer(struct in_addr source, dhcp_packet_data *offer_pack
|
||||
// We may need to escape this, buffer size: 4
|
||||
// chars per control character plus room for
|
||||
// possible "(empty)"
|
||||
char *buffer = calloc(4*optlen + 9, sizeof(char));
|
||||
binbuf_to_escaped_C_literal(&offer_packet->options[x], optlen, buffer, sizeof(buffer));
|
||||
const size_t bufsiz = 4*optlen + 9;
|
||||
char *buffer = calloc(bufsiz, sizeof(char));
|
||||
binbuf_to_escaped_C_literal(&offer_packet->options[x], optlen, buffer, bufsiz);
|
||||
printf("%s: \"%s\"\n", opttab[i].name, buffer);
|
||||
free(buffer);
|
||||
}
|
||||
@@ -469,15 +479,15 @@ static void print_dhcp_offer(struct in_addr source, dhcp_packet_data *offer_pack
|
||||
if(cidr == 0)
|
||||
{
|
||||
// default route (0.0.0.0/0)
|
||||
printf(" %u: uefault via %u.%u.%u.%u\n", i,
|
||||
router[0], router[1], router[2], router[3]);
|
||||
printf(" %u: default via %u.%u.%u.%u\n\n", i,
|
||||
router[0], router[1], router[2], router[3]);
|
||||
}
|
||||
else
|
||||
{
|
||||
// specific route
|
||||
printf(" %u: %u.%u.%u.%u/%u via %u.%u.%u.%u\n", i,
|
||||
addr[0], addr[1], addr[2], addr[3], cidr,
|
||||
router[0], router[1], router[2], router[3]);
|
||||
addr[0], addr[1], addr[2], addr[3], cidr,
|
||||
router[0], router[1], router[2], router[3]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -523,15 +533,14 @@ static bool receive_dhcp_packet(void *buffer, int buffer_size, const char *iface
|
||||
address_size = sizeof(struct sockaddr_in);
|
||||
recv_result = recvfrom(sock, (char *)buffer, buffer_size, 0, (struct sockaddr *)address, &address_size);
|
||||
|
||||
pthread_mutex_lock(&lock);
|
||||
printf("* Received %d bytes on %s%s%s @ %s\n",
|
||||
recv_result, cli_bold(), iface, cli_normal(), inet_ntoa(address->sin_addr));
|
||||
printf_locked("\n* Received %d bytes from %s @ %s\n", recv_result, inet_ntoa(address->sin_addr), iface);
|
||||
#ifdef DEBUG
|
||||
printf(" after waiting for %f seconds\n", difftime(time(NULL), start_time));
|
||||
printf_locked(" after waiting for %f seconds\n", difftime(time(NULL), start_time));
|
||||
#endif
|
||||
// Return on error
|
||||
if(recv_result == -1){
|
||||
printf(" recvfrom() failed on %s, error: %s\n", iface, strerror(errno));
|
||||
if(recv_result == -1)
|
||||
{
|
||||
printf_locked(" recvfrom() failed on %s, error: %s\n", iface, strerror(errno));
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -539,9 +548,9 @@ static bool receive_dhcp_packet(void *buffer, int buffer_size, const char *iface
|
||||
}
|
||||
|
||||
// waits for a DHCPOFFER message from one or more DHCP servers
|
||||
static bool get_dhcp_offer(const int sock, const uint32_t xid, const char *iface, unsigned char *mac)
|
||||
static void get_dhcp_offer(const int sock, const uint32_t xid, const char *iface, unsigned char *mac)
|
||||
{
|
||||
dhcp_packet_data offer_packet;
|
||||
struct dhcp_packet_data offer_packet;
|
||||
struct sockaddr_in source;
|
||||
unsigned int responses = 0;
|
||||
unsigned int valid_responses = 0;
|
||||
@@ -569,7 +578,7 @@ static bool get_dhcp_offer(const int sock, const uint32_t xid, const char *iface
|
||||
if(ntohl(offer_packet.xid) != xid)
|
||||
{
|
||||
printf(" DHCPOFFER XID (%lu) does not match our DHCPDISCOVER XID (%lu) - ignoring packet (not for us)\n",
|
||||
(unsigned long) ntohl(offer_packet.xid), (unsigned long) xid);
|
||||
(unsigned long) ntohl(offer_packet.xid), (unsigned long) xid);
|
||||
|
||||
pthread_mutex_unlock(&lock);
|
||||
continue;
|
||||
@@ -648,7 +657,11 @@ static bool get_dhcp_offer(const int sock, const uint32_t xid, const char *iface
|
||||
else
|
||||
printf("DHCP packets received on %s%s%s: %u (%u seen for other machines)\n",
|
||||
cli_bold(), iface, cli_normal(), valid_responses, responses);
|
||||
return true;
|
||||
|
||||
#ifdef DEBUG
|
||||
printf(" Responses seen while scanning: %d\n", responses);
|
||||
printf(" Responses meant for this machine: %d\n\n", valid_responses);
|
||||
#endif
|
||||
}
|
||||
|
||||
static void *dhcp_discover_iface(void *args)
|
||||
@@ -664,7 +677,7 @@ static void *dhcp_discover_iface(void *args)
|
||||
|
||||
// Cannot create socket, likely a permission error
|
||||
if(dhcp_socket < 0)
|
||||
pthread_exit(NULL);
|
||||
goto end_dhcp_discover_iface;
|
||||
|
||||
// get hardware address of client machine
|
||||
unsigned char mac[MAX_DHCP_CHADDR_LENGTH] = { 0 };
|
||||
@@ -674,32 +687,31 @@ static void *dhcp_discover_iface(void *args)
|
||||
srand((unsigned int)time(NULL));
|
||||
const uint32_t xid = (uint32_t)random();
|
||||
|
||||
if(strcmp(iface, "lo") == 0)
|
||||
{
|
||||
// Probe a local server listening on this interface
|
||||
// Send DHCPDISCOVER packet to interface address
|
||||
struct sockaddr_in ifaddr = { 0 };
|
||||
memcpy(&ifaddr, ((struct ifaddrs*)args)->ifa_addr, sizeof(ifaddr));
|
||||
send_dhcp_discover(dhcp_socket, xid, iface, mac, ifaddr.sin_addr.s_addr);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Probe distant servers
|
||||
// Send DHCPDISCOVER packet to broadcast address
|
||||
send_dhcp_discover(dhcp_socket, xid, iface, mac, INADDR_BROADCAST);
|
||||
}
|
||||
// Probe servers on this interface
|
||||
if(!send_dhcp_discover(dhcp_socket, xid, iface, mac))
|
||||
goto end_dhcp_discover_iface;
|
||||
|
||||
// wait for a DHCPOFFER packet
|
||||
get_dhcp_offer(dhcp_socket, xid, iface, mac);
|
||||
|
||||
// close socket we created
|
||||
close(dhcp_socket);
|
||||
end_dhcp_discover_iface:
|
||||
// Close socket if we created one
|
||||
if(dhcp_socket > 0)
|
||||
close(dhcp_socket);
|
||||
|
||||
pthread_exit(NULL);
|
||||
}
|
||||
|
||||
int run_dhcp_discover(void)
|
||||
{
|
||||
// Check if we are capable of binding to port 67 (DHCP)
|
||||
// DHCP uses normal UDP datagrams, so we cdon't need CAP_NET_RAW
|
||||
if(!check_capability(CAP_NET_BIND_SERVICE))
|
||||
{
|
||||
puts("Error: Insufficient permissions or capabilities (needs CAP_NET_BIND_SERVICE). Try running as root (sudo)");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
// Disable terminal output during config config file parsing
|
||||
log_ctrl(false, false);
|
||||
// Process pihole-FTL.conf to get gravity.db
|
||||
@@ -708,7 +720,7 @@ int run_dhcp_discover(void)
|
||||
log_ctrl(false, true);
|
||||
|
||||
printf("Scanning all your interfaces for DHCP servers\n");
|
||||
printf("Timeout: %d seconds\n\n", DHCPOFFER_TIMEOUT);
|
||||
printf("Timeout: %d seconds\n", DHCPOFFER_TIMEOUT);
|
||||
|
||||
// Get interface names for available interfaces on this machine
|
||||
// and launch a thread for each one
|
||||
@@ -735,13 +747,27 @@ int run_dhcp_discover(void)
|
||||
int tid = 0;
|
||||
while(tmp != NULL && tid < MAXTHREADS)
|
||||
{
|
||||
// Create a thread for interfaces of type AF_PACKET
|
||||
if(tmp->ifa_addr && tmp->ifa_addr->sa_family == AF_PACKET)
|
||||
// Create a thread for interfaces of type AF_INET (IPv4)
|
||||
if(tmp->ifa_addr && tmp->ifa_addr->sa_family == AF_INET)
|
||||
{
|
||||
// Skip interface scan if ...
|
||||
// - interface is not up
|
||||
// - broadcast is not supported
|
||||
// - interface is loopback net
|
||||
if(!(tmp->ifa_flags & IFF_UP) ||
|
||||
!(tmp->ifa_flags & IFF_BROADCAST) ||
|
||||
tmp->ifa_flags & IFF_LOOPBACK)
|
||||
{
|
||||
tmp = tmp->ifa_next;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Create a probing thread for this interface
|
||||
if(pthread_create(&scanthread[tid], &attr, dhcp_discover_iface, tmp ) != 0)
|
||||
{
|
||||
printf("Unable to launch thread for interface %s, skipping...\n",
|
||||
tmp->ifa_name);
|
||||
printf_locked("Unable to launch thread for interface %s, skipping...",
|
||||
tmp->ifa_name);
|
||||
tmp = tmp->ifa_next;
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -1139,7 +1139,7 @@
|
||||
@test "Embedded SQLite3 shell available and functional" {
|
||||
run bash -c './pihole-FTL sqlite3 -help'
|
||||
printf "%s\n" "${lines[@]}"
|
||||
[[ ${lines[0]} == "Usage: sqlite3 [OPTIONS] FILENAME [SQL]" ]]
|
||||
[[ ${lines[0]} == "Usage: sqlite3 [OPTIONS] [FILENAME [SQL]]" ]]
|
||||
}
|
||||
|
||||
@test "Embedded SQLite3 shell is called for .db file" {
|
||||
|
||||
Reference in New Issue
Block a user