From 4cb5b4fca95a02429d977e7fb6d0bc26e5e4aa22 Mon Sep 17 00:00:00 2001 From: Julien Kauffmann Date: Tue, 5 May 2015 19:30:45 -0400 Subject: [PATCH] Added a DNS script for linux and Mac OSX as an alternative to system calls (fixes #15) --- apps/freelan/config/freelan.cfg | 21 ++++++++ apps/freelan/src/configuration_helper.cpp | 2 + apps/freelan/src/main.cpp | 5 ++ apps/freelan/src/tools.cpp | 18 +++++++ apps/freelan/src/tools.hpp | 11 ++++ apps/freelan/src/windows/service.cpp | 5 ++ .../asiotap/base_dns_servers_manager.hpp | 50 +++++++++++++++++-- libs/asiotap/include/asiotap/error.hpp | 4 +- libs/asiotap/src/error.cpp | 8 +++ .../freelan/include/freelan/configuration.hpp | 5 ++ libs/freelan/include/freelan/core.hpp | 21 ++++++++ libs/freelan/src/configuration.cpp | 4 +- libs/freelan/src/core.cpp | 35 ++++++++++++- 13 files changed, 183 insertions(+), 6 deletions(-) diff --git a/apps/freelan/config/freelan.cfg b/apps/freelan/config/freelan.cfg index f4df4148..207afc77 100644 --- a/apps/freelan/config/freelan.cfg +++ b/apps/freelan/config/freelan.cfg @@ -722,6 +722,27 @@ ipv6_address_prefix_length=2aa1::1/8 # Default: in_network #dns_servers_acceptance_policy=in_network +# The script to call when a DNS entry is to be added or removed. +# +# The script is called with the tap adapter's name as it's first argument. +# The second argument is a verb which can be: +# - add: A DNS entry must be added. +# - remove: A DNS entry must be removed. +# The third argument is the DNS server address to add or remove. +# +# If the script exits with a non-zero value, it is assumed that the addition or +# removal of the DNS entry failed. If the addition fails for a given address, +# the script won't be called for removal for this same address. +# +# On Windows, if no script is provided, FreeLAN will add/remove the DNS server +# using system calls. +# +# On Mac OS X and Linux, there is sadly no reliable system call and you MUST +# provide a script or DNS settings will simply be ignored. +# +# Default: +#dns_script= + [security] # The passphrase used to generate a pre-shared key to use for encryption. diff --git a/apps/freelan/src/configuration_helper.cpp b/apps/freelan/src/configuration_helper.cpp index 8fb75c9b..e32afd51 100644 --- a/apps/freelan/src/configuration_helper.cpp +++ b/apps/freelan/src/configuration_helper.cpp @@ -372,6 +372,7 @@ po::options_description get_router_options() ("router.system_route_acceptance_policy", po::value()->default_value(fl::router_configuration::system_route_scope_type::none), "The system route acceptance policy.") ("router.maximum_routes_limit", po::value()->default_value(1), "The maximum count of routes to accept for a given host.") ("router.dns_servers_acceptance_policy", po::value()->default_value(fl::router_configuration::dns_servers_scope_type::in_network), "The DNS servers acceptance policy.") + ("router.dns_script", po::value()->default_value(""), "The DNS script.") ; return result; @@ -567,4 +568,5 @@ void setup_configuration(const fscp::logger& logger, fl::configuration& configur configuration.router.system_route_acceptance_policy = vm["router.system_route_acceptance_policy"].as(); configuration.router.maximum_routes_limit = vm["router.maximum_routes_limit"].as(); configuration.router.dns_servers_acceptance_policy = vm["router.dns_servers_acceptance_policy"].as(); + configuration.router.dns_script = vm["router.dns_script"].as(); } diff --git a/apps/freelan/src/main.cpp b/apps/freelan/src/main.cpp index fa866906..2c5661a7 100644 --- a/apps/freelan/src/main.cpp +++ b/apps/freelan/src/main.cpp @@ -478,6 +478,11 @@ void run(fscp::logger& logger, const cli_configuration& configuration, int& exit core.set_authentication_callback(boost::bind(&execute_authentication_script, configuration.fl_configuration.server.authentication_script, logger, _1, _2, _3, _4)); } + if (!configuration.fl_configuration.router.dns_script.empty()) + { + core.set_dns_callback(boost::bind(&execute_dns_script, configuration.fl_configuration.router.dns_script, logger, _1, _2, _3)); + } + core.open(); signals.async_wait(boost::bind(signal_handler, _1, _2, boost::ref(core), boost::ref(exit_signal))); diff --git a/apps/freelan/src/tools.cpp b/apps/freelan/src/tools.cpp index bd7be13b..c592ebf2 100644 --- a/apps/freelan/src/tools.cpp +++ b/apps/freelan/src/tools.cpp @@ -227,3 +227,21 @@ bool execute_authentication_script(const boost::filesystem::path& script, const return (exit_status == 0); } + +bool execute_dns_script(const boost::filesystem::path& script, const fscp::logger& logger, const std::string& tap_adapter, freelan::core::DnsAction action, const boost::asio::ip::address& dns_server) +{ + const std::string str_action = (action == freelan::core::DnsAction::Add) ? "add" : "remove"; + +#if defined(WINDOWS) && defined(UNICODE) + int exit_status = execute(logger, script, { to_wstring(tap_adapter), to_wstring(str_action), to_wstring(dns_server.to_string()) }); +#else + int exit_status = execute(logger, script, { tap_adapter, str_action, dns_server.to_string() }); +#endif + + if (exit_status != 0) + { + logger(fscp::log_level::warning) << "DNS script exited with a non-zero exit status: " << exit_status; + } + + return (exit_status == 0); +} \ No newline at end of file diff --git a/apps/freelan/src/tools.hpp b/apps/freelan/src/tools.hpp index 8029849d..b1319b32 100644 --- a/apps/freelan/src/tools.hpp +++ b/apps/freelan/src/tools.hpp @@ -110,4 +110,15 @@ bool execute_certificate_validation_script(const boost::filesystem::path& script */ bool execute_authentication_script(const boost::filesystem::path& script, const fscp::logger& logger, const std::string& username, const std::string& password, const std::string& remote_host, uint16_t remote_port); +/** + * \brief The DNS function. + * \param script The script to call. + * \param logger The logger instance. + * \param tap_adapter The tap_adapter instance. + * \param action A flag that indicates the action. + * \param dns_server The DNS server address to add or remove. + * \return The execution result of the specified script. + */ +bool execute_dns_script(const boost::filesystem::path& script, const fscp::logger& logger, const std::string& tap_adapter, freelan::core::DnsAction action, const boost::asio::ip::address& dns_server); + #endif /* TOOLS_HPP */ diff --git a/apps/freelan/src/windows/service.cpp b/apps/freelan/src/windows/service.cpp index 1af5554e..601722b9 100644 --- a/apps/freelan/src/windows/service.cpp +++ b/apps/freelan/src/windows/service.cpp @@ -515,6 +515,11 @@ namespace windows core.set_authentication_callback(boost::bind(&execute_authentication_script, fl_configuration.server.authentication_script, logger, _1, _2, _3, _4)); } + if (!fl_configuration.router.dns_script.empty()) + { + core.set_dns_callback(boost::bind(&execute_dns_script, fl_configuration.router.dns_script, logger, _1, _2, _3)); + } + core.open(); boost::unique_lock lock(ctx.stop_function_mutex); diff --git a/libs/asiotap/include/asiotap/base_dns_servers_manager.hpp b/libs/asiotap/include/asiotap/base_dns_servers_manager.hpp index b9016469..0c43ce2b 100644 --- a/libs/asiotap/include/asiotap/base_dns_servers_manager.hpp +++ b/libs/asiotap/include/asiotap/base_dns_servers_manager.hpp @@ -130,6 +130,16 @@ namespace asiotap */ typedef boost::shared_ptr entry_type; + /** + * \brief The add handler type. + */ + typedef boost::function dns_server_add_handler_type; + + /** + * \brief The remove handler type. + */ + typedef boost::function dns_server_remove_handler_type; + /** * \brief The registration success handler type. */ @@ -151,7 +161,13 @@ namespace asiotap typedef boost::function dns_server_unregistration_failure_handler_type; explicit base_dns_servers_manager(boost::asio::io_service& io_service_) : - m_io_service(io_service_) + m_io_service(io_service_), + m_dns_server_add_handler(), + m_dns_server_remove_handler(), + m_dns_server_registration_success_handler(), + m_dns_server_registration_failure_handler(), + m_dns_server_unregistration_success_handler(), + m_dns_server_unregistration_failure_handler() { } @@ -166,6 +182,16 @@ namespace asiotap return m_io_service; } + void set_dns_server_add_handler(dns_server_add_handler_type handler) + { + m_dns_server_add_handler = handler; + } + + void set_dns_server_remove_handler(dns_server_remove_handler_type handler) + { + m_dns_server_remove_handler = handler; + } + void set_dns_server_registration_success_handler(dns_server_registration_success_handler_type handler) { m_dns_server_registration_success_handler = handler; @@ -190,7 +216,15 @@ namespace asiotap { try { - static_cast(this)->register_dns_server(dns_server); + bool result = false; + + if (m_dns_server_add_handler) { + result = m_dns_server_add_handler(dns_server); + } + + if (!result) { + static_cast(this)->register_dns_server(dns_server); + } if (m_dns_server_registration_success_handler) { @@ -214,7 +248,15 @@ namespace asiotap { try { - static_cast(this)->unregister_dns_server(dns_server); + bool result = false; + + if (m_dns_server_remove_handler) { + result = m_dns_server_remove_handler(dns_server); + } + + if (!result) { + static_cast(this)->unregister_dns_server(dns_server); + } if (m_dns_server_unregistration_success_handler) { @@ -256,6 +298,8 @@ namespace asiotap boost::asio::io_service& m_io_service; entry_table_type m_entry_table; + dns_server_add_handler_type m_dns_server_add_handler; + dns_server_remove_handler_type m_dns_server_remove_handler; dns_server_registration_success_handler_type m_dns_server_registration_success_handler; dns_server_registration_failure_handler_type m_dns_server_registration_failure_handler; dns_server_unregistration_success_handler_type m_dns_server_unregistration_success_handler; diff --git a/libs/asiotap/include/asiotap/error.hpp b/libs/asiotap/include/asiotap/error.hpp index b827cbb2..46d8a805 100644 --- a/libs/asiotap/include/asiotap/error.hpp +++ b/libs/asiotap/include/asiotap/error.hpp @@ -72,7 +72,9 @@ namespace asiotap process_handle_expected, external_process_output_parsing_error, no_such_tap_adapter, - invalid_ip_configuration + invalid_ip_configuration, + external_process_execution_failed, + no_dns_script_provided }; /** diff --git a/libs/asiotap/src/error.cpp b/libs/asiotap/src/error.cpp index ea1e5472..7301495e 100644 --- a/libs/asiotap/src/error.cpp +++ b/libs/asiotap/src/error.cpp @@ -94,6 +94,14 @@ namespace asiotap { return "The specified IP configuration is invalid"; } + case asiotap_error::external_process_execution_failed: + { + return "The execution of the external process failed"; + } + case asiotap_error::no_dns_script_provided: + { + return "No DNS script was provided"; + } default: { return "Unknown asiotap error"; diff --git a/libs/freelan/include/freelan/configuration.hpp b/libs/freelan/include/freelan/configuration.hpp index af3473ad..767eb9df 100644 --- a/libs/freelan/include/freelan/configuration.hpp +++ b/libs/freelan/include/freelan/configuration.hpp @@ -620,6 +620,11 @@ namespace freelan * \brief The DNS servers acceptance policy. */ dns_servers_scope_type dns_servers_acceptance_policy; + + /** + * \brief The DNS script. + */ + boost::filesystem::path dns_script; }; /** diff --git a/libs/freelan/include/freelan/core.hpp b/libs/freelan/include/freelan/core.hpp index f7f686b7..69c3bbf0 100644 --- a/libs/freelan/include/freelan/core.hpp +++ b/libs/freelan/include/freelan/core.hpp @@ -239,6 +239,16 @@ namespace freelan */ typedef boost::function tap_adapter_handler_type; + enum class DnsAction { + Add, + Remove + }; + + /** + * \brief The up callback type. + */ + typedef boost::function dns_handler_type; + // Public constants /** @@ -408,6 +418,16 @@ namespace freelan m_tap_adapter_down_callback = callback; } + /** + * \brief Set the DNS callback. + * \param callback The callback. + * \warning This method can only be called when the core is NOT running. + */ + void set_dns_callback(dns_handler_type callback) + { + m_dns_callback = callback; + } + /** * \brief Open the core. * \see close @@ -441,6 +461,7 @@ namespace freelan certificate_validation_handler_type m_certificate_validation_callback; tap_adapter_handler_type m_tap_adapter_up_callback; tap_adapter_handler_type m_tap_adapter_down_callback; + dns_handler_type m_dns_callback; private: /* General purpose */ diff --git a/libs/freelan/src/configuration.cpp b/libs/freelan/src/configuration.cpp index 176957f8..fe2b8afa 100644 --- a/libs/freelan/src/configuration.cpp +++ b/libs/freelan/src/configuration.cpp @@ -121,7 +121,9 @@ namespace freelan accept_routes_requests(true), internal_route_acceptance_policy(internal_route_scope_type::unicast_in_network), system_route_acceptance_policy(system_route_scope_type::none), - maximum_routes_limit(1) + maximum_routes_limit(1), + dns_servers_acceptance_policy(dns_servers_scope_type::in_network), + dns_script() { } diff --git a/libs/freelan/src/core.cpp b/libs/freelan/src/core.cpp index 85857cd4..7fa8fc26 100644 --- a/libs/freelan/src/core.cpp +++ b/libs/freelan/src/core.cpp @@ -434,6 +434,7 @@ namespace freelan m_certificate_validation_callback(), m_tap_adapter_up_callback(), m_tap_adapter_down_callback(), + m_dns_callback(), m_fscp_server(), m_contact_timer(m_io_service, CONTACT_PERIOD), m_dynamic_contact_timer(m_io_service, DYNAMIC_CONTACT_PERIOD), @@ -502,8 +503,40 @@ namespace freelan m_route_manager.set_route_unregistration_failure_handler([this](const asiotap::route_manager::route_type& route, const boost::system::system_error& ex){ m_logger(fscp::log_level::warning) << "Unable to remove system route (" << route << "): " << ex.what(); }); - + // Setup the DNS servers manager. + m_dns_servers_manager.set_dns_server_add_handler([this](const asiotap::dns_servers_manager::dns_server_type& dns_server) -> bool { + if (m_dns_callback) { + if (!m_dns_callback(dns_server.interface_name, DnsAction::Add, dns_server.dns_server_address.value())) { + throw boost::system::system_error(make_error_code(asiotap::asiotap_error::external_process_execution_failed)); + } + + return true; + } else { +#ifndef WINDOWS + throw boost::system::system_error(make_error_code(asiotap::asiotap_error::no_dns_script_provided)); +#endif + } + + return false; + }); + m_dns_servers_manager.set_dns_server_remove_handler([this](const asiotap::dns_servers_manager::dns_server_type& dns_server) -> bool { + if (m_dns_callback) { + if (!m_dns_callback(dns_server.interface_name, DnsAction::Remove, dns_server.dns_server_address.value())) { + throw boost::system::system_error(make_error_code(asiotap::asiotap_error::external_process_execution_failed)); + } + + return true; + } + else { +#ifndef WINDOWS + m_logger(fscp::log_level::warning) << "Should have added a DNS server but no DNS script was configured."; + throw boost::system::system_error(make_error_code(asiotap::asiotap_error::no_dns_script_provided)); +#endif + } + + return false; + }); m_dns_servers_manager.set_dns_server_registration_success_handler([this](const asiotap::dns_servers_manager::dns_server_type& dns_server){ m_logger(fscp::log_level::information) << "Added DNS server: " << dns_server; });