[util] a function to check if IP address is among specified

This patch introduces a new utility function IsAddrOneOf()
to check if an IPv4 address represented by Sockaddr object matches
against a set of other addresses.  Any element of the reference set
may be a wildcard.  This is just a wrapper for std::any() with proper
custom comparison operator for Sockaddr.

A unit test for the new functionality is added as well.

This patch will be used in a follow-up patch.

Change-Id: I3f6bac9a5fa038d27ba5924f0fd7d5a50b72a7aa
Reviewed-on: http://gerrit.cloudera.org:8080/19230
Tested-by: Alexey Serbin <alexey@apache.org>
Reviewed-by: Attila Bukor <abukor@apache.org>
diff --git a/src/kudu/util/net/net_util-test.cc b/src/kudu/util/net/net_util-test.cc
index 89094bb..1207b9d 100644
--- a/src/kudu/util/net/net_util-test.cc
+++ b/src/kudu/util/net/net_util-test.cc
@@ -317,4 +317,126 @@
   ASSERT_FALSE(Sockaddr() == addr);
 }
 
+TEST_F(NetUtilTest, IsAddrOneOfBasic) {
+  // The address should match itself.
+  Sockaddr a;
+  ASSERT_OK(a.ParseString("10.0.23.1:123", 0));
+
+  // The address should not match an empty set of pattern addresses.
+  ASSERT_FALSE(IsAddrOneOf(a, {}));
+
+  ASSERT_TRUE(IsAddrOneOf(a, { a }));
+  ASSERT_TRUE(IsAddrOneOf(a, { a, a }));
+
+  Sockaddr a0;
+  ASSERT_OK(a0.ParseString("10.0.23.1:123", 0));
+  ASSERT_TRUE(IsAddrOneOf(a, { a0 }));
+  ASSERT_TRUE(IsAddrOneOf(a0, { a }));
+
+  // Make a copy of Sockaddr and make sure it behaves as expected.
+  Sockaddr b = a;
+  ASSERT_TRUE(IsAddrOneOf(b, { a }));
+  ASSERT_TRUE(IsAddrOneOf(a, { b }));
+
+  Sockaddr n;
+  ASSERT_OK(n.ParseString("10.0.23.100:234", 0));
+  ASSERT_TRUE(IsAddrOneOf(a, { b, n }));
+  ASSERT_FALSE(IsAddrOneOf(n, { a, b }));
+
+  // Same IP address, different port.
+  Sockaddr c;
+  ASSERT_OK(c.ParseString("10.0.23.1:1234", 0));
+  ASSERT_FALSE(IsAddrOneOf(c, { a }));
+  ASSERT_FALSE(IsAddrOneOf(a, { c }));
+  ASSERT_FALSE(IsAddrOneOf(a, { c, c }));
+  ASSERT_TRUE(IsAddrOneOf(a, { c, a, c }));
+  ASSERT_TRUE(IsAddrOneOf(a, { c, c, a }));
+
+  // Different IP addresses, same port.
+  Sockaddr d;
+  ASSERT_OK(d.ParseString("1.23.0.10:1234", 0));
+  ASSERT_FALSE(IsAddrOneOf(d, { c }));
+  ASSERT_FALSE(IsAddrOneOf(c, { d }));
+  ASSERT_FALSE(IsAddrOneOf(c, { d, d }));
+  ASSERT_TRUE(IsAddrOneOf(c, { d, d, c }));
+
+  // Different IP addresses and ports.
+  ASSERT_FALSE(IsAddrOneOf(a, { c }));
+  ASSERT_FALSE(IsAddrOneOf(c, { a }));
+  ASSERT_FALSE(IsAddrOneOf(a, { c, c }));
+  ASSERT_TRUE(IsAddrOneOf(a, { c, c, a }));
+}
+
+TEST_F(NetUtilTest, IsAddrOneOfWildcardPort) {
+  // The address should match itself.
+  Sockaddr ap0;
+  ASSERT_OK(ap0.ParseString("10.0.23.1", 0));
+  ASSERT_EQ(0, ap0.port());
+
+  Sockaddr a0;
+  ASSERT_OK(a0.ParseString("10.0.23.1:0", 0));
+  // Sanity check.
+  ASSERT_EQ(0, a0.port());
+  ASSERT_EQ(a0, ap0);
+
+  Sockaddr ap123;
+  ASSERT_OK(ap123.ParseString("10.0.23.1:123", 0));
+  ASSERT_EQ(123, ap123.port());
+  ASSERT_TRUE(IsAddrOneOf(ap123, { ap0 }));
+  ASSERT_TRUE(IsAddrOneOf(ap123, { a0 }));
+  ASSERT_TRUE(IsAddrOneOf(ap123, { ap0, a0 }));
+  ASSERT_TRUE(IsAddrOneOf(ap123, { ap0, ap123 }));
+  ASSERT_TRUE(IsAddrOneOf(ap123, { a0, ap123 }));
+
+  // The port is wildcard, but the address is different.
+  Sockaddr bp0;
+  ASSERT_OK(bp0.ParseString("1.13.0.10", 0));
+  ASSERT_EQ(0, bp0.port());
+  ASSERT_FALSE(IsAddrOneOf(ap123, { bp0 }));
+  ASSERT_FALSE(IsAddrOneOf(ap123, { bp0, bp0 }));
+  ASSERT_TRUE(IsAddrOneOf(ap123, { bp0, ap123 }));
+  ASSERT_TRUE(IsAddrOneOf(ap123, { bp0, ap0 }));
+}
+
+TEST_F(NetUtilTest, IsAddrOneOfWildcardAddr) {
+  // Wildcards with particular port numbers.
+  auto wp123 = Sockaddr::Wildcard();
+  wp123.set_port(123);
+
+  auto wp234 = Sockaddr::Wildcard();
+  wp234.set_port(234);
+
+  // The same port as in the wildcard.
+  Sockaddr a123;
+  ASSERT_OK(a123.ParseString("10.0.23.1:123", 0));
+  ASSERT_TRUE(IsAddrOneOf(a123, { wp123 }));
+  ASSERT_TRUE(IsAddrOneOf(a123, { wp123, wp234 }));
+  ASSERT_TRUE(IsAddrOneOf(a123, { wp234, wp123 }));
+
+  // Different port from the wildcard.
+  Sockaddr a234;
+  ASSERT_OK(a234.ParseString("10.0.23.1:234", 0));
+  ASSERT_FALSE(IsAddrOneOf(a234, { wp123 }));
+  ASSERT_FALSE(IsAddrOneOf(a123, { wp234 }));
+  ASSERT_FALSE(IsAddrOneOf(a234, { wp123, a123 }));
+  ASSERT_FALSE(IsAddrOneOf(a123, { wp234, a234 }));
+
+  // A mix of matching wildcard address and non-matching address.
+  ASSERT_TRUE(IsAddrOneOf(a234, { wp234, a123 }));
+  ASSERT_TRUE(IsAddrOneOf(a234, { wp123, a234 }));
+  ASSERT_TRUE(IsAddrOneOf(a234, { wp123, wp234, a234 }));
+
+  const auto wpn = Sockaddr::Wildcard();
+  auto wp0 = Sockaddr::Wildcard();
+  wp0.set_port(0);
+  // Sanity check.
+  ASSERT_EQ(wpn, wp0);
+
+  // Wildcard with no port number set (i.e. port number 0) matches any address.
+  ASSERT_TRUE(IsAddrOneOf(a123, { wpn }));
+  ASSERT_TRUE(IsAddrOneOf(a234, { wpn }));
+  ASSERT_TRUE(IsAddrOneOf(a123, { wp234, wpn }));
+  ASSERT_TRUE(IsAddrOneOf(a234, { wp123, wpn }));
+}
+
 } // namespace kudu
diff --git a/src/kudu/util/net/net_util.cc b/src/kudu/util/net/net_util.cc
index 54f577e..496cf36 100644
--- a/src/kudu/util/net/net_util.cc
+++ b/src/kudu/util/net/net_util.cc
@@ -19,13 +19,15 @@
 
 #include <arpa/inet.h>
 #include <ifaddrs.h>
-#include <limits.h>
 #include <netdb.h>
 #include <netinet/in.h>
 #include <sys/socket.h>
 #include <unistd.h>
+// IWYU pragma: no_include <bits/local_lim.h>
 
+#include <algorithm>
 #include <cerrno>
+#include <climits>  // IWYU pragma: keep
 #include <cstring>
 #include <functional>
 #include <memory>
@@ -524,6 +526,33 @@
   return Status::OK();
 }
 
+bool IsAddrOneOf(const Sockaddr& addr, const vector<Sockaddr>& ref_addresses) {
+  DCHECK(addr.is_ip());
+  DCHECK(!addr.IsWildcard());
+  DCHECK_NE(0, addr.port());
+  const bool have_match = std::any_of(
+      ref_addresses.begin(),
+      ref_addresses.end(),
+      [&addr](const Sockaddr& s) {
+        DCHECK(s.is_ip());
+        const bool is_same_or_wildcard_port = s.port() == addr.port() ||
+            s.port() == 0;
+        if (s.IsWildcard()) {
+          return is_same_or_wildcard_port;
+        }
+        const auto& lhs = s.ipv4_addr().sin_addr;
+        const auto& rhs = addr.ipv4_addr().sin_addr;
+        return is_same_or_wildcard_port &&
+            memcmp(&lhs.s_addr, &rhs.s_addr, sizeof(decltype(lhs))) == 0;
+  });
+  VLOG(2) << Substitute("found IP address match for $0 among $1",
+                        addr.ToString(),
+                        JoinMapped(ref_addresses, [](const Sockaddr& addr) {
+                          return addr.ToString();
+                        }, ","));
+  return have_match;
+}
+
 Status HostPortsFromAddrs(const vector<Sockaddr>& addrs, vector<HostPort>* hps) {
   DCHECK(!addrs.empty());
   for (const auto& addr : addrs) {
diff --git a/src/kudu/util/net/net_util.h b/src/kudu/util/net/net_util.h
index ff756d7..5115afc 100644
--- a/src/kudu/util/net/net_util.h
+++ b/src/kudu/util/net/net_util.h
@@ -190,6 +190,14 @@
 // list and logs a message in verbose mode.
 Status SockaddrFromHostPort(const HostPort& host_port, Sockaddr* addr);
 
+// Returns true if the specified address 'addr' matches at least one of the
+// addresses in 'ref_addresses'. The 'addr' itself must not be a wildcard, but
+// any of the addresses in 'ref_addresses' may: both the address (i.e. 0.0.0.0)
+// and port wildcard (i.e. port 0) are supported. All the addresses must be in
+// the IP notation, not UNIX socket or anything else. Only IPv4 addresses are
+// supported since Sockaddr doesn't support IPv6 yet.
+bool IsAddrOneOf(const Sockaddr& addr, const std::vector<Sockaddr>& ref_addresses);
+
 // Converts the given list of Sockaddrs into a list of HostPorts that can be
 // accessed from other machines, i.e. wildcards are replaced with the FQDN, the
 // --host_for_tests gflag is honored with the expectation that 'addrs' is the