#include <array>
#include <cstdint>
#include <cstring>
#include <string>
#include <string_view>
#include "ip_utils.hpp"
#if defined(_WIN32) || defined(__MINGW32__)
# if defined(__MINGW32__) || defined(_WIN32_WINNT)
# undef _WIN32_WINNT
# define _WIN32_WINNT 0x600
# endif
# include <ws2tcpip.h>
#else
# include <arpa/inet.h>
# include <netinet/in.h>
# include <sys/socket.h>
#endif
namespace ddwaf {
namespace {
template <int af, std::size_t N> bool parse_ip_internal(std::array<char, N> ip, ipaddr &out)
{
const int ret = inet_pton(af, ip.data(), &out.data);
if (ret != 1) {
return false;
}
if constexpr (af == AF_INET) {
out.type = ipaddr::address_family::ipv4;
out.mask = 32;
} else if constexpr (af == AF_INET6) {
out.type = ipaddr::address_family::ipv6;
out.mask = 128;
}
return true;
}
template <std::size_t N> bool parse_ipv6_internal(std::array<char, N> ip, ipaddr &out)
{
static_assert(N >= INET6_ADDRSTRLEN);
return parse_ip_internal<AF_INET6, N>(ip, out);
}
template <std::size_t N> bool parse_ipv4_internal(std::array<char, N> ip, ipaddr &out)
{
static_assert(N >= INET_ADDRSTRLEN);
return parse_ip_internal<AF_INET, N>(ip, out);
}
}
bool parse_ip(std::string_view ip, ipaddr &out)
{
if (ip.size() >= INET6_ADDRSTRLEN) {
return false;
}
std::array<char, INET6_ADDRSTRLEN> ip_cstr{0};
memcpy(ip_cstr.data(), ip.data(), ip.size());
if (ip.size() > INET_ADDRSTRLEN || !parse_ipv4_internal(ip_cstr, out)) {
return parse_ipv6_internal(ip_cstr, out);
}
return true;
}
bool parse_ipv4(std::string_view ip, ipaddr &out)
{
if (ip.size() >= INET_ADDRSTRLEN) {
return false;
}
std::array<char, INET_ADDRSTRLEN> ip_cstr{0};
memcpy(ip_cstr.data(), ip.data(), ip.size());
return parse_ipv4_internal(ip_cstr, out);
}
bool parse_ipv6(std::string_view ip, ipaddr &out)
{
if (ip.size() >= INET6_ADDRSTRLEN) {
return false;
}
std::array<char, INET6_ADDRSTRLEN> ip_cstr{0};
memcpy(ip_cstr.data(), ip.data(), ip.size());
return parse_ipv6_internal(ip_cstr, out);
}
void ipv4_to_ipv6(ipaddr &out)
{
if (out.type != ipaddr::address_family::ipv4) {
return;
}
out.data[10] = 0xff;
out.data[11] = 0xff;
out.data[12] = out.data[0];
out.data[13] = out.data[1];
out.data[14] = out.data[2];
out.data[15] = out.data[3];
memset(&out.data[0], 0, 10);
out.mask += 96;
out.type = ipaddr::address_family::ipv4_mapped_ipv6;
}
bool parse_cidr(std::string_view str, ipaddr &out)
{
auto slash_idx = str.find('/');
if (slash_idx != std::string_view::npos) {
if (slash_idx >= INET6_ADDRSTRLEN) {
return false;
}
auto mask_len = str.size() - slash_idx - 1;
if (mask_len == 0 || mask_len > 3) {
return false;
}
if (!parse_ip(str.substr(0, slash_idx), out)) {
return false;
}
int mask;
try {
mask = std::stoi(std::string(str.substr(slash_idx + 1, mask_len)));
} catch (...) {
return false;
}
if ((out.type == ipaddr::address_family::ipv4 && mask > 32) ||
(out.type == ipaddr::address_family::ipv6 && mask > 128) || mask < 0) {
return false;
}
out.mask = static_cast<uint8_t>(mask);
} else {
if (!parse_ip(str, out)) {
return false;
}
}
ipv4_to_ipv6(out);
uint8_t byte_index = out.mask / 8;
if (byte_index < 16) {
out.data[byte_index] &= ~(0xff >> (out.mask & 0x7));
while (++byte_index < 16) { out.data[byte_index] = 0; }
}
return true;
}
}