mirror of
https://gitlab.isc.org/isc-projects/kea
synced 2025-09-02 15:05:16 +00:00
[#3605] Prepare existing code for fuzzing
- Separate ENABLE_AFL into ENABLE_FUZZING and HAVE_AFL. - Add the --disable-unicode flag required in the oss-fuzz container. - Add checking of support for C++17. - Make Kea compile with afl++. - Rotate ports in `getServerPort()` functions under an env var. - Fix some destruction issues that would result in crashes when fuzzing. - Add some checks in the UnixControlClient that prevent some crashes when fuzzing. - Add `isc::util::isSocket()` function. - Change `isc::util::file::Path` to not append a trailing slash to allow chained calls of `parentPath()`. - Add `isc::util::file::TemporaryDirectory` useful when fuzzing.
This commit is contained in:
62
configure.ac
62
configure.ac
@@ -204,6 +204,9 @@ AM_CONDITIONAL(USE_CLANGPP, test "X${CLANGPP}" = "Xyes")
|
||||
# Check for C++14 features support
|
||||
AX_ISC_CPP14
|
||||
|
||||
# Check for C++17 features support
|
||||
AX_ISC_CPP17
|
||||
|
||||
# Check for C++20 compiler support.
|
||||
AX_ISC_CPP20
|
||||
|
||||
@@ -346,6 +349,15 @@ if less_than "5" "$CXX_DUMP_VERSION"; then
|
||||
CPPP="$CPPP -P"
|
||||
fi
|
||||
|
||||
# Kea does not support unicode aka wide character strings. Some systems force it
|
||||
# by default in headers. Provide a way to explicitly disable it.
|
||||
AC_ARG_ENABLE(unicode,
|
||||
[AS_HELP_STRING([--disable-unicode], [Explicitly disable unicode])],
|
||||
[case "${enableval}" in
|
||||
yes) AC_MSG_ERROR(["You are trying to explicitly enable unicode. Kea does not support unicode."]) ;;
|
||||
no) KEA_CXXFLAGS="${KEA_CXXFLAGS} -U_UNICODE -UUNICODE" ;;
|
||||
esac])
|
||||
|
||||
case "$host" in
|
||||
*-solaris*)
|
||||
MULTITHREADING_FLAG=-pthreads
|
||||
@@ -676,7 +688,7 @@ int main() {
|
||||
# usable or not.
|
||||
# Let's be optimistic and assume it is by testing only the negative case.
|
||||
if test "${usable_regex}" = 'no'; then
|
||||
AC_MSG_ERROR([Need proper regex functionality.])]
|
||||
AC_MSG_ERROR([Need proper regex functionality.])
|
||||
fi
|
||||
|
||||
# Check for NETCONF support. If NETCONF was enabled in the build, and this check
|
||||
@@ -1405,26 +1417,49 @@ if test "x$VALGRIND" != "xno"; then
|
||||
found_valgrind="found"
|
||||
fi
|
||||
|
||||
AC_MSG_CHECKING([for fuzzing])
|
||||
AC_ARG_ENABLE([fuzzing],
|
||||
[AS_HELP_STRING([--enable-fuzzing],
|
||||
[indicates that the code will be built with AFL (American Fuzzy Lop) support.
|
||||
Code built this way is unusable as a regular server. [default=no]])],
|
||||
[enable_fuzzing=$enableval], [enable_fuzzing=no])
|
||||
AM_CONDITIONAL([ENABLE_AFL], [test x$enable_fuzzing != xno])
|
||||
[AS_HELP_STRING(
|
||||
[--enable-fuzzing[[=mode]]],
|
||||
[indicates that the code will be built for fuzzing purposes.
|
||||
Code built this way is unusable as a regular server.
|
||||
Mode can be ci or standalone. [default=no]])],
|
||||
[if test ! "${CPP17_SUPPORTED}"; then
|
||||
AC_MSG_RESULT("no. Fuzzing requires C++17 support.")
|
||||
AC_MSG_ERROR("Fuzzing requires C++17 support.")
|
||||
fi
|
||||
enable_fuzzing=${enableval}],
|
||||
[enable_fuzzing=no]
|
||||
)
|
||||
AM_CONDITIONAL([FUZZING], [test "${enable_fuzzing}" != 'no'])
|
||||
AM_CONDITIONAL([FUZZING_IN_CI], [test "${enable_fuzzing}" = 'ci'])
|
||||
fuzzing_enabled='no'
|
||||
if test "${enable_fuzzing}" != 'no' ; then
|
||||
fuzzing_enabled='yes'
|
||||
AC_DEFINE([FUZZING], [true], [Fuzzing enabled.])
|
||||
|
||||
if test "x$enable_fuzzing" != "xno" ; then
|
||||
AC_DEFINE([ENABLE_AFL], [1], [AFL fuzzing was enabled.])
|
||||
AC_MSG_CHECKING([for AFL enabled compiler])
|
||||
if test "${enable_fuzzing}" = 'ci'; then
|
||||
fuzzing_enabled='yes, running in CI'
|
||||
AC_DEFINE([FUZZING_IN_CI], [true], [Fuzzing running in CI.])
|
||||
fi
|
||||
fi
|
||||
AC_MSG_RESULT(${fuzzing_enabled})
|
||||
|
||||
# Check for AFL.
|
||||
AC_MSG_CHECKING([for AFL compiler])
|
||||
AC_COMPILE_IFELSE([AC_LANG_PROGRAM([],
|
||||
[#ifndef __AFL_COMPILER
|
||||
#error AFL compiler required
|
||||
#endif
|
||||
])],
|
||||
[AC_MSG_RESULT([yes])],
|
||||
[AC_MSG_ERROR([set CXX to afl-clang-fast++ when --enable-fuzzing is used])])
|
||||
[have_afl='yes'],
|
||||
[have_afl='no'])
|
||||
AC_MSG_RESULT([${have_afl}])
|
||||
AM_CONDITIONAL([HAVE_AFL], [test "${have_afl}" = 'yes'])
|
||||
if test "${have_afl}" = 'yes'; then
|
||||
AC_DEFINE([HAVE_AFL], [true], [AFL compiler enabled.])
|
||||
fi
|
||||
|
||||
|
||||
# Check for optreset in unistd.h. On BSD systems the optreset is
|
||||
# used to reset the state of getopt() function. Resetting its state
|
||||
# is required if command line arguments are parsed multiple times
|
||||
@@ -2148,7 +2183,8 @@ Developer:
|
||||
Generate Messages Files: $enable_generate_messages
|
||||
Perfdhcp: $enable_perfdhcp
|
||||
Kea-shell: $shell_report
|
||||
Enable fuzzing: $enable_fuzzing
|
||||
Fuzzing: $fuzzing_enabled
|
||||
AFL: $have_afl
|
||||
|
||||
END
|
||||
|
||||
|
@@ -4,6 +4,8 @@ CXX_SAVED=$CXX
|
||||
feature=
|
||||
for retry in "none" "--std=c++14" "--std=c++1y" "fail"; do
|
||||
if test "$retry" = "fail"; then
|
||||
AC_MSG_CHECKING([c++14 support])
|
||||
AC_MSG_RESULT([no])
|
||||
AC_MSG_ERROR([$feature (a C++14 feature) is not supported])
|
||||
fi
|
||||
if test "$retry" != "none"; then
|
||||
@@ -250,4 +252,7 @@ for retry in "none" "--std=c++14" "--std=c++1y" "fail"; do
|
||||
break
|
||||
done
|
||||
|
||||
AC_MSG_CHECKING([c++14 support])
|
||||
AC_MSG_RESULT([yes])
|
||||
|
||||
])
|
||||
|
23
m4macros/ax_cpp17.m4
Normal file
23
m4macros/ax_cpp17.m4
Normal file
@@ -0,0 +1,23 @@
|
||||
AC_DEFUN([AX_ISC_CPP17], [
|
||||
AC_MSG_CHECKING([c++17 support])
|
||||
|
||||
# Save flags.
|
||||
CPPFLAGS_SAVED="${CPPFLAGS}"
|
||||
|
||||
# Provide -std=c++17 flag temporarily.
|
||||
CPPFLAGS="${CPPFLAGS} -std=c++17"
|
||||
|
||||
# Check that the filesystem library is supported.
|
||||
AC_LINK_IFELSE(
|
||||
[AC_LANG_PROGRAM(
|
||||
[#include <filesystem>],
|
||||
[std::filesystem::path cwd = std::filesystem::current_path();]
|
||||
)],
|
||||
[AC_MSG_RESULT([yes])
|
||||
CPP17_SUPPORTED=true],
|
||||
[AC_MSG_RESULT([no])
|
||||
CPP17_SUPPORTED=false])
|
||||
|
||||
# Restore flags.
|
||||
CPPFLAGS="${CPPFLAGS_SAVED}"
|
||||
])
|
@@ -1,9 +1,8 @@
|
||||
AC_DEFUN([AX_ISC_CPP20], [
|
||||
AC_MSG_CHECKING(c++20 support)
|
||||
AC_MSG_CHECKING([c++20 support])
|
||||
|
||||
# Save flags.
|
||||
CPPFLAGS_SAVED="${CPPFLAGS}"
|
||||
LIBS_SAVED="${LIBS}"
|
||||
|
||||
# Provide -std=c++20 flag temporarily.
|
||||
CPPFLAGS="${CPPFLAGS} -std=c++20"
|
||||
|
@@ -36,12 +36,12 @@
|
||||
#include <dhcpsrv/cfg_shared_networks.h>
|
||||
#include <dhcpsrv/cfg_subnets4.h>
|
||||
#include <dhcpsrv/dhcpsrv_exceptions.h>
|
||||
#include <dhcpsrv/fuzz.h>
|
||||
#include <dhcpsrv/host_data_source_factory.h>
|
||||
#include <dhcpsrv/host_mgr.h>
|
||||
#include <dhcpsrv/lease_mgr.h>
|
||||
#include <dhcpsrv/lease_mgr_factory.h>
|
||||
#include <dhcpsrv/ncr_generator.h>
|
||||
#include <dhcpsrv/packet-fuzzer.h>
|
||||
#include <dhcpsrv/resource_handler.h>
|
||||
#include <dhcpsrv/shared_network.h>
|
||||
#include <dhcpsrv/subnet.h>
|
||||
@@ -55,6 +55,7 @@
|
||||
#include <stats/stats_mgr.h>
|
||||
#include <util/encode/encode.h>
|
||||
#include <util/str.h>
|
||||
#include <log/interprocess/interprocess_sync_file.h>
|
||||
#include <log/logger.h>
|
||||
#include <cryptolink/cryptolink.h>
|
||||
#include <process/cfgrpt/config_report.h>
|
||||
@@ -65,6 +66,8 @@
|
||||
#include <boost/pointer_cast.hpp>
|
||||
#include <boost/shared_ptr.hpp>
|
||||
|
||||
|
||||
#include <chrono>
|
||||
#include <functional>
|
||||
#include <iomanip>
|
||||
#include <set>
|
||||
@@ -78,9 +81,11 @@ using namespace isc::dhcp;
|
||||
using namespace isc::dhcp_ddns;
|
||||
using namespace isc::hooks;
|
||||
using namespace isc::log;
|
||||
using namespace isc::log::interprocess;
|
||||
using namespace isc::stats;
|
||||
using namespace isc::util;
|
||||
using namespace std;
|
||||
using namespace std::chrono_literals;
|
||||
namespace ph = std::placeholders;
|
||||
|
||||
namespace {
|
||||
@@ -1122,10 +1127,25 @@ Dhcpv4Srv::earlyGHRLookup(const Pkt4Ptr& query,
|
||||
|
||||
int
|
||||
Dhcpv4Srv::run() {
|
||||
#ifdef ENABLE_AFL
|
||||
#ifdef HAVE_AFL
|
||||
// Get the values of the environment variables used to control the
|
||||
// fuzzing.
|
||||
|
||||
// Specfies the interface to be used to pass packets from AFL to Kea.
|
||||
const char* interface = getenv("KEA_AFL_INTERFACE");
|
||||
if (!interface) {
|
||||
isc_throw(FuzzInitFail, "no fuzzing interface has been set");
|
||||
}
|
||||
|
||||
// The address on the interface to be used.
|
||||
const char* address = getenv("KEA_AFL_ADDRESS");
|
||||
if (!address) {
|
||||
isc_throw(FuzzInitFail, "no fuzzing address has been set");
|
||||
}
|
||||
|
||||
// Set up structures needed for fuzzing.
|
||||
Fuzz fuzzer(4, server_port_);
|
||||
//
|
||||
PacketFuzzer fuzzer(4, server_port_, interface, address);
|
||||
|
||||
// The next line is needed as a signature for AFL to recognize that we are
|
||||
// running persistent fuzzing. This has to be in the main image file.
|
||||
while (__AFL_LOOP(fuzzer.maxLoopCount())) {
|
||||
@@ -1134,7 +1154,7 @@ Dhcpv4Srv::run() {
|
||||
fuzzer.transfer();
|
||||
#else
|
||||
while (!shutdown_) {
|
||||
#endif // ENABLE_AFL
|
||||
#endif // HAVE_AFL
|
||||
try {
|
||||
runOne();
|
||||
// Handle events registered by hooks using external IOService objects.
|
||||
@@ -5159,6 +5179,39 @@ void Dhcpv4Srv::discardPackets() {
|
||||
HooksManager::clearParkingLots();
|
||||
}
|
||||
|
||||
uint16_t Dhcpv4Srv::getServerPort() const {
|
||||
char const* const randomize(getenv("KEA_DHCP4_FUZZING_RANDOMIZE_PORT"));
|
||||
if (randomize) {
|
||||
InterprocessSyncFile file("kea-dhcp4-fuzzing-randomize-port");
|
||||
InterprocessSyncLocker locker(file);
|
||||
while (!locker.lock()) {
|
||||
this_thread::sleep_for(1s);
|
||||
}
|
||||
fstream port_file;
|
||||
port_file.open("/tmp/port4.txt", ios::in);
|
||||
string line;
|
||||
int port;
|
||||
getline(port_file, line);
|
||||
port_file.close();
|
||||
if (line.empty()) {
|
||||
port = 2000;
|
||||
} else {
|
||||
port = stoi(line);
|
||||
if (port < 3000) {
|
||||
++port;
|
||||
} else {
|
||||
port = 2000;
|
||||
}
|
||||
}
|
||||
port_file.open("/tmp/port4.txt", ios::out | ios::trunc);
|
||||
port_file << to_string(port) << endl;
|
||||
port_file.close();
|
||||
locker.unlock();
|
||||
return port;
|
||||
}
|
||||
return server_port_;
|
||||
}
|
||||
|
||||
std::list<std::list<std::string>> Dhcpv4Srv::jsonPathsToRedact() const {
|
||||
static std::list<std::list<std::string>> const list({
|
||||
{"config-control", "config-databases", "[]"},
|
||||
|
@@ -454,9 +454,7 @@ public:
|
||||
/// for testing purposes only.
|
||||
///
|
||||
/// @return UDP port on which server should listen.
|
||||
uint16_t getServerPort() const {
|
||||
return (server_port_);
|
||||
}
|
||||
uint16_t getServerPort() const;
|
||||
|
||||
/// @brief Return bool value indicating that broadcast flags should be set
|
||||
/// on sockets.
|
||||
|
@@ -15,7 +15,6 @@
|
||||
#include <dhcp/docsis3_option_defs.h>
|
||||
#include <dhcp/duid.h>
|
||||
#include <dhcp/duid_factory.h>
|
||||
#include <dhcpsrv/fuzz.h>
|
||||
#include <dhcp/iface_mgr.h>
|
||||
#include <dhcp/libdhcp++.h>
|
||||
#include <dhcp/option6_addrlst.h>
|
||||
@@ -41,6 +40,7 @@
|
||||
#include <dhcpsrv/lease_mgr.h>
|
||||
#include <dhcpsrv/lease_mgr_factory.h>
|
||||
#include <dhcpsrv/ncr_generator.h>
|
||||
#include <dhcpsrv/packet-fuzzer.h>
|
||||
#include <dhcpsrv/subnet.h>
|
||||
#include <dhcpsrv/subnet_selector.h>
|
||||
#include <dhcpsrv/utils.h>
|
||||
@@ -54,6 +54,7 @@
|
||||
#include <util/encode/encode.h>
|
||||
#include <util/pointer_util.h>
|
||||
#include <util/range_utilities.h>
|
||||
#include <log/interprocess/interprocess_sync_file.h>
|
||||
#include <log/logger.h>
|
||||
#include <cryptolink/cryptolink.h>
|
||||
#include <process/cfgrpt/config_report.h>
|
||||
@@ -82,6 +83,7 @@ using namespace isc::dhcp;
|
||||
using namespace isc::dhcp_ddns;
|
||||
using namespace isc::hooks;
|
||||
using namespace isc::log;
|
||||
using namespace isc::log::interprocess;
|
||||
using namespace isc::stats;
|
||||
using namespace isc::util;
|
||||
using namespace std;
|
||||
@@ -597,10 +599,25 @@ Dhcpv6Srv::initContext(AllocEngine::ClientContext6& ctx, bool& drop) {
|
||||
|
||||
int
|
||||
Dhcpv6Srv::run() {
|
||||
#ifdef ENABLE_AFL
|
||||
#ifdef HAVE_AFL
|
||||
// Get the values of the environment variables used to control the
|
||||
// fuzzing.
|
||||
|
||||
// Specfies the interface to be used to pass packets from AFL to Kea.
|
||||
const char* interface = getenv("KEA_AFL_INTERFACE");
|
||||
if (!interface) {
|
||||
isc_throw(FuzzInitFail, "no fuzzing interface has been set");
|
||||
}
|
||||
|
||||
// The address on the interface to be used.
|
||||
const char* address = getenv("KEA_AFL_ADDRESS");
|
||||
if (!address) {
|
||||
isc_throw(FuzzInitFail, "no fuzzing address has been set");
|
||||
}
|
||||
|
||||
// Set up structures needed for fuzzing.
|
||||
Fuzz fuzzer(6, server_port_);
|
||||
//
|
||||
PacketFuzzer fuzzer(6, server_port_, interface, address);
|
||||
|
||||
// The next line is needed as a signature for AFL to recognize that we are
|
||||
// running persistent fuzzing. This has to be in the main image file.
|
||||
while (__AFL_LOOP(fuzzer.maxLoopCount())) {
|
||||
@@ -609,7 +626,7 @@ Dhcpv6Srv::run() {
|
||||
fuzzer.transfer();
|
||||
#else
|
||||
while (!shutdown_) {
|
||||
#endif // ENABLE_AFL
|
||||
#endif // HAVE_AFL
|
||||
try {
|
||||
runOne();
|
||||
// Handle events registered by hooks using external IOService objects.
|
||||
@@ -4902,6 +4919,39 @@ void Dhcpv6Srv::discardPackets() {
|
||||
HooksManager::clearParkingLots();
|
||||
}
|
||||
|
||||
uint16_t Dhcpv6Srv::getServerPort() const {
|
||||
char const* const randomize(getenv("KEA_DHCP6_FUZZING_RANDOMIZE_PORT"));
|
||||
if (randomize) {
|
||||
InterprocessSyncFile file("kea-dhcp6-fuzzing-randomize-port");
|
||||
InterprocessSyncLocker locker(file);
|
||||
while (!locker.lock()) {
|
||||
this_thread::sleep_for(1s);
|
||||
}
|
||||
fstream port_file;
|
||||
port_file.open("/tmp/port6.txt", ios::in);
|
||||
string line;
|
||||
int port;
|
||||
getline(port_file, line);
|
||||
port_file.close();
|
||||
if (line.empty()) {
|
||||
port = 2000;
|
||||
} else {
|
||||
port = stoi(line);
|
||||
if (port < 3000) {
|
||||
++port;
|
||||
} else {
|
||||
port = 2000;
|
||||
}
|
||||
}
|
||||
port_file.open("/tmp/port6.txt", ios::out | ios::trunc);
|
||||
port_file << to_string(port) << endl;
|
||||
port_file.close();
|
||||
locker.unlock();
|
||||
return port;
|
||||
}
|
||||
return server_port_;
|
||||
}
|
||||
|
||||
/// @todo This logic to be modified if we decide to support infinite lease times.
|
||||
void
|
||||
Dhcpv6Srv::setTeeTimes(uint32_t preferred_lft, const Subnet6Ptr& subnet, Option6IAPtr& resp) {
|
||||
|
@@ -240,9 +240,7 @@ public:
|
||||
/// for testing purposes only.
|
||||
///
|
||||
/// @return UDP port on which server should listen.
|
||||
uint16_t getServerPort() const {
|
||||
return (server_port_);
|
||||
}
|
||||
uint16_t getServerPort() const;
|
||||
//@}
|
||||
|
||||
/// @brief Starts DHCP_DDNS client IO if DDNS updates are enabled.
|
||||
|
@@ -172,11 +172,11 @@ libkea_dhcpsrv_la_SOURCES += parsers/simple_parser4.h
|
||||
libkea_dhcpsrv_la_SOURCES += parsers/simple_parser6.cc
|
||||
libkea_dhcpsrv_la_SOURCES += parsers/simple_parser6.h
|
||||
|
||||
if ENABLE_AFL
|
||||
libkea_dhcpsrv_la_SOURCES += fuzz.cc fuzz.h
|
||||
if FUZZING
|
||||
libkea_dhcpsrv_la_SOURCES += packet-fuzzer.cc packet-fuzzer.h
|
||||
libkea_dhcpsrv_la_SOURCES += fuzz_log.cc fuzz_log.h
|
||||
libkea_dhcpsrv_la_SOURCES += fuzz_messages.cc fuzz_messages.h
|
||||
endif
|
||||
endif # FUZZING
|
||||
|
||||
libkea_dhcpsrv_la_CXXFLAGS = $(AM_CXXFLAGS)
|
||||
libkea_dhcpsrv_la_CPPFLAGS = $(AM_CPPFLAGS)
|
||||
@@ -350,9 +350,9 @@ libkea_dhcpsrv_include_HEADERS = \
|
||||
utils.h \
|
||||
writable_host_data_source.h
|
||||
|
||||
if ENABLE_AFL
|
||||
if FUZZING
|
||||
libkea_dhcpsrv_include_HEADERS += \
|
||||
fuzz.h \
|
||||
packet-fuzzer.h \
|
||||
fuzz_log.h \
|
||||
fuzz_messages.h
|
||||
endif
|
||||
|
@@ -1,4 +1,4 @@
|
||||
// Copyright (C) 2022-2023 Internet Systems Consortium, Inc. ("ISC")
|
||||
// Copyright (C) 2022-2024 Internet Systems Consortium, Inc. ("ISC")
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
@@ -31,8 +31,7 @@ Allocator::~Allocator() {
|
||||
return;
|
||||
}
|
||||
// Remove the callbacks.
|
||||
auto& lease_mgr = LeaseMgrFactory::instance();
|
||||
lease_mgr.unregisterCallbacks(subnet_id_, pool_type_);
|
||||
LeaseMgrFactory::instance().unregisterCallbacks(subnet_id_, pool_type_);
|
||||
}
|
||||
|
||||
bool
|
||||
|
@@ -92,6 +92,7 @@ LeaseMgrFactory::destroy() {
|
||||
if (getLeaseMgrPtr()) {
|
||||
LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE, DHCPSRV_CLOSE_DB)
|
||||
.arg(getLeaseMgrPtr()->getType());
|
||||
getLeaseMgrPtr().reset();
|
||||
}
|
||||
getLeaseMgrPtr().reset();
|
||||
}
|
||||
|
@@ -42,6 +42,10 @@ public:
|
||||
/// user-supplied backends (so that there is no need to modify the code).
|
||||
class LeaseMgrFactory {
|
||||
public:
|
||||
~LeaseMgrFactory() {
|
||||
destroy();
|
||||
}
|
||||
|
||||
/// @brief Create an instance of a lease manager.
|
||||
///
|
||||
/// Each database backend has its own lease manager type. This static
|
||||
|
@@ -1,4 +1,4 @@
|
||||
// Copyright (C) 2016-2019 Internet Systems Consortium, Inc. ("ISC")
|
||||
// Copyright (C) 2016-2024 Internet Systems Consortium, Inc. ("ISC")
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
@@ -6,14 +6,10 @@
|
||||
|
||||
#include <config.h>
|
||||
|
||||
#ifdef ENABLE_AFL
|
||||
|
||||
#ifndef __AFL_LOOP
|
||||
#error To use American Fuzzy Lop you have to set CXX to afl-clang-fast++
|
||||
#endif
|
||||
#ifdef FUZZING
|
||||
|
||||
#include <dhcp/dhcp6.h>
|
||||
#include <dhcpsrv/fuzz.h>
|
||||
#include <dhcpsrv/packet-fuzzer.h>
|
||||
#include <dhcpsrv/fuzz_log.h>
|
||||
|
||||
#include <boost/lexical_cast.hpp>
|
||||
@@ -23,6 +19,7 @@
|
||||
#include <string.h>
|
||||
#include <signal.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <fstream>
|
||||
@@ -32,39 +29,29 @@ using namespace isc;
|
||||
using namespace isc::dhcp;
|
||||
using namespace std;
|
||||
|
||||
// Constants defined in the Fuzz class definition.
|
||||
constexpr size_t Fuzz::BUFFER_SIZE;
|
||||
constexpr size_t Fuzz::MAX_SEND_SIZE;
|
||||
constexpr long Fuzz::MAX_LOOP_COUNT;
|
||||
// Constants defined in the PacketFuzzer class definition.
|
||||
constexpr size_t PacketFuzzer::BUFFER_SIZE;
|
||||
constexpr size_t PacketFuzzer::MAX_SEND_SIZE;
|
||||
constexpr long PacketFuzzer::MAX_LOOP_COUNT;
|
||||
|
||||
// Constructor
|
||||
Fuzz::Fuzz(int ipversion, uint16_t port) :
|
||||
loop_max_(MAX_LOOP_COUNT), sockaddr_len_(0), sockaddr_ptr_(nullptr),
|
||||
sockfd_(-1) {
|
||||
PacketFuzzer::PacketFuzzer(int const ipversion,
|
||||
uint16_t const port,
|
||||
string const interface,
|
||||
string const address)
|
||||
: loop_max_(MAX_LOOP_COUNT), sockaddr_len_(0), sockaddr_ptr_(nullptr), sockfd_(-1) {
|
||||
|
||||
try {
|
||||
stringstream reason; // Used to construct exception messages
|
||||
|
||||
// Get the values of the environment variables used to control the
|
||||
// fuzzing.
|
||||
|
||||
// Specfies the interface to be used to pass packets from AFL to Kea.
|
||||
const char* interface = getenv("KEA_AFL_INTERFACE");
|
||||
if (! interface) {
|
||||
isc_throw(FuzzInitFail, "no fuzzing interface has been set");
|
||||
}
|
||||
|
||||
// The address on the interface to be used.
|
||||
const char* address = getenv("KEA_AFL_ADDRESS");
|
||||
if (address == 0) {
|
||||
isc_throw(FuzzInitFail, "no fuzzing address has been set");
|
||||
}
|
||||
|
||||
// Number of Kea packet-read loops before Kea exits and AFL starts a
|
||||
// new instance. This is optional: the default is set by the constant
|
||||
// MAX_LOOP_COUNT.
|
||||
const char *loop_max_ptr = getenv("KEA_AFL_LOOP_MAX");
|
||||
if (loop_max_ptr != 0) {
|
||||
const char *loop_max_ptr(nullptr);
|
||||
#ifdef HAVE_AFL
|
||||
loop_max_ptr = getenv("KEA_AFL_LOOP_MAX");
|
||||
#endif
|
||||
if (loop_max_ptr) {
|
||||
try {
|
||||
loop_max_ = boost::lexical_cast<long>(loop_max_ptr);
|
||||
} catch (const boost::bad_lexical_cast&) {
|
||||
@@ -81,7 +68,7 @@ Fuzz::Fuzz(int ipversion, uint16_t port) :
|
||||
}
|
||||
|
||||
// Set up address structures used to route the packets from AFL to Kea.
|
||||
createAddressStructures(ipversion, interface, address, port);
|
||||
createAddressStructures(ipversion, port, interface, address);
|
||||
|
||||
// Create the socket through which packets read from stdin will be sent
|
||||
// to the port on which Kea is listening. This is closed in the
|
||||
@@ -105,24 +92,26 @@ Fuzz::Fuzz(int ipversion, uint16_t port) :
|
||||
}
|
||||
|
||||
// Destructor
|
||||
Fuzz::~Fuzz() {
|
||||
PacketFuzzer::~PacketFuzzer() {
|
||||
static_cast<void>(close(sockfd_));
|
||||
}
|
||||
|
||||
// Set up address structures.
|
||||
void
|
||||
Fuzz::createAddressStructures(int ipversion, const char* interface,
|
||||
const char* address, uint16_t port) {
|
||||
PacketFuzzer::createAddressStructures(int const ipversion,
|
||||
uint16_t const port,
|
||||
string const interface,
|
||||
string const address) {
|
||||
stringstream reason; // Used in error messages
|
||||
|
||||
// Set up the appropriate data structure depending on the address given.
|
||||
if ((ipversion == 6) && (strstr(address, ":") != NULL)) {
|
||||
if (ipversion == 6 && address.find(":") != string::npos) {
|
||||
// Expecting IPv6 and the address contains a colon, so assume it is an
|
||||
// an IPv6 address.
|
||||
memset(&servaddr6_, 0, sizeof (servaddr6_));
|
||||
|
||||
servaddr6_.sin6_family = AF_INET6;
|
||||
if (inet_pton(AF_INET6, address, &servaddr6_.sin6_addr) != 1) {
|
||||
if (inet_pton(AF_INET6, address.c_str(), &servaddr6_.sin6_addr) != 1) {
|
||||
reason << "inet_pton() failed: can't convert "
|
||||
<< address << " to an IPv6 address" << endl;
|
||||
isc_throw(FuzzInitFail, reason.str());
|
||||
@@ -130,7 +119,7 @@ Fuzz::createAddressStructures(int ipversion, const char* interface,
|
||||
servaddr6_.sin6_port = htons(port);
|
||||
|
||||
// Interface ID is needed for IPv6 address structures.
|
||||
servaddr6_.sin6_scope_id = if_nametoindex(interface);
|
||||
servaddr6_.sin6_scope_id = if_nametoindex(interface.c_str());
|
||||
if (servaddr6_.sin6_scope_id == 0) {
|
||||
reason << "error retrieving interface ID for "
|
||||
<< interface << ": " << strerror(errno);
|
||||
@@ -140,14 +129,14 @@ Fuzz::createAddressStructures(int ipversion, const char* interface,
|
||||
sockaddr_ptr_ = reinterpret_cast<sockaddr*>(&servaddr6_);
|
||||
sockaddr_len_ = sizeof(servaddr6_);
|
||||
|
||||
} else if ((ipversion == 4) && (strstr(address, ".") != NULL)) {
|
||||
} else if (ipversion == 4 && address.find(".") != string::npos) {
|
||||
// Expecting an IPv4 address and it contains a dot, so assume it is.
|
||||
// This check is done after the IPv6 check, as it is possible for an
|
||||
// IPv4 address to be embedded in an IPv6 one.
|
||||
memset(&servaddr4_, 0, sizeof(servaddr4_));
|
||||
|
||||
servaddr4_.sin_family = AF_INET;
|
||||
if (inet_pton(AF_INET, address, &servaddr4_.sin_addr) != 1) {
|
||||
if (inet_pton(AF_INET, address.c_str(), &servaddr4_.sin_addr) != 1) {
|
||||
reason << "inet_pton() failed: can't convert "
|
||||
<< address << " to an IPv6 address" << endl;
|
||||
isc_throw(FuzzInitFail, reason.str());
|
||||
@@ -166,16 +155,26 @@ Fuzz::createAddressStructures(int ipversion, const char* interface,
|
||||
|
||||
}
|
||||
|
||||
void
|
||||
PacketFuzzer::transfer() const {
|
||||
// Read from stdin. Just return if nothing is read (or there is an error)
|
||||
// and hope that this does not cause a hang.
|
||||
uint8_t buf[BUFFER_SIZE];
|
||||
ssize_t const length(read(0, buf, sizeof(buf)));
|
||||
|
||||
transfer(&buf[0], length);
|
||||
}
|
||||
|
||||
// This is the main fuzzing function. It receives data from fuzzing engine over
|
||||
// stdin and then sends it to the configured UDP socket.
|
||||
void
|
||||
Fuzz::transfer(void) const {
|
||||
|
||||
// Read from stdin. Just return if nothing is read (or there is an error)
|
||||
// and hope that this does not cause a hang.
|
||||
PacketFuzzer::transfer(uint8_t const* data, size_t size) const {
|
||||
char buf[BUFFER_SIZE];
|
||||
ssize_t length = read(0, buf, sizeof(buf));
|
||||
ssize_t const length(size);
|
||||
|
||||
if (data) {
|
||||
memcpy(&buf[0], data, min(BUFFER_SIZE, size));
|
||||
}
|
||||
|
||||
// Save the errno in case there was an error because if debugging is
|
||||
// enabled, the following LOG_DEBUG call may destroy its value.
|
||||
@@ -189,12 +188,12 @@ Fuzz::transfer(void) const {
|
||||
size_t send_len = (length < MAX_SEND_SIZE) ? length : MAX_SEND_SIZE;
|
||||
ssize_t sent = sendto(sockfd_, buf, send_len, 0, sockaddr_ptr_,
|
||||
sockaddr_len_);
|
||||
if (sent > 0) {
|
||||
LOG_DEBUG(fuzz_logger, FUZZ_DBG_TRACE_DETAIL, FUZZ_SEND).arg(sent);
|
||||
if (sent < 0) {
|
||||
LOG_ERROR(fuzz_logger, FUZZ_SEND_ERROR).arg(strerror(errno));
|
||||
} else if (sent != length) {
|
||||
LOG_WARN(fuzz_logger, FUZZ_SHORT_SEND).arg(length).arg(sent);
|
||||
} else {
|
||||
LOG_ERROR(fuzz_logger, FUZZ_SEND_ERROR).arg(strerror(errno));
|
||||
LOG_DEBUG(fuzz_logger, FUZZ_DBG_TRACE_DETAIL, FUZZ_SEND).arg(sent);
|
||||
}
|
||||
} else {
|
||||
// Read did not get any bytes. A zero-length read (EOF) may have been
|
||||
@@ -203,7 +202,6 @@ Fuzz::transfer(void) const {
|
||||
LOG_ERROR(fuzz_logger, FUZZ_READ_FAIL).arg(strerror(errnum));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif // ENABLE_AFL
|
||||
#endif // FUZZING
|
@@ -1,13 +1,13 @@
|
||||
// Copyright (C) 2016-2019 Internet Systems Consortium, Inc. ("ISC")
|
||||
// Copyright (C) 2016-2024 Internet Systems Consortium, Inc. ("ISC")
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
#ifndef FUZZ_H
|
||||
#define FUZZ_H
|
||||
#ifndef DHCPSRV_PACKET_FUZZER_H
|
||||
#define DHCPSRV_PACKET_FUZZER_H
|
||||
|
||||
#ifdef ENABLE_AFL
|
||||
#ifdef FUZZING
|
||||
|
||||
#include <exceptions/exceptions.h>
|
||||
|
||||
@@ -23,7 +23,6 @@
|
||||
|
||||
namespace isc {
|
||||
|
||||
|
||||
/// @brief AFL Fuzzing
|
||||
///
|
||||
/// Persistent-mode AFL fuzzing has the AFL fuzzer send packets of data to
|
||||
@@ -35,12 +34,12 @@ namespace isc {
|
||||
/// is listening. Kea then reads the data from that port and processes it
|
||||
/// in the usual way.
|
||||
///
|
||||
/// The Fuzz class handles the transfer of data between AFL and Kea. After
|
||||
/// The PacketFuzzer class handles the transfer of data between AFL and Kea. After
|
||||
/// suitable initialization, its transfer() method is called in the main
|
||||
/// processing loop, right before Kea waits for input. The method handles the
|
||||
/// read from stdin and the write to the selected address port.
|
||||
|
||||
class Fuzz {
|
||||
class PacketFuzzer {
|
||||
public:
|
||||
/// @brief size of the buffer used to transfer data between AFL and Kea.
|
||||
///
|
||||
@@ -65,7 +64,6 @@ public:
|
||||
/// environment variable KEA_AFL_LOOP_MAX.
|
||||
static constexpr long MAX_LOOP_COUNT = 1000;
|
||||
|
||||
|
||||
/// @brief Constructor
|
||||
///
|
||||
/// Sets up data structures to access the address/port being used to
|
||||
@@ -75,20 +73,24 @@ public:
|
||||
/// server responds to.
|
||||
/// @param port Port on which the server is listening, and hence the
|
||||
/// port to which the fuzzer will send input from AFL.
|
||||
Fuzz(int ipversion, uint16_t port);
|
||||
PacketFuzzer(int const ipversion,
|
||||
uint16_t const port,
|
||||
std::string const interface,
|
||||
std::string const address);
|
||||
|
||||
/// @brief Destructor
|
||||
///
|
||||
/// Closes the socket used for transferring data from stdin to the selected
|
||||
/// interface.
|
||||
~Fuzz();
|
||||
~PacketFuzzer();
|
||||
|
||||
/// @brief Transfer Data
|
||||
///
|
||||
/// Called immediately prior to Kea reading data, this reads stdin (where
|
||||
/// AFL will have sent the packet being tested) and copies the data to the
|
||||
/// interface on which Kea is listening.
|
||||
void transfer(void) const;
|
||||
void transfer() const;
|
||||
void transfer(uint8_t const* data, size_t size) const;
|
||||
|
||||
/// @brief Return Max Loop Count
|
||||
///
|
||||
@@ -117,8 +119,10 @@ private:
|
||||
///
|
||||
/// @throws FuzzInitFail Thrown if the address is not in the expected
|
||||
/// format.
|
||||
void createAddressStructures(int ipversion, const char* interface,
|
||||
const char* address, uint16_t port);
|
||||
void createAddressStructures(int const ipversion,
|
||||
uint16_t const port,
|
||||
std::string const interface,
|
||||
std::string const address);
|
||||
|
||||
// Other member variables.
|
||||
long loop_max_; //< Maximum number of loop iterations
|
||||
@@ -127,18 +131,17 @@ private:
|
||||
struct sockaddr_in servaddr4_; //< IPv6 address information
|
||||
struct sockaddr_in6 servaddr6_; //< IPv6 address information
|
||||
int sockfd_; //< Socket used to transfer data
|
||||
};
|
||||
|
||||
}; // class PacketFuzzer
|
||||
|
||||
/// @brief Exception thrown if fuzzing initialization fails.
|
||||
class FuzzInitFail : public Exception {
|
||||
public:
|
||||
FuzzInitFail(const char* file, size_t line, const char* what) :
|
||||
isc::Exception(file, line, what) { };
|
||||
};
|
||||
}; // class FuzzInitFail
|
||||
|
||||
}
|
||||
} // namespace isc
|
||||
|
||||
#endif // ENABLE_AFL
|
||||
#endif // FUZZING
|
||||
|
||||
#endif // FUZZ_H
|
||||
#endif // DHCPSRV_PACKET_FUZZER_H
|
@@ -17,8 +17,11 @@ libkea_testutils_la_SOURCES += user_context_utils.cc user_context_utils.h
|
||||
libkea_testutils_la_SOURCES += gtest_utils.h
|
||||
libkea_testutils_la_SOURCES += multi_threading_utils.h
|
||||
libkea_testutils_la_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
|
||||
libkea_testutils_la_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
|
||||
|
||||
libkea_testutils_la_LIBADD = $(top_builddir)/src/lib/asiolink/libkea-asiolink.la
|
||||
libkea_testutils_la_LIBADD += $(top_builddir)/src/lib/dns/libkea-dns++.la
|
||||
libkea_testutils_la_LIBADD += $(GTEST_LDADD)
|
||||
endif
|
||||
|
||||
# Include common libraries being used by shell-based tests.
|
||||
|
@@ -1,4 +1,4 @@
|
||||
// Copyright (C) 2015-2019 Internet Systems Consortium, Inc. ("ISC")
|
||||
// Copyright (C) 2015-2024 Internet Systems Consortium, Inc. ("ISC")
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
@@ -73,6 +73,10 @@ bool UnixControlClient::connectToServer(const std::string& socket_path) {
|
||||
}
|
||||
|
||||
bool UnixControlClient::sendCommand(const std::string& command) {
|
||||
if (socket_fd_ < 0) {
|
||||
ADD_FAILURE() << "send command with closed socket";
|
||||
return (false);
|
||||
}
|
||||
// Send command
|
||||
int bytes_sent = send(socket_fd_, command.c_str(), command.length(), 0);
|
||||
if (bytes_sent < command.length()) {
|
||||
@@ -118,6 +122,14 @@ bool UnixControlClient::getResponse(std::string& response,
|
||||
}
|
||||
|
||||
int UnixControlClient::selectCheck(const unsigned int timeout_sec) {
|
||||
if (socket_fd_ < 0) {
|
||||
ADD_FAILURE() << "select check with closed socket";
|
||||
return -1;
|
||||
}
|
||||
if (socket_fd_ > 1023) {
|
||||
ADD_FAILURE() << "select check with out of bound socket";
|
||||
return -1;
|
||||
}
|
||||
int maxfd = 0;
|
||||
|
||||
fd_set read_fds;
|
||||
@@ -134,6 +146,6 @@ int UnixControlClient::selectCheck(const unsigned int timeout_sec) {
|
||||
return (select(maxfd + 1, &read_fds, NULL, NULL, &select_timeout));
|
||||
}
|
||||
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,4 +1,4 @@
|
||||
// Copyright (C) 2015-2017 Internet Systems Consortium, Inc. ("ISC")
|
||||
// Copyright (C) 2015-2024 Internet Systems Consortium, Inc. ("ISC")
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
@@ -59,8 +59,8 @@ public:
|
||||
int socket_fd_;
|
||||
};
|
||||
|
||||
}; // end of isc::dhcp::test namespace
|
||||
}; // end of isc::dhcp namespace
|
||||
}; // end of isc namespace
|
||||
} // end of isc::dhcp::test namespace
|
||||
} // end of isc::dhcp namespace
|
||||
} // end of isc namespace
|
||||
|
||||
#endif // UNIX_CONTROL_CLIENT_H
|
||||
|
@@ -10,14 +10,12 @@
|
||||
#include <util/filesystem.h>
|
||||
#include <util/str.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <cerrno>
|
||||
#include <cstring>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
#include <dirent.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
using namespace isc::util::str;
|
||||
@@ -76,6 +74,15 @@ Umask::~Umask() {
|
||||
umask(orig_umask_);
|
||||
}
|
||||
|
||||
bool
|
||||
isSocket(string const& path) {
|
||||
struct stat statbuf;
|
||||
if (::stat(path.c_str(), &statbuf) < 0) {
|
||||
return (false);
|
||||
}
|
||||
return ((statbuf.st_mode & S_IFMT) == S_IFSOCK);
|
||||
}
|
||||
|
||||
Path::Path(string const& full_name) {
|
||||
if (!full_name.empty()) {
|
||||
bool dir_present = false;
|
||||
@@ -84,9 +91,9 @@ Path::Path(string const& full_name) {
|
||||
if (last_slash != string::npos) {
|
||||
// Found the last slash, so extract directory component and
|
||||
// set where the scan for the last_dot should terminate.
|
||||
parent_path_ = full_name.substr(0, last_slash + 1);
|
||||
parent_path_ = full_name.substr(0, last_slash);
|
||||
if (last_slash == full_name.size()) {
|
||||
// The entire string was a directory, so exit not and don't
|
||||
// The entire string was a directory, so exit and don't
|
||||
// do any more searching.
|
||||
return;
|
||||
}
|
||||
@@ -119,7 +126,7 @@ Path::Path(string const& full_name) {
|
||||
|
||||
string
|
||||
Path::str() const {
|
||||
return (parent_path_ + stem_ + extension_);
|
||||
return (parent_path_ + ((parent_path_.empty() || parent_path_ == "/") ? string() : "/") + stem_ + extension_);
|
||||
}
|
||||
|
||||
string
|
||||
@@ -163,14 +170,47 @@ Path::replaceParentPath(string const& replacement) {
|
||||
string const trimmed_replacement(trim(replacement));
|
||||
if (trimmed_replacement.empty()) {
|
||||
parent_path_ = string();
|
||||
} else if (trimmed_replacement.at(trimmed_replacement.size() - 1) == '/') {
|
||||
} else if (trimmed_replacement == "/") {
|
||||
parent_path_ = trimmed_replacement;
|
||||
} else if (trimmed_replacement.at(trimmed_replacement.size() - 1) == '/') {
|
||||
parent_path_ = trimmed_replacement.substr(0, trimmed_replacement.size() - 1);
|
||||
} else {
|
||||
parent_path_ = trimmed_replacement + '/';
|
||||
parent_path_ = trimmed_replacement;
|
||||
}
|
||||
return (*this);
|
||||
}
|
||||
|
||||
TemporaryDirectory::TemporaryDirectory() {
|
||||
char dir[]("/tmp/kea-tmpdir-XXXXXX");
|
||||
char const* dir_name = mkdtemp(dir);
|
||||
if(!dir_name) {
|
||||
isc_throw(Unexpected, "mkdtemp failed " << dir << ": " << strerror(errno));
|
||||
}
|
||||
dir_name_ = string(dir_name);
|
||||
}
|
||||
|
||||
TemporaryDirectory::~TemporaryDirectory() {
|
||||
rmdir(dir_name_.c_str());
|
||||
DIR *dir(opendir(dir_name_.c_str()));
|
||||
struct dirent *i;
|
||||
string filepath;
|
||||
|
||||
while ((i = readdir(dir))) {
|
||||
if (strcmp(i->d_name, ".") == 0 || strcmp(i->d_name, "..") == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
filepath = dir_name_ + '/' + i->d_name;
|
||||
remove(filepath.c_str());
|
||||
}
|
||||
closedir(dir);
|
||||
rmdir(dir_name_.c_str());
|
||||
}
|
||||
|
||||
string TemporaryDirectory::dirName() {
|
||||
return dir_name_;
|
||||
}
|
||||
|
||||
} // namespace file
|
||||
} // namespace util
|
||||
} // namespace isc
|
||||
|
@@ -14,7 +14,7 @@ namespace isc {
|
||||
namespace util {
|
||||
namespace file {
|
||||
|
||||
/// \brief Get the content of a regular file.
|
||||
/// @brief Get the content of a regular file.
|
||||
///
|
||||
/// \param file_name The file name.
|
||||
///
|
||||
@@ -23,7 +23,7 @@ namespace file {
|
||||
std::string
|
||||
getContent(const std::string& file_name);
|
||||
|
||||
/// \brief Check if there is a file or directory at the given path.
|
||||
/// @brief Check if there is a file or directory at the given path.
|
||||
///
|
||||
/// \param path The path being checked.
|
||||
///
|
||||
@@ -31,7 +31,7 @@ getContent(const std::string& file_name);
|
||||
bool
|
||||
exists(const std::string& path);
|
||||
|
||||
/// \brief Check if there is a directory at the given path.
|
||||
/// @brief Check if there is a directory at the given path.
|
||||
///
|
||||
/// \param path The path being checked.
|
||||
///
|
||||
@@ -40,7 +40,7 @@ exists(const std::string& path);
|
||||
bool
|
||||
isDir(const std::string& path);
|
||||
|
||||
/// \brief Check if there is a file at the given path.
|
||||
/// @brief Check if there is a file at the given path.
|
||||
///
|
||||
/// \param path The path being checked.
|
||||
///
|
||||
@@ -49,66 +49,69 @@ isDir(const std::string& path);
|
||||
bool
|
||||
isFile(const std::string& path);
|
||||
|
||||
/// \brief RAII device to limit access of created files.
|
||||
/// @brief RAII device to limit access of created files.
|
||||
struct Umask {
|
||||
/// \brief Constructor
|
||||
/// @brief Constructor
|
||||
///
|
||||
/// Set wanted bits in umask.
|
||||
Umask(mode_t mask);
|
||||
|
||||
/// \brief Destructor.
|
||||
/// @brief Destructor.
|
||||
///
|
||||
/// Restore umask.
|
||||
~Umask();
|
||||
|
||||
private:
|
||||
/// \brief Original umask.
|
||||
/// @brief Original umask.
|
||||
mode_t orig_umask_;
|
||||
};
|
||||
|
||||
/// \brief Paths on a filesystem
|
||||
bool
|
||||
isSocket(const std::string& path);
|
||||
|
||||
/// @brief Paths on a filesystem
|
||||
struct Path {
|
||||
/// \brief Constructor
|
||||
/// @brief Constructor
|
||||
///
|
||||
/// Splits the full name into components.
|
||||
Path(std::string const& path);
|
||||
|
||||
/// \brief Get the path in textual format.
|
||||
/// @brief Get the path in textual format.
|
||||
///
|
||||
/// Counterpart for std::filesystem::path::string.
|
||||
///
|
||||
/// \return stored filename.
|
||||
std::string str() const;
|
||||
|
||||
/// \brief Get the parent path.
|
||||
/// @brief Get the parent path.
|
||||
///
|
||||
/// Counterpart for std::filesystem::path::parent_path.
|
||||
///
|
||||
/// \return parent path of current path.
|
||||
std::string parentPath() const;
|
||||
|
||||
/// \brief Get the base name of the file without the extension.
|
||||
/// @brief Get the base name of the file without the extension.
|
||||
///
|
||||
/// Counterpart for std::filesystem::path::stem.
|
||||
///
|
||||
/// \return the base name of current path without the extension.
|
||||
std::string stem() const;
|
||||
|
||||
/// \brief Get the extension of the file.
|
||||
/// @brief Get the extension of the file.
|
||||
///
|
||||
/// Counterpart for std::filesystem::path::extension.
|
||||
///
|
||||
/// \return extension of current path.
|
||||
std::string extension() const;
|
||||
|
||||
/// \brief Get the name of the file, extension included.
|
||||
/// @brief Get the name of the file, extension included.
|
||||
///
|
||||
/// Counterpart for std::filesystem::path::filename.
|
||||
///
|
||||
/// \return name + extension of current path.
|
||||
std::string filename() const;
|
||||
|
||||
/// \brief Identifies the extension in {replacement}, trims it, and
|
||||
/// @brief Identifies the extension in {replacement}, trims it, and
|
||||
/// replaces this instance's extension with it.
|
||||
///
|
||||
/// Counterpart for std::filesystem::path::replace_extension.
|
||||
@@ -121,7 +124,7 @@ struct Path {
|
||||
/// \return The current instance after the replacement was done.
|
||||
Path& replaceExtension(std::string const& replacement = std::string());
|
||||
|
||||
/// \brief Trims {replacement} and replaces this instance's parent path with
|
||||
/// @brief Trims {replacement} and replaces this instance's parent path with
|
||||
/// it.
|
||||
///
|
||||
/// The change is done in the members and {this} is returned to allow call
|
||||
@@ -133,16 +136,24 @@ struct Path {
|
||||
Path& replaceParentPath(std::string const& replacement = std::string());
|
||||
|
||||
private:
|
||||
/// \brief Parent path.
|
||||
/// @brief Parent path.
|
||||
std::string parent_path_;
|
||||
|
||||
/// \brief Stem.
|
||||
/// @brief Stem.
|
||||
std::string stem_;
|
||||
|
||||
/// \brief File name extension.
|
||||
/// @brief File name extension.
|
||||
std::string extension_;
|
||||
};
|
||||
|
||||
struct TemporaryDirectory {
|
||||
TemporaryDirectory();
|
||||
~TemporaryDirectory();
|
||||
std::string dirName();
|
||||
private:
|
||||
std::string dir_name_;
|
||||
};
|
||||
|
||||
} // namespace file
|
||||
} // namespace util
|
||||
} // namespace isc
|
||||
|
@@ -85,7 +85,8 @@ TEST_F(FileUtilTest, umask) {
|
||||
TEST(PathTest, components) {
|
||||
// Complete name
|
||||
Path fname("/alpha/beta/gamma.delta");
|
||||
EXPECT_EQ("/alpha/beta/", fname.parentPath());
|
||||
EXPECT_EQ("/alpha/beta/gamma.delta", fname.str());
|
||||
EXPECT_EQ("/alpha/beta", fname.parentPath());
|
||||
EXPECT_EQ("gamma", fname.stem());
|
||||
EXPECT_EQ(".delta", fname.extension());
|
||||
EXPECT_EQ("gamma.delta", fname.filename());
|
||||
@@ -94,6 +95,7 @@ TEST(PathTest, components) {
|
||||
/// @brief Check replaceExtension.
|
||||
TEST(PathTest, replaceExtension) {
|
||||
Path fname("a.b");
|
||||
EXPECT_EQ("a.b", fname.str());
|
||||
|
||||
EXPECT_EQ("a", fname.replaceExtension("").str());
|
||||
EXPECT_EQ("a.f", fname.replaceExtension(".f").str());
|
||||
@@ -110,11 +112,11 @@ TEST(PathTest, replaceParentPath) {
|
||||
EXPECT_EQ("a.b", fname.str());
|
||||
|
||||
fname.replaceParentPath("/just/some/dir/");
|
||||
EXPECT_EQ("/just/some/dir/", fname.parentPath());
|
||||
EXPECT_EQ("/just/some/dir", fname.parentPath());
|
||||
EXPECT_EQ("/just/some/dir/a.b", fname.str());
|
||||
|
||||
fname.replaceParentPath("/just/some/dir");
|
||||
EXPECT_EQ("/just/some/dir/", fname.parentPath());
|
||||
EXPECT_EQ("/just/some/dir", fname.parentPath());
|
||||
EXPECT_EQ("/just/some/dir/a.b", fname.str());
|
||||
|
||||
fname.replaceParentPath("/");
|
||||
@@ -126,11 +128,11 @@ TEST(PathTest, replaceParentPath) {
|
||||
EXPECT_EQ("a.b", fname.str());
|
||||
|
||||
fname = Path("/first/a.b");
|
||||
EXPECT_EQ("/first/", fname.parentPath());
|
||||
EXPECT_EQ("/first", fname.parentPath());
|
||||
EXPECT_EQ("/first/a.b", fname.str());
|
||||
|
||||
fname.replaceParentPath("/just/some/dir");
|
||||
EXPECT_EQ("/just/some/dir/", fname.parentPath());
|
||||
EXPECT_EQ("/just/some/dir", fname.parentPath());
|
||||
EXPECT_EQ("/just/some/dir/a.b", fname.str());
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user