Commit 9bf26065 authored by Petr Lapukhov's avatar Petr Lapukhov Committed by Facebook Github Bot

add tryCreateNetwork()

Summary: Add non-throwing version of createNetwork(), and rework existing code to throw exceptions based on error codes returned by non-throwing version.

Reviewed By: yfeldblum

Differential Revision: D6705425

fbshipit-source-id: 268ff64c36e7cceeea3463248d18b7b2cb81390e
parent 61cdb3dc
...@@ -13,7 +13,6 @@ ...@@ -13,7 +13,6 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
#include <folly/IPAddress.h> #include <folly/IPAddress.h>
#include <limits> #include <limits>
...@@ -68,43 +67,108 @@ IPAddressV6 IPAddress::createIPv6(const IPAddress& addr) { ...@@ -68,43 +67,108 @@ IPAddressV6 IPAddress::createIPv6(const IPAddress& addr) {
} }
} }
namespace {
vector<string> splitIpSlashCidr(StringPiece ipSlashCidr) {
vector<string> vec;
split("/", ipSlashCidr, vec);
return vec;
}
} // namespace
// public static // public static
CIDRNetwork IPAddress::createNetwork( CIDRNetwork IPAddress::createNetwork(
StringPiece ipSlashCidr, StringPiece ipSlashCidr,
int defaultCidr, /* = -1 */ int defaultCidr, /* = -1 */
bool applyMask /* = true */) { bool applyMask /* = true */) {
if (defaultCidr > std::numeric_limits<uint8_t>::max()) { auto const ret =
IPAddress::tryCreateNetwork(ipSlashCidr, defaultCidr, applyMask);
if (ret.hasValue()) {
return ret.value();
}
if (ret.error() == CIDRNetworkError::INVALID_DEFAULT_CIDR) {
throw std::range_error("defaultCidr must be <= UINT8_MAX"); throw std::range_error("defaultCidr must be <= UINT8_MAX");
} }
vector<string> vec;
split("/", ipSlashCidr, vec);
vector<string>::size_type elemCount = vec.size();
if (elemCount == 0 || // weird invalid string if (ret.error() == CIDRNetworkError::INVALID_IP_SLASH_CIDR) {
elemCount > 2) { // invalid string (IP/CIDR/extras)
throw IPAddressFormatException(sformat( throw IPAddressFormatException(sformat(
"Invalid ipSlashCidr specified. Expected IP/CIDR format, got '{}'", "Invalid ipSlashCidr specified. Expected IP/CIDR format, got '{}'",
ipSlashCidr)); ipSlashCidr));
} }
IPAddress subnet(vec.at(0));
auto cidr =
uint8_t((defaultCidr > -1) ? defaultCidr : (subnet.isV4() ? 32 : 128));
if (elemCount == 2) { // Handler the remaining error cases. We re-parse the ip/mask pair
try { // to make error messages more meaningful
cidr = to<uint8_t>(vec.at(1)); auto const vec = splitIpSlashCidr(ipSlashCidr);
} catch (...) {
switch (ret.error()) {
case CIDRNetworkError::INVALID_IP:
CHECK_GE(vec.size(), 1);
throw IPAddressFormatException(
sformat("Invalid IP address {}", vec.at(0)));
case CIDRNetworkError::INVALID_CIDR:
CHECK_GE(vec.size(), 2);
throw IPAddressFormatException( throw IPAddressFormatException(
sformat("Mask value '{}' not a valid mask", vec.at(1))); sformat("Mask value '{}' not a valid mask", vec.at(1)));
case CIDRNetworkError::CIDR_MISMATCH: {
auto const subnet = IPAddress::tryFromString(vec.at(0)).value();
auto cidr = static_cast<uint8_t>(
(defaultCidr > -1) ? defaultCidr : (subnet.isV4() ? 32 : 128));
throw IPAddressFormatException(sformat(
"CIDR value '{}' is > network bit count '{}'",
vec.size() == 2 ? vec.at(1) : to<string>(cidr),
subnet.bitCount()));
} }
default:
// unreachable
break;
} }
if (cidr > subnet.bitCount()) {
throw IPAddressFormatException(sformat( CHECK(0);
"CIDR value '{}' is > network bit count '{}'",
cidr, return CIDRNetwork{};
subnet.bitCount())); }
// public static
Expected<CIDRNetwork, CIDRNetworkError> IPAddress::tryCreateNetwork(
StringPiece ipSlashCidr,
int defaultCidr,
bool applyMask) {
if (defaultCidr > std::numeric_limits<uint8_t>::max()) {
return makeUnexpected(CIDRNetworkError::INVALID_DEFAULT_CIDR);
}
auto const vec = splitIpSlashCidr(ipSlashCidr);
auto const elemCount = vec.size();
if (elemCount == 0 || // weird invalid string
elemCount > 2) { // invalid string (IP/CIDR/extras)
return makeUnexpected(CIDRNetworkError::INVALID_IP_SLASH_CIDR);
} }
return std::make_pair(applyMask ? subnet.mask(cidr) : subnet, cidr);
auto const subnet = IPAddress::tryFromString(vec.at(0));
if (subnet.hasError()) {
return makeUnexpected(CIDRNetworkError::INVALID_IP);
}
auto cidr = static_cast<uint8_t>(
(defaultCidr > -1) ? defaultCidr : (subnet.value().isV4() ? 32 : 128));
if (elemCount == 2) {
auto const maybeCidr = tryTo<uint8_t>(vec.at(1));
if (maybeCidr.hasError()) {
return makeUnexpected(CIDRNetworkError::INVALID_CIDR);
}
cidr = maybeCidr.value();
}
if (cidr > subnet.value().bitCount()) {
return makeUnexpected(CIDRNetworkError::CIDR_MISMATCH);
}
return std::make_pair(
applyMask ? subnet.value().mask(cidr) : subnet.value(), cidr);
} }
// public static // public static
......
...@@ -91,6 +91,19 @@ class IPAddress { ...@@ -91,6 +91,19 @@ class IPAddress {
* is -1, will use /32 for IPv4 and /128 for IPv6) * is -1, will use /32 for IPv4 and /128 for IPv6)
* @param [in] mask apply mask on the address or not, * @param [in] mask apply mask on the address or not,
* e.g. 192.168.13.46/24 => 192.168.13.0/24 * e.g. 192.168.13.46/24 => 192.168.13.0/24
* @return either pair with IPAddress network and uint8_t mask or
* CIDRNetworkError
*/
static Expected<CIDRNetwork, CIDRNetworkError> tryCreateNetwork(
StringPiece ipSlashCidr,
int defaultCidr = -1,
bool mask = true);
/**
* Create a network and mask from a CIDR formatted address string.
* Same as tryCreateNetwork() but throws IPAddressFormatException on error.
* The implementation calls tryCreateNetwork(...) underneath
*
* @throws IPAddressFormatException if invalid address * @throws IPAddressFormatException if invalid address
* @return pair with IPAddress network and uint8_t mask * @return pair with IPAddress network and uint8_t mask
*/ */
......
...@@ -29,6 +29,17 @@ namespace folly { ...@@ -29,6 +29,17 @@ namespace folly {
*/ */
enum class IPAddressFormatError { INVALID_IP, UNSUPPORTED_ADDR_FAMILY }; enum class IPAddressFormatError { INVALID_IP, UNSUPPORTED_ADDR_FAMILY };
/**
* Wraps error from parsing IP/MASK string
*/
enum class CIDRNetworkError {
INVALID_DEFAULT_CIDR,
INVALID_IP_SLASH_CIDR,
INVALID_IP,
INVALID_CIDR,
CIDR_MISMATCH,
};
/** /**
* Exception for invalid IP addresses. * Exception for invalid IP addresses.
*/ */
......
...@@ -13,7 +13,6 @@ ...@@ -13,7 +13,6 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
#include <sys/types.h> #include <sys/types.h>
#include <string> #include <string>
...@@ -281,10 +280,10 @@ TEST(IPAddress, InvalidAddressFamilyExceptions) { ...@@ -281,10 +280,10 @@ TEST(IPAddress, InvalidAddressFamilyExceptions) {
} }
} }
TEST(IPAddress, CreateNetwork) { TEST(IPAddress, TryCreateNetwork) {
// test valid IPv4 network // test valid IPv4 network
{ {
auto net = IPAddress::createNetwork("192.168.0.1/24"); auto net = IPAddress::tryCreateNetwork("192.168.0.1/24").value();
ASSERT_TRUE(net.first.isV4()); ASSERT_TRUE(net.first.isV4());
EXPECT_EQ("192.168.0.0", net.first.str()); EXPECT_EQ("192.168.0.0", net.first.str());
EXPECT_EQ(24, net.second); EXPECT_EQ(24, net.second);
...@@ -292,7 +291,7 @@ TEST(IPAddress, CreateNetwork) { ...@@ -292,7 +291,7 @@ TEST(IPAddress, CreateNetwork) {
} }
// test valid IPv4 network without applying mask // test valid IPv4 network without applying mask
{ {
auto net = IPAddress::createNetwork("192.168.0.1/24", -1, false); auto net = IPAddress::tryCreateNetwork("192.168.0.1/24", -1, false).value();
ASSERT_TRUE(net.first.isV4()); ASSERT_TRUE(net.first.isV4());
EXPECT_EQ("192.168.0.1", net.first.str()); EXPECT_EQ("192.168.0.1", net.first.str());
EXPECT_EQ(24, net.second); EXPECT_EQ(24, net.second);
...@@ -300,7 +299,7 @@ TEST(IPAddress, CreateNetwork) { ...@@ -300,7 +299,7 @@ TEST(IPAddress, CreateNetwork) {
} }
// test valid IPv6 network // test valid IPv6 network
{ {
auto net = IPAddress::createNetwork("1999::1/24"); auto net = IPAddress::tryCreateNetwork("1999::1/24").value();
ASSERT_TRUE(net.first.isV6()); ASSERT_TRUE(net.first.isV6());
EXPECT_EQ("1999::", net.first.str()); EXPECT_EQ("1999::", net.first.str());
EXPECT_EQ(24, net.second); EXPECT_EQ(24, net.second);
...@@ -308,20 +307,30 @@ TEST(IPAddress, CreateNetwork) { ...@@ -308,20 +307,30 @@ TEST(IPAddress, CreateNetwork) {
} }
// test valid IPv6 network without applying mask // test valid IPv6 network without applying mask
{ {
auto net = IPAddress::createNetwork("1999::1/24", -1, false); auto net = IPAddress::tryCreateNetwork("1999::1/24", -1, false).value();
ASSERT_TRUE(net.first.isV6()); ASSERT_TRUE(net.first.isV6());
EXPECT_EQ("1999::1", net.first.str()); EXPECT_EQ("1999::1", net.first.str());
EXPECT_EQ(24, net.second); EXPECT_EQ(24, net.second);
EXPECT_EQ("1999::1/24", IPAddress::networkToString(net)); EXPECT_EQ("1999::1/24", IPAddress::networkToString(net));
} }
// test invalid default CIDR
EXPECT_EQ(
CIDRNetworkError::INVALID_DEFAULT_CIDR,
IPAddress::tryCreateNetwork("192.168.1.1", 300).error());
// test empty string // test empty string
EXPECT_THROW(IPAddress::createNetwork(""), IPAddressFormatException); EXPECT_EQ(
CIDRNetworkError::INVALID_IP, IPAddress::tryCreateNetwork("").error());
// test multi slash string // test multi slash string
EXPECT_THROW( EXPECT_EQ(
IPAddress::createNetwork("192.168.0.1/24/36"), IPAddressFormatException); CIDRNetworkError::INVALID_IP_SLASH_CIDR,
IPAddress::tryCreateNetwork("192.168.0.1/24/36").error());
// test no slash string with default IPv4 // test no slash string with default IPv4
{ {
auto net = IPAddress::createNetwork("192.168.0.1"); auto net = IPAddress::tryCreateNetwork("192.168.0.1").value();
ASSERT_TRUE(net.first.isV4()); ASSERT_TRUE(net.first.isV4());
EXPECT_EQ("192.168.0.1", net.first.str()); EXPECT_EQ("192.168.0.1", net.first.str());
EXPECT_EQ(32, net.second); // auto-detected EXPECT_EQ(32, net.second); // auto-detected
...@@ -332,12 +341,27 @@ TEST(IPAddress, CreateNetwork) { ...@@ -332,12 +341,27 @@ TEST(IPAddress, CreateNetwork) {
} }
// test no slash string with default IPv6 // test no slash string with default IPv6
{ {
auto net = IPAddress::createNetwork("1999::1"); auto net = IPAddress::tryCreateNetwork("1999::1").value();
ASSERT_TRUE(net.first.isV6()); ASSERT_TRUE(net.first.isV6());
EXPECT_EQ("1999::1", net.first.str()); EXPECT_EQ("1999::1", net.first.str());
EXPECT_EQ(128, net.second); EXPECT_EQ(128, net.second);
} }
// test no slash string with invalid default // test no slash string with invalid default
EXPECT_EQ(
CIDRNetworkError::CIDR_MISMATCH,
IPAddress::tryCreateNetwork("192.168.0.1", 33).error());
}
// test that throwing version actually throws
TEST(IPAddress, CreateNetworkExceptions) {
// test invalid default CIDR
EXPECT_THROW(IPAddress::createNetwork("192.168.0.1", 300), std::range_error);
// test empty string
EXPECT_THROW(IPAddress::createNetwork(""), IPAddressFormatException);
// test multi slash string
EXPECT_THROW(
IPAddress::createNetwork("192.168.0.1/24/36"), IPAddressFormatException);
// test no slash string with invalid default
EXPECT_THROW( EXPECT_THROW(
IPAddress::createNetwork("192.168.0.1", 33), IPAddressFormatException); IPAddress::createNetwork("192.168.0.1", 33), IPAddressFormatException);
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment