util: don't resolve valid IP addresses when doing FaceUri canonization

refs #4095

Change-Id: Ide7835438268aa3219540d8b2ea4143355f6f8a0
diff --git a/src/util/face-uri.cpp b/src/util/face-uri.cpp
index 83ad2eb..d1a26df 100644
--- a/src/util/face-uri.cpp
+++ b/src/util/face-uri.cpp
@@ -282,24 +282,37 @@
       return;
     }
 
-    dns::AddressSelector addressSelector;
-    if (faceUri.getScheme() == m_v4Scheme) {
-      addressSelector = dns::Ipv4Only();
-    }
-    else if (faceUri.getScheme() == m_v6Scheme) {
-      addressSelector = dns::Ipv6Only();
-    }
-    else {
-      BOOST_ASSERT(faceUri.getScheme() == m_baseScheme);
-      addressSelector = dns::AnyAddress();
-    }
-
     // make a copy because caller may modify faceUri
     auto uri = make_shared<FaceUri>(faceUri);
-    dns::asyncResolve(faceUri.getHost(),
-      bind(&IpHostCanonizeProvider<Protocol>::onDnsSuccess, this, uri, onSuccess, onFailure, _1),
-      bind(&IpHostCanonizeProvider<Protocol>::onDnsFailure, this, uri, onFailure, _1),
-      io, addressSelector, timeout);
+    boost::system::error_code ec;
+    auto ipAddress = boost::asio::ip::address::from_string(faceUri.getHost(), ec);
+    if (!ec) {
+      // No need to resolve IP address if host is already an IP
+      if ((faceUri.getScheme() == m_v4Scheme && !ipAddress.is_v4()) ||
+          (faceUri.getScheme() == m_v6Scheme && !ipAddress.is_v6())) {
+        return onFailure("IPv4/v6 mismatch");
+      }
+
+      onDnsSuccess(uri, onSuccess, onFailure, ipAddress);
+    }
+    else {
+      dns::AddressSelector addressSelector;
+      if (faceUri.getScheme() == m_v4Scheme) {
+        addressSelector = dns::Ipv4Only();
+      }
+      else if (faceUri.getScheme() == m_v6Scheme) {
+        addressSelector = dns::Ipv6Only();
+      }
+      else {
+        BOOST_ASSERT(faceUri.getScheme() == m_baseScheme);
+        addressSelector = dns::AnyAddress();
+      }
+
+      dns::asyncResolve(faceUri.getHost(),
+        bind(&IpHostCanonizeProvider<Protocol>::onDnsSuccess, this, uri, onSuccess, onFailure, _1),
+        bind(&IpHostCanonizeProvider<Protocol>::onDnsFailure, this, uri, onFailure, _1),
+        io, addressSelector, timeout);
+    }
   }
 
 protected:
diff --git a/tests/unit-tests/util/face-uri.t.cpp b/tests/unit-tests/util/face-uri.t.cpp
index 0b9a61a..559a5b0 100644
--- a/tests/unit-tests/util/face-uri.t.cpp
+++ b/tests/unit-tests/util/face-uri.t.cpp
@@ -181,10 +181,13 @@
   BOOST_CHECK_EQUAL(FaceUri("udp4://192.0.2.1:6363/").isCanonical(), false);
   BOOST_CHECK_EQUAL(FaceUri("udp6://[2001:db8::1]:6363").isCanonical(), true);
   BOOST_CHECK_EQUAL(FaceUri("udp6://[2001:db8::01]:6363").isCanonical(), false);
+  BOOST_CHECK_EQUAL(FaceUri("udp://[2001:db8::1]:6363").isCanonical(), false);
   BOOST_CHECK_EQUAL(FaceUri("udp://example.net:6363").isCanonical(), false);
   BOOST_CHECK_EQUAL(FaceUri("udp4://example.net:6363").isCanonical(), false);
   BOOST_CHECK_EQUAL(FaceUri("udp6://example.net:6363").isCanonical(), false);
   BOOST_CHECK_EQUAL(FaceUri("udp4://224.0.23.170:56363").isCanonical(), true);
+  BOOST_CHECK_EQUAL(FaceUri("udp4://[2001:db8::1]:6363").isCanonical(), false);
+  BOOST_CHECK_EQUAL(FaceUri("udp6://192.0.2.1:6363").isCanonical(), false);
 }
 
 BOOST_AUTO_TEST_CASE_EXPECTED_FAILURES(CanonizeUdpV4, 1)
@@ -209,6 +212,9 @@
   addTest("udp4://224.0.23.170", true, "udp4://224.0.23.170:56363");
   addTest("udp4://all-routers.mcast.net:56363", true, "udp4://224.0.0.2:56363");
 
+  // IPv6 used with udp4 protocol - not canonical
+  addTest("udp4://[2001:db8::1]:6363", false, "");
+
   runTests();
 }
 
@@ -233,6 +239,9 @@
   addTest("udp6://[ff02::2]:56363", true, "udp6://[ff02::2]:56363");
   addTest("udp6://[ff02::2]", true, "udp6://[ff02::2]:56363");
 
+  // IPv4 used with udp6 protocol - not canonical
+  addTest("udp6://192.0.2.1:6363", false, "");
+
   runTests();
 }
 
@@ -275,10 +284,13 @@
   BOOST_CHECK_EQUAL(FaceUri("tcp4://192.0.2.1:6363/").isCanonical(), false);
   BOOST_CHECK_EQUAL(FaceUri("tcp6://[2001:db8::1]:6363").isCanonical(), true);
   BOOST_CHECK_EQUAL(FaceUri("tcp6://[2001:db8::01]:6363").isCanonical(), false);
+  BOOST_CHECK_EQUAL(FaceUri("tcp://[2001:db8::1]:6363").isCanonical(), false);
   BOOST_CHECK_EQUAL(FaceUri("tcp://example.net:6363").isCanonical(), false);
   BOOST_CHECK_EQUAL(FaceUri("tcp4://example.net:6363").isCanonical(), false);
   BOOST_CHECK_EQUAL(FaceUri("tcp6://example.net:6363").isCanonical(), false);
   BOOST_CHECK_EQUAL(FaceUri("tcp4://224.0.23.170:56363").isCanonical(), false);
+  BOOST_CHECK_EQUAL(FaceUri("tcp4://[2001:db8::1]:6363").isCanonical(), false);
+  BOOST_CHECK_EQUAL(FaceUri("tcp6://192.0.2.1:6363").isCanonical(), false);
 }
 
 BOOST_AUTO_TEST_CASE_EXPECTED_FAILURES(CanonizeTcpV4, 1)
@@ -303,6 +315,9 @@
   addTest("tcp4://224.0.23.170", false, "");
   addTest("tcp4://all-routers.mcast.net:56363", false, "");
 
+  // IPv6 used with tcp4 protocol - not canonical
+  addTest("tcp4://[2001:db8::1]:6363", false, "");
+
   runTests();
 }
 
@@ -327,6 +342,9 @@
   addTest("tcp6://[ff02::2]:56363", false, "");
   addTest("tcp6://[ff02::2]", false, "");
 
+  // IPv4 used with tcp6 protocol - not canonical
+  addTest("tcp6://192.0.2.1:6363", false, "");
+
   runTests();
 }