This is an automated email from the ASF dual-hosted git repository.

szaszm pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/nifi-minifi-cpp.git

commit c4c80e63012fa92c00c7a4f3b5ea383f1ffa55d3
Author: Martin Zink <[email protected]>
AuthorDate: Tue Feb 28 13:10:01 2023 +0100

    MINIFICPP-2047 added reverseDnsLookup to EL
    
    Closes #1510
    Signed-off-by: Marton Szasz <[email protected]>
---
 EXPRESSIONS.md                                     | 26 +++++++++
 extensions/expression-language/Expression.cpp      | 20 +++++++
 .../tests/ExpressionLanguageTests.cpp              | 64 ++++++++++++++++++++++
 libminifi/include/utils/net/DNS.h                  |  7 +++
 libminifi/src/utils/net/DNS.cpp                    | 36 ++++++++++++
 libminifi/test/unit/NetUtilsTest.cpp               | 34 +++++++++++-
 6 files changed, 185 insertions(+), 2 deletions(-)

diff --git a/EXPRESSIONS.md b/EXPRESSIONS.md
index 79b9e0c11..97ba68341 100644
--- a/EXPRESSIONS.md
+++ b/EXPRESSIONS.md
@@ -224,6 +224,7 @@ token, filename.
 - [`hostname`](#hostname)
 - [`UUID`](#uuid)
 - [`literal`](#literal)
+- [`reverseDnsLookup`](#reversednslookup)
 
 ### Evaluating Multiple Attributes
 
@@ -1590,6 +1591,31 @@ to evaluate additional functions against.
 ${allMatchingAttributes('a.*'):count()} ):gt(3)}` returns true if there are
 more than 3 attributes whose names begin with the letter a.
 
+### reverseDnsLookup
+
+**Description**: Performs a reverse DNS lookup on an ip address, and returns 
the corresponding hostname.
+
+**Subject Type**: No subject
+
+**Arguments**:
+
+| Argument                      | Description                                  
                                                                                
|
+|-------------------------------|------------------------------------------------------------------------------------------------------------------------------|
+| IP address                    | The ip address to perform the reverse DNS 
lookup on.                                                                      
   |
+| Timeout duration milliseconds | Optional parameter that specifies the 
timeout duration of the operation in milliseconds. If not specified, defaults 
to 5000. |
+
+
+**Return Type**: String
+
+**Examples**:
+
+| Expression                                         | Value        |
+|----------------------------------------------------|--------------|
+| `${reverseDnsLookup('127.0.0.1')}`                 | `localhost`  |
+| `${reverseDnsLookup('::1')}`                       | `localhost`  |
+| `${reverseDnsLookup('2001:4860:4860::8888'), 100}` | `dns.google` |
+
+
 ## Evaluating Multiple Attributes
 
 When it becomes necessary to evaluate the same conditions against multiple
diff --git a/extensions/expression-language/Expression.cpp 
b/extensions/expression-language/Expression.cpp
index c52411c74..f1570775c 100644
--- a/extensions/expression-language/Expression.cpp
+++ b/extensions/expression-language/Expression.cpp
@@ -64,6 +64,10 @@
 #include "Driver.h"
 
 #include "date/tz.h"
+#include "utils/net/DNS.h"
+#include "utils/expected.h"
+
+using namespace std::literals::chrono_literals;
 
 namespace org::apache::nifi::minifi::expression {
 
@@ -183,6 +187,20 @@ Value expr_ip(const std::vector<Value>& /*args*/) {
   return {};
 }
 
+Value expr_reverseDnsLookup(const std::vector<Value>& args) {
+  std::string ip_address_str = args[0].asString();
+
+  std::chrono::steady_clock::duration timeout_duration = 5s;
+  if (args.size() > 1) {
+    timeout_duration = std::chrono::milliseconds(args[1].asUnsignedLong());
+  }
+
+  return utils::net::addressFromString(ip_address_str)
+      | utils::flatMap([timeout_duration](const auto& ip_address) { return 
utils::net::reverseDnsLookup(ip_address, timeout_duration);})
+      | utils::map([](const auto& hostname)-> Value { return Value(hostname); 
})
+      | utils::valueOrElse([&](std::error_code error_code) { throw 
std::system_error(error_code);});
+}
+
 Value expr_uuid(const std::vector<Value>& /*args*/) {
   return Value(utils::IdGenerator::getIdGenerator()->generate().to_string());
 }
@@ -1339,6 +1357,8 @@ Expression make_dynamic_function(const std::string 
&function_name, const std::ve
     return make_dynamic_function_incomplete<resolve_user_id>(function_name, 
args, 0);
   } else if (function_name == "ip") {
     return make_dynamic_function_incomplete<expr_ip>(function_name, args, 0);
+  } else if (function_name == "reverseDnsLookup") {
+    return 
make_dynamic_function_incomplete<expr_reverseDnsLookup>(function_name, args, 1);
   } else if (function_name == "UUID") {
     return make_dynamic_function_incomplete<expr_uuid>(function_name, args, 0);
   } else if (function_name == "toUpper") {
diff --git a/extensions/expression-language/tests/ExpressionLanguageTests.cpp 
b/extensions/expression-language/tests/ExpressionLanguageTests.cpp
index 77cd383af..477000981 100644
--- a/extensions/expression-language/tests/ExpressionLanguageTests.cpp
+++ b/extensions/expression-language/tests/ExpressionLanguageTests.cpp
@@ -41,6 +41,7 @@
 #include "Catch.h"
 #include "unit/ProvenanceTestHelper.h"
 #include "date/tz.h"
+#include "Utils.h"
 
 namespace expression = org::apache::nifi::minifi::expression;
 
@@ -1280,6 +1281,69 @@ TEST_CASE("Full Hostname", "[expressionFullHostname]") {
   REQUIRE(!expr(expression::Parameters{ flow_file_a }).asString().empty());
 }
 
+TEST_CASE("Reverse DNS lookup with valid ip", 
"[ExpressionLanguage][reverseDnsLookup]") {
+  auto expr = expression::compile("${reverseDnsLookup(${ip_addr})}");
+
+  auto flow_file_a = std::make_shared<core::FlowFile>();
+  std::string expected_hostname;
+  SECTION("dns.google IPv4") {
+    flow_file_a->addAttribute("ip_addr", "8.8.8.8");
+    expected_hostname = "dns.google";
+  }
+
+  SECTION("dns.google IPv6") {
+    if (minifi::test::utils::isIPv6Disabled())
+      return;
+    flow_file_a->addAttribute("ip_addr", "2001:4860:4860::8888");
+    expected_hostname = "dns.google";
+  }
+
+  SECTION("Unresolvable address IPv4") {
+    flow_file_a->addAttribute("ip_addr", "192.0.2.0");
+    expected_hostname = "192.0.2.0";
+  }
+
+  SECTION("Unresolvable address IPv6") {
+    if (minifi::test::utils::isIPv6Disabled())
+      return;
+    flow_file_a->addAttribute("ip_addr", "2001:db8::");
+    expected_hostname = "2001:db8::";
+  }
+
+  REQUIRE(expr(expression::Parameters{ flow_file_a }).asString() ==  
expected_hostname);
+}
+
+TEST_CASE("Reverse DNS lookup with invalid ip", 
"[ExpressionLanguage][reverseDnsLookup]") {
+  auto expr = expression::compile("${reverseDnsLookup(${ip_addr})}");
+
+  auto flow_file_a = std::make_shared<core::FlowFile>();
+  flow_file_a->addAttribute("ip_addr", "banana");
+
+  REQUIRE_THROWS_AS(expr(expression::Parameters{flow_file_a}), 
std::runtime_error);
+}
+
+TEST_CASE("Reverse DNS lookup with invalid timeout parameter", 
"[ExpressionLanguage][reverseDnsLookup]") {
+  auto expr = expression::compile("${reverseDnsLookup(${ip_addr}, 
${timeout})}");
+
+  auto flow_file_a = std::make_shared<core::FlowFile>();
+  flow_file_a->addAttribute("ip_addr", "192.0.2.1");
+  flow_file_a->addAttribute("timeout", "strawberry");
+
+  REQUIRE_THROWS_AS(expr(expression::Parameters{ flow_file_a }), 
std::invalid_argument);
+}
+
+TEST_CASE("Reverse DNS lookup with valid timeout parameter", 
"[ExpressionLanguage][reverseDnsLookup]") {
+  auto reverse_lookup_expr_500ms = 
expression::compile("${reverseDnsLookup(${ip_addr}, 500)}");
+  auto reverse_lookup_expr_0ms = 
expression::compile("${reverseDnsLookup(${ip_addr}, 0)}");  // 0ms to make sure 
it times out
+
+  auto flow_file_a = std::make_shared<core::FlowFile>();
+  std::string expected_hostname;
+  flow_file_a->addAttribute("ip_addr", "192.0.2.1");
+
+  
REQUIRE_THROWS_AS(reverse_lookup_expr_0ms(expression::Parameters{flow_file_a}), 
std::runtime_error);
+  REQUIRE_NOTHROW(reverse_lookup_expr_500ms(expression::Parameters{ 
flow_file_a }));
+}
+
 TEST_CASE("UUID", "[expressionUuid]") {
   auto expr = expression::compile("${UUID()}");
 
diff --git a/libminifi/include/utils/net/DNS.h 
b/libminifi/include/utils/net/DNS.h
index 2b9a31b57..4b33609bd 100644
--- a/libminifi/include/utils/net/DNS.h
+++ b/libminifi/include/utils/net/DNS.h
@@ -15,6 +15,7 @@
  * limitations under the License.
  */
 #pragma once
+#include <chrono>
 #include <memory>
 #include <string>
 #include <string_view>
@@ -22,6 +23,7 @@
 #include "nonstd/expected.hpp"
 #include "utils/gsl.h"
 #include "IpProtocol.h"
+#include "asio/ip/address.hpp"
 
 struct addrinfo;
 
@@ -41,4 +43,9 @@ inline auto resolveHost(const char* const hostname, const 
uint16_t port, const I
 inline auto resolveHost(const uint16_t port, const IpProtocol proto = 
IpProtocol::TCP, const bool need_canonname = false) {
   return resolveHost(nullptr, port, proto, need_canonname);
 }
+
+nonstd::expected<asio::ip::address, std::error_code> 
addressFromString(std::string_view ip_address_str);
+
+nonstd::expected<std::string, std::error_code> reverseDnsLookup(const 
asio::ip::address& ip_address, std::chrono::steady_clock::duration timeout = 
std::chrono::seconds(5));
+
 }  // namespace org::apache::nifi::minifi::utils::net
diff --git a/libminifi/src/utils/net/DNS.cpp b/libminifi/src/utils/net/DNS.cpp
index b5eac92bb..ba8678912 100644
--- a/libminifi/src/utils/net/DNS.cpp
+++ b/libminifi/src/utils/net/DNS.cpp
@@ -17,6 +17,9 @@
 #include "utils/net/DNS.h"
 #include "Exception.h"
 #include "utils/StringUtils.h"
+#include "utils/net/AsioCoro.h"
+#include "asio/detached.hpp"
+#include "asio/ip/udp.hpp"
 
 #ifdef WIN32
 #ifndef WIN32_LEAN_AND_MEAN
@@ -91,4 +94,37 @@ nonstd::expected<gsl::not_null<std::unique_ptr<addrinfo, 
addrinfo_deleter>>, std
   return addr_info;
 }
 
+nonstd::expected<asio::ip::address, std::error_code> addressFromString(const 
std::string_view ip_address_str) {
+  std::error_code ip_address_from_string_error;
+  auto ip_address = asio::ip::address::from_string(ip_address_str.data(), 
ip_address_from_string_error);
+  if (ip_address_from_string_error)
+    return nonstd::make_unexpected(ip_address_from_string_error);
+  return ip_address;
+}
+
+namespace {
+asio::awaitable<std::tuple<std::error_code, 
asio::ip::basic_resolver<asio::ip::udp>::results_type>> 
asyncReverseDnsLookup(const asio::ip::address& ip_address,
+    std::chrono::steady_clock::duration timeout_duration) {
+  asio::ip::basic_resolver<asio::ip::udp> resolver(co_await 
asio::this_coro::executor);
+  co_return co_await 
asyncOperationWithTimeout(resolver.async_resolve({ip_address, 0}, 
use_nothrow_awaitable), timeout_duration);
+}
+}  // namespace
+
+nonstd::expected<std::string, std::error_code> reverseDnsLookup(const 
asio::ip::address& ip_address, std::chrono::steady_clock::duration 
timeout_duration) {
+  asio::io_context io_context;
+
+  std::error_code resolve_error;
+  asio::ip::basic_resolver<asio::ip::udp>::results_type results;
+
+  co_spawn(io_context, asyncReverseDnsLookup(ip_address, timeout_duration), 
[&resolve_error, &results](const std::exception_ptr&, const auto& 
resolve_results) {
+    resolve_error = std::get<std::error_code>(resolve_results);
+    results = 
std::get<asio::ip::basic_resolver<asio::ip::udp>::results_type>(resolve_results);
+  });
+  io_context.run();
+
+  if (resolve_error)
+    return nonstd::make_unexpected(resolve_error);
+  return results->host_name();
+}
+
 }  // namespace org::apache::nifi::minifi::utils::net
diff --git a/libminifi/test/unit/NetUtilsTest.cpp 
b/libminifi/test/unit/NetUtilsTest.cpp
index 25fec78c4..add55e050 100644
--- a/libminifi/test/unit/NetUtilsTest.cpp
+++ b/libminifi/test/unit/NetUtilsTest.cpp
@@ -16,15 +16,15 @@
  * limitations under the License.
  */
 
-#include <functional>
 #include <string>
-#include <type_traits>
 
 #include "../TestBase.h"
 #include "../Catch.h"
 #include "utils/net/DNS.h"
 #include "utils/net/Socket.h"
 #include "utils/StringUtils.h"
+#include "../Utils.h"
+#include "range/v3/algorithm/contains.hpp"
 
 namespace utils = org::apache::nifi::minifi::utils;
 namespace net = utils::net;
@@ -34,3 +34,33 @@ TEST_CASE("net::resolveHost", 
"[net][dns][utils][resolveHost]") {
   const auto localhost_address = 
net::sockaddr_ntop(net::resolveHost("localhost", "10080").value()->ai_addr);
   REQUIRE((utils::StringUtils::startsWith(localhost_address, "127") || 
localhost_address == "::1"));
 }
+
+TEST_CASE("net::reverseDnsLookup", "[net][dns][reverseDnsLookup]") {
+  SECTION("dns.google IPv4") {
+    auto dns_google_hostname = 
net::reverseDnsLookup(asio::ip::address::from_string("8.8.8.8"));
+    REQUIRE(dns_google_hostname.has_value());
+    CHECK(dns_google_hostname == "dns.google");
+  }
+
+  SECTION("dns.google IPv6") {
+    if (minifi::test::utils::isIPv6Disabled())
+      return;
+    auto dns_google_hostname = 
net::reverseDnsLookup(asio::ip::address::from_string("2001:4860:4860::8888"));
+    REQUIRE(dns_google_hostname.has_value());
+    CHECK(dns_google_hostname == "dns.google");
+  }
+
+  SECTION("Unresolvable address IPv4") {
+    auto unresolvable_hostname = 
net::reverseDnsLookup(asio::ip::address::from_string("192.0.2.0"));
+    REQUIRE(unresolvable_hostname.has_value());
+    CHECK(unresolvable_hostname == "192.0.2.0");
+  }
+
+  SECTION("Unresolvable address IPv6") {
+    if (minifi::test::utils::isIPv6Disabled())
+      return;
+    auto unresolvable_hostname = 
net::reverseDnsLookup(asio::ip::address::from_string("2001:db8::"));
+    REQUIRE(unresolvable_hostname.has_value());
+    CHECK(unresolvable_hostname == "2001:db8::");
+  }
+}

Reply via email to