Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial IPv6 classes #1901

Merged
merged 9 commits into from
Oct 29, 2023
2 changes: 1 addition & 1 deletion common/network/IPV4Address.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
* IPV4Address.cpp
* A IPV4 address
* An IPV4 address
* Copyright (C) 2011 Simon Newton
*/

Expand Down
170 changes: 170 additions & 0 deletions common/network/IPV6Address.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
/*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
* IPV6Address.cpp
* An IPV6 address
* Copyright (C) 2023 Peter Newman
*/

#if HAVE_CONFIG_H
#include <config.h>
#endif // HAVE_CONFIG_H

#ifdef HAVE_SYS_SOCKET_H
#include <sys/socket.h> // Required by FreeBSD
#endif // HAVE_SYS_SOCKET_H
#ifdef HAVE_ARPA_INET_H
#include <arpa/inet.h>
#endif // HAVE_ARPA_INET_H
#ifdef HAVE_NETINET_IN_H
#include <netinet/in.h> // Required by FreeBSD
#endif // HAVE_NETINET_IN_H

#include <assert.h>
#include <math.h>
#include <stdint.h>
#include <limits>
#include <string>

#include "common/network/NetworkUtilsInternal.h"
#include "ola/Logging.h"
#include "ola/network/IPV6Address.h"
#include "ola/network/NetworkUtils.h"

namespace ola {
namespace network {

using std::string;

IPV6Address::IPV6Address(const uint8_t *address) {
// TODO(Peter): Deal with any network byte order conversion?
memcpy(&m_address.s6_addr[0], address, sizeof (struct in6_addr));
}

bool IPV6Address::operator<(const IPV6Address &other) const {
// TODO(Peter): Deal with any network byte order conversion?
peternewman marked this conversation as resolved.
Show resolved Hide resolved
return (memcmp(&m_address.s6_addr[0],
&other.m_address.s6_addr[0],
sizeof (struct in6_addr)) < 0);
}

bool IPV6Address::operator>(const IPV6Address &other) const {
// TODO(Peter): Deal with any network byte order conversion?
peternewman marked this conversation as resolved.
Show resolved Hide resolved
return (memcmp(&m_address.s6_addr[0],
&other.m_address.s6_addr[0],
sizeof (struct in6_addr)) > 0);
}

bool IPV6StringToAddress(const string &address, struct in6_addr *addr) {
bool ok;
//// TODO(Peter): This currently allows some rather quirky values as per
//// inet_pton, we may want to restrict that in future to match IPV6Validator
//// if that deviates

if (address.empty()) {
// Don't bother trying to extract an address if we weren't given one
return false;
}

#ifdef HAVE_INET_PTON
ok = (1 == inet_pton(AF_INET6, address.data(), addr));
#else
OLA_FATAL << "Failed to convert string to address, inet_pton unavailable";
return false;
#endif // HAVE_INET_PTON

if (!ok) {
OLA_WARN << "Could not convert address " << address;
}
return ok;
}

bool IPV6Address::IsWildcard() const {
return IN6_IS_ADDR_UNSPECIFIED(&m_address);
}

string IPV6Address::ToString() const {
struct in6_addr addr;
addr = m_address;
#ifdef HAVE_INET_NTOP
char str[INET6_ADDRSTRLEN];
if (inet_ntop(AF_INET6, &addr, str, INET6_ADDRSTRLEN) == NULL) {
OLA_FATAL << "Failed to convert address to string using inet_ntop";
return NULL;
}
return str;
#else
OLA_FATAL << "Failed to convert address to string, inet_ntop unavailable";
return NULL;
#endif // HAVE_INET_NTOP
}

IPV6Address* IPV6Address::FromString(const string &address) {
struct in6_addr addr;
if (!IPV6StringToAddress(address, &addr)) {
return NULL;
}

return new IPV6Address(addr);
}

bool IPV6Address::FromString(const string &address, IPV6Address *target) {
struct in6_addr addr;
if (!IPV6StringToAddress(address, &addr)) {
return false;
}
*target = IPV6Address(addr);
return true;
}

IPV6Address IPV6Address::FromStringOrDie(const string &address) {
struct in6_addr addr;
assert(IPV6StringToAddress(address, &addr));
return IPV6Address(addr);
}

/*bool IPV6Address::ToCIDRMask(IPV6Address address, uint8_t *mask) {
uint32_t netmask = NetworkToHost(address.AsInt());
uint8_t bits = 0;
bool seen_one = false;
for (uint8_t i = 0; i < std::numeric_limits<uint32_t>::digits; i++) {
if (netmask & 1) {
bits++;
seen_one = true;
} else {
if (seen_one) {
return false;
}
}
netmask = netmask >> 1;
}
*mask = bits;
return true;
}*/

IPV6Address IPV6Address::WildCard() {
in6_addr wildCard = IN6ADDR_ANY_INIT;
// TODO(Peter): Deal with any host to network conversion...
return IPV6Address(wildCard);
}

IPV6Address IPV6Address::Loopback() {
in6_addr loopback = IN6ADDR_LOOPBACK_INIT;
// TODO(Peter): Deal with any host to network conversion...
// return IPV6Address(HostToNetwork(IN6ADDR_LOOPBACK_INIT));
return IPV6Address(loopback);
}
} // namespace network
} // namespace ola
209 changes: 209 additions & 0 deletions common/network/IPV6AddressTest.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
/*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
* IPV6AddressTest.cpp
* Test fixture for the IPV6Address class
* Copyright (C) 2023 Peter Newman
*/

#include <cppunit/extensions/HelperMacros.h>

#include <stdint.h>
#include <algorithm>
#include <iostream>
#include <memory>
#include <string>
#include <vector>
#include "common/network/NetworkUtilsInternal.h"
#include "ola/network/IPV6Address.h"
#include "ola/network/NetworkUtils.h"
#include "ola/testing/TestUtils.h"


using ola::network::IPV6Address;
using ola::network::HostToNetwork;
using std::auto_ptr;
using std::string;
using std::vector;

class IPV6AddressTest: public CppUnit::TestFixture {
CPPUNIT_TEST_SUITE(IPV6AddressTest);
CPPUNIT_TEST(testIPV6Address);
CPPUNIT_TEST(testWildcard);
CPPUNIT_TEST(testLoopback);
CPPUNIT_TEST_SUITE_END();

public:
void testIPV6Address();
void testWildcard();
// TODO(Peter): Test the all-nodes link-local multicast group if we add it
void testLoopback();
};

CPPUNIT_TEST_SUITE_REGISTRATION(IPV6AddressTest);


/*
* Test the IPV6 Address class works
*/
void IPV6AddressTest::testIPV6Address() {
IPV6Address wildcard_address;
OLA_ASSERT_EQ(string("::"), wildcard_address.ToString());
// OLA_ASSERT_EQ(static_cast<in_addr_t>(0), wildcard_address.AsInt());
OLA_ASSERT_TRUE(wildcard_address.IsWildcard());

IPV6Address address1 = IPV6Address::FromStringOrDie("::ffff:c0a8:101");
// int ip_as_int = address1.AsInt();
OLA_ASSERT_NE(wildcard_address, address1);
// OLA_ASSERT_NE(HostToNetwork(0xc0a811), ip_as_int);
// OLA_ASSERT_EQ(HostToNetwork(0xc0a80101), static_cast<uint32_t>(ip_as_int));
OLA_ASSERT_EQ(string("::ffff:192.168.1.1"), address1.ToString());

IPV6Address address2 = IPV6Address::FromStringOrDie(
"2001:db8:1234:5678:90ab:cdef:feed:face");
// int ip_as_int = address2.AsInt();
OLA_ASSERT_NE(wildcard_address, address2);
// OLA_ASSERT_NE(HostToNetwork(0xc0a811), ip_as_int);
// OLA_ASSERT_EQ(HostToNetwork(0xc0a80101), static_cast<uint32_t>(ip_as_int));

const uint8_t big_endian_address_data[] =
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 10, 0, 0, 1};
IPV6Address binary_address(big_endian_address_data);
OLA_ASSERT_EQ(string("::ffff:10.0.0.1"), binary_address.ToString());

// Test Get()
uint8_t address_data[] = {32, 1, 13, 184, 18, 52, 86, 120,
144, 171, 205, 239, 254, 237, 250, 206};
uint8_t addr[IPV6Address::LENGTH];
address2.Get(addr);
OLA_ASSERT_DATA_EQUALS(addr,
sizeof(addr),
reinterpret_cast<uint8_t*>(&address_data),
sizeof(address_data));

// test copy and assignment
IPV6Address address3(address1);
OLA_ASSERT_EQ(address1, address3);
IPV6Address address4 = address1;
OLA_ASSERT_EQ(address1, address4);

// test stringification
OLA_ASSERT_EQ(string("::ffff:192.168.1.1"), address1.ToString());
std::ostringstream str;
str << address1;
OLA_ASSERT_EQ(string("::ffff:192.168.1.1"), str.str());

// test from string
auto_ptr<IPV6Address> string_address(
IPV6Address::FromString("::ffff:10.0.0.1"));
OLA_ASSERT_NOT_NULL(string_address.get());
OLA_ASSERT_EQ(string("::ffff:10.0.0.1"), string_address->ToString());

auto_ptr<IPV6Address> string_address2(IPV6Address::FromString("foo"));
OLA_ASSERT_NULL(string_address2.get());

// and the second form
IPV6Address string_address3;
OLA_ASSERT_TRUE(IPV6Address::FromString(
"::ffff:172.16.4.1", &string_address3));
OLA_ASSERT_EQ(string("::ffff:172.16.4.1"), string_address3.ToString());

IPV6Address string_address4;
// Add the leading zero to the second group
OLA_ASSERT_TRUE(IPV6Address::FromString(
"2001:0db8:1234:5678:90ab:cdef:feed:face", &string_address4));
// Confirm it's not rendered when we convert to a string
OLA_ASSERT_EQ(string("2001:db8:1234:5678:90ab:cdef:feed:face"),
string_address4.ToString());

IPV6Address string_address5;
OLA_ASSERT_TRUE(IPV6Address::FromString(
"2001:db8:dead:beef:dead:beef:dead:beef", &string_address5));
OLA_ASSERT_EQ(string("2001:db8:dead:beef:dead:beef:dead:beef"),
string_address5.ToString());

IPV6Address string_address6;
OLA_ASSERT_FALSE(IPV6Address::FromString("", &string_address6));

// make sure sorting works
vector<IPV6Address> addresses;
addresses.push_back(address1);
addresses.push_back(*string_address);
addresses.push_back(string_address3);
addresses.push_back(string_address4);
addresses.push_back(string_address5);
std::sort(addresses.begin(), addresses.end());

// The comparisons take into account network byte order automagically.
OLA_ASSERT_EQ(string("::ffff:10.0.0.1"), addresses[0].ToString());
OLA_ASSERT_EQ(string("::ffff:172.16.4.1"), addresses[1].ToString());
OLA_ASSERT_EQ(string("::ffff:192.168.1.1"), addresses[2].ToString());
OLA_ASSERT_EQ(string("2001:db8:1234:5678:90ab:cdef:feed:face"),
addresses[3].ToString());
OLA_ASSERT_EQ(string("2001:db8:dead:beef:dead:beef:dead:beef"),
addresses[4].ToString());

/* uint8_t mask = 255; // UINT8_MAX;
OLA_ASSERT_TRUE(
IPV6Address::ToCIDRMask(IPV6Address::FromStringOrDie("0.0.0.0"), &mask));
OLA_ASSERT_EQ(0, static_cast<int>(mask));

OLA_ASSERT_TRUE(
IPV6Address::ToCIDRMask(IPV6Address::FromStringOrDie("255.0.0.0"),
&mask));
OLA_ASSERT_EQ(8, static_cast<int>(mask));

OLA_ASSERT_TRUE(
IPV6Address::ToCIDRMask(IPV6Address::FromStringOrDie("255.255.255.0"),
&mask));
OLA_ASSERT_EQ(24, static_cast<int>(mask));

OLA_ASSERT_TRUE(
IPV6Address::ToCIDRMask(IPV6Address::FromStringOrDie("255.255.255.252"),
&mask));
OLA_ASSERT_EQ(30, static_cast<int>(mask));

OLA_ASSERT_TRUE(
IPV6Address::ToCIDRMask(IPV6Address::FromStringOrDie("255.255.255.255"),
&mask));
OLA_ASSERT_EQ(32, static_cast<int>(mask));

OLA_ASSERT_FALSE(
IPV6Address::ToCIDRMask(IPV6Address::FromStringOrDie("255.0.0.255"),
&mask));*/
}


/*
* Test the wildcard address works.
*/
void IPV6AddressTest::testWildcard() {
IPV6Address wildcard_address;
OLA_ASSERT_EQ(string("::"), wildcard_address.ToString());
// OLA_ASSERT_EQ(static_cast<in_addr_t>(0), wildcard_address.AsInt());
OLA_ASSERT_TRUE(wildcard_address.IsWildcard());

IPV6Address wildcard_address2 = IPV6Address::WildCard();
OLA_ASSERT_EQ(wildcard_address, wildcard_address2);
}


/*
* Test the loopback address works.
*/
void IPV6AddressTest::testLoopback() {
IPV6Address loopback_address = IPV6Address::Loopback();
OLA_ASSERT_EQ(string("::1"), loopback_address.ToString());
}
Loading
Loading