/*
 * Copyright (C) 2009-2016 Red Hat, Inc.
 *
 * 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, see
 * <http://www.gnu.org/licenses/>.
 *
 * Authors:
 *     Daniel Veillard <veillard@redhat.com>
 *     Laine Stump <laine@laine.org>
 *     Daniel P. Berrange <berrange@redhat.com>
 */

#include <config.h>

#include "virsocketaddr.h"
#include "virerror.h"
#include "virstring.h"
#include "viralloc.h"
#include "virbuffer.h"

#include <netdb.h>

#define VIR_FROM_THIS VIR_FROM_NONE

/*
 * Helpers to extract the IP arrays from the virSocketAddrPtr
 * That part is the less portable of the module
 */
typedef unsigned char virSocketAddrIPv4[4];
typedef virSocketAddrIPv4 *virSocketAddrIPv4Ptr;
typedef unsigned short virSocketAddrIPv6[8];
typedef virSocketAddrIPv6 *virSocketAddrIPv6Ptr;
typedef unsigned char virSocketAddrIPv6Nibbles[32];
typedef virSocketAddrIPv6Nibbles *virSocketAddrIPv6NibblesPtr;

static int
virSocketAddrGetIPv4Addr(const virSocketAddr *addr,
                         virSocketAddrIPv4Ptr tab)
{
    unsigned long val;
    size_t i;

    if (!addr || !tab || addr->data.stor.ss_family != AF_INET)
        return -1;

    val = ntohl(addr->data.inet4.sin_addr.s_addr);

    for (i = 0; i < 4; i++) {
        (*tab)[3 - i] = val & 0xFF;
        val >>= 8;
    }

    return 0;
}

static int
virSocketAddrGetIPv6Addr(const virSocketAddr *addr, virSocketAddrIPv6Ptr tab)
{
    size_t i;

    if (!addr || !tab || addr->data.stor.ss_family != AF_INET6)
        return -1;

    for (i = 0; i < 8; i++) {
        (*tab)[i] = ((addr->data.inet6.sin6_addr.s6_addr[2 * i] << 8) |
                     addr->data.inet6.sin6_addr.s6_addr[2 * i + 1]);
    }

    return 0;
}

static int
virSocketAddrGetIPv6Nibbles(const virSocketAddr *addr,
                            virSocketAddrIPv6NibblesPtr tab)
{
    size_t i;

    if (!addr || !tab || addr->data.stor.ss_family != AF_INET6)
        return -1;

    for (i = 0; i < 16; i++) {
        (*tab)[2 * i] = addr->data.inet6.sin6_addr.s6_addr[i] >> 4;
        (*tab)[2 * i + 1] = addr->data.inet6.sin6_addr.s6_addr[i] & 0xF;
    }

    return 0;
}

static int
virSocketAddrParseInternal(struct addrinfo **res,
                           const char *val,
                           int family,
                           int ai_flags,
                           bool reportError)
{
    struct addrinfo hints;
    int err;

    if (val == NULL) {
        if (reportError)
            virReportError(VIR_ERR_INVALID_ARG, "%s", _("Missing address"));
        return -1;
    }

    memset(&hints, 0, sizeof(hints));
    hints.ai_family = family;
    hints.ai_flags = ai_flags;
    if ((err = getaddrinfo(val, NULL, &hints, res)) != 0) {
        if (reportError)
            virReportError(VIR_ERR_SYSTEM_ERROR,
                           _("Cannot parse socket address '%s': %s"),
                           val, gai_strerror(err));

        return -1;
    }

    return 0;
}

/**
 * virSocketAddrParse:
 * @val: a numeric network address IPv4 or IPv6
 * @addr: where to store the return value, optional.
 * @family: address family to pass down to getaddrinfo
 *
 * Mostly a wrapper for getaddrinfo() extracting the address storage
 * from the numeric string like 1.2.3.4 or 2001:db8:85a3:0:0:8a2e:370:7334
 *
 * Returns the length of the network address or -1 in case of error.
 */
int virSocketAddrParse(virSocketAddrPtr addr, const char *val, int family)
{
    int len;
    struct addrinfo *res;

    if (virSocketAddrParseInternal(&res, val, family, AI_NUMERICHOST, true) < 0)
        return -1;

    if (res == NULL) {
        virReportError(VIR_ERR_SYSTEM_ERROR,
                       _("No socket addresses found for '%s'"),
                       val);
        return -1;
    }

    len = res->ai_addrlen;
    if (addr != NULL) {
        memcpy(&addr->data.stor, res->ai_addr, len);
        addr->len = res->ai_addrlen;
    }

    freeaddrinfo(res);
    return len;
}

/**
 * virSocketAddrParseAny:
 * @addr: where to store the return value, optional.
 * @val: a network host name or a numeric network address IPv4 or IPv6
 * @family: address family to pass down to getaddrinfo
 * @reportError: boolean to control error reporting
 *
 * Mostly a wrapper for getaddrinfo() extracting the address storage
 * from a host name like acme.example.com or a numeric string like 1.2.3.4
 * or 2001:db8:85a3:0:0:8a2e:370:7334.
 *
 * When @val is a network host name, this function may be susceptible to a
 * delay due to potentially lengthy network host address lookups.
 *
 * Returns the length of the network address or -1 in case of error.
 */
int virSocketAddrParseAny(virSocketAddrPtr addr,
                          const char *val,
                          int family,
                          bool reportError)
{
    int len;
    struct addrinfo *res;

    if (virSocketAddrParseInternal(&res, val, family, 0, reportError) < 0)
        return -1;

    if (res == NULL) {
        if (reportError) {
            virReportError(VIR_ERR_SYSTEM_ERROR,
                           _("No socket addresses found for '%s'"),
                           val);
        }
        return -1;
    }

    len = res->ai_addrlen;
    if (addr != NULL) {
        memcpy(&addr->data.stor, res->ai_addr, len);
        addr->len = res->ai_addrlen;
    }

    freeaddrinfo(res);
    return len;
}

/*
 * virSocketAddrParseIPv4:
 * @val: an IPv4 numeric address
 * @addr: the location to store the result
 *
 * Extract the address storage from an IPv4 numeric address
 *
 * Returns the length of the network address or -1 in case of error.
 */
int
virSocketAddrParseIPv4(virSocketAddrPtr addr, const char *val)
{
    return virSocketAddrParse(addr, val, AF_INET);
}

/*
 * virSocketAddrParseIPv6:
 * @val: an IPv6 numeric address
 * @addr: the location to store the result
 *
 * Extract the address storage from an IPv6 numeric address
 *
 * Returns the length of the network address or -1 in case of error.
 */
int
virSocketAddrParseIPv6(virSocketAddrPtr addr, const char *val)
{
    return virSocketAddrParse(addr, val, AF_INET6);
}

/*
 * virSocketAddrSetIPv4AddrNetOrder:
 * @addr: the location to store the result
 * @val: the 32bit integer in network byte order representing the IPv4 address
 *
 * Set the IPv4 address given an integer in network order. This function does not
 * touch any previously set port.
 */
void
virSocketAddrSetIPv4AddrNetOrder(virSocketAddrPtr addr, uint32_t val)
{
    addr->data.stor.ss_family = AF_INET;
    addr->data.inet4.sin_addr.s_addr = val;
    addr->len = sizeof(struct sockaddr_in);
}

/*
 * virSocketAddrSetIPv4Addr:
 * @addr: the location to store the result
 * @val: the 32bit integer in host byte order representing the IPv4 address
 *
 * Set the IPv4 address given an integer in host order. This function does not
 * touch any previously set port.
 */
void
virSocketAddrSetIPv4Addr(virSocketAddrPtr addr, uint32_t val)
{
    virSocketAddrSetIPv4AddrNetOrder(addr, htonl(val));
}

/*
 * virSocketAddrSetIPv6AddrNetOrder:
 * @addr: the location to store the result
 * @val: the 128bit integer in network byte order representing the IPv6 address
 *
 * Set the IPv6 address given an integer in network order. This function does not
 * touch any previously set port.
 */
void virSocketAddrSetIPv6AddrNetOrder(virSocketAddrPtr addr, uint32_t val[4])
{
    addr->data.stor.ss_family = AF_INET6;
    memcpy(addr->data.inet6.sin6_addr.s6_addr, val, 4 * sizeof(*val));
    addr->len = sizeof(struct sockaddr_in6);
}

/*
 * virSocketAddrSetIPv6Addr:
 * @addr: the location to store the result
 * @val: the 128bit integer in host byte order representing the IPv6 address
 *
 * Set the IPv6 address given an integer in host order. This function does not
 * touch any previously set port.
 */
void virSocketAddrSetIPv6Addr(virSocketAddrPtr addr, uint32_t val[4])
{
    size_t i = 0;
    uint32_t host_val[4];

    for (i = 0; i < 4; i++)
        host_val[i] = htonl(val[i]);

    virSocketAddrSetIPv6AddrNetOrder(addr, host_val);
}

/*
 * virSocketAddrEqual:
 * @s1: the location of the one IP address
 * @s2: the location of the other IP address
 *
 * Compare two IP addresses for equality. Two addresses are equal
 * if their IP addresses and ports are equal.
 */
bool
virSocketAddrEqual(const virSocketAddr *s1, const virSocketAddr *s2)
{
    if (s1->data.stor.ss_family != s2->data.stor.ss_family)
        return false;

    switch (s1->data.stor.ss_family) {
    case AF_INET:
        return (memcmp(&s1->data.inet4.sin_addr.s_addr,
                       &s2->data.inet4.sin_addr.s_addr,
                       sizeof(s1->data.inet4.sin_addr.s_addr)) == 0 &&
                s1->data.inet4.sin_port == s2->data.inet4.sin_port);
    case AF_INET6:
        return (memcmp(&s1->data.inet6.sin6_addr.s6_addr,
                       &s2->data.inet6.sin6_addr.s6_addr,
                       sizeof(s1->data.inet6.sin6_addr.s6_addr)) == 0 &&
                s1->data.inet6.sin6_port == s2->data.inet6.sin6_port);
    }
    return false;
}

/*
 * virSocketAddrIsPrivate:
 * @s: the location of the IP address
 *
 * Return true if this address is in its family's defined
 * "private/local" address space. For IPv4, private addresses are in
 * the range of 192.168.0.0/16, 172.16.0.0/12, or 10.0.0.0/8.  For
 * IPv6, local addresses are in the range of FC00::/7 or FEC0::/10
 * (that last one is deprecated, but still in use).
 *
 * See RFC1918, RFC3484, and RFC4193 for details.
 */
bool
virSocketAddrIsPrivate(const virSocketAddr *addr)
{
    unsigned long val;

    switch (addr->data.stor.ss_family) {
    case AF_INET:
       val = ntohl(addr->data.inet4.sin_addr.s_addr);

       return ((val & 0xFFFF0000) == ((192UL << 24) + (168 << 16)) ||
               (val & 0xFFF00000) == ((172UL << 24) + (16  << 16)) ||
               (val & 0xFF000000) == ((10UL  << 24)));

    case AF_INET6:
        return ((addr->data.inet6.sin6_addr.s6_addr[0] & 0xFE) == 0xFC ||
                ((addr->data.inet6.sin6_addr.s6_addr[0] & 0xFF) == 0xFE &&
                 (addr->data.inet6.sin6_addr.s6_addr[1] & 0xC0) == 0xC0));
    }
    return false;
}

/*
 * virSocketAddrIsWildcard:
 * @addr: address to check
 *
 * Check if passed address is a variant of ANYCAST address.
 */
bool
virSocketAddrIsWildcard(const virSocketAddr *addr)
{
    struct in_addr tmp = { .s_addr = INADDR_ANY };
    switch (addr->data.stor.ss_family) {
    case AF_INET:
        return memcmp(&addr->data.inet4.sin_addr.s_addr, &tmp.s_addr,
                      sizeof(addr->data.inet4.sin_addr.s_addr)) == 0;
    case AF_INET6:
        return IN6_IS_ADDR_UNSPECIFIED(&addr->data.inet6.sin6_addr);
    }
    return false;
}

/*
 * virSocketAddrFormat:
 * @addr: an initialized virSocketAddrPtr
 *
 * Returns a string representation of the given address
 * Returns NULL on any error
 * Caller must free the returned string
 */
char *
virSocketAddrFormat(const virSocketAddr *addr)
{
    return virSocketAddrFormatFull(addr, false, NULL);
}


/*
 * virSocketAddrFormatFull:
 * @addr: an initialized virSocketAddrPtr
 * @withService: if true, then service info is appended
 * @separator: separator between hostname & service.
 *
 * Returns a string representation of the given address. If a format conforming
 * to URI specification is required, NULL should be passed to separator.
 * Set @separator only if non-URI format is required, e.g. passing ';' for
 * @separator if the address should be used with SASL.
 * Caller must free the returned string.
 */
char *
virSocketAddrFormatFull(const virSocketAddr *addr,
                        bool withService,
                        const char *separator)
{
    char host[NI_MAXHOST], port[NI_MAXSERV];
    char *addrstr;
    int err;

    if (addr == NULL) {
        virReportError(VIR_ERR_INVALID_ARG, "%s", _("Missing address"));
        return NULL;
    }

    /* Short-circuit since getnameinfo doesn't work
     * nicely for UNIX sockets */
    if (addr->data.sa.sa_family == AF_UNIX) {
        if (withService) {
            if (virAsprintf(&addrstr, VIR_LOOPBACK_IPV4_ADDR"%s0",
                            separator ? separator : ":") < 0)
                goto error;
        } else {
            if (VIR_STRDUP(addrstr, VIR_LOOPBACK_IPV4_ADDR) < 0)
                goto error;
        }
        return addrstr;
    }

    if ((err = getnameinfo(&addr->data.sa,
                           addr->len,
                           host, sizeof(host),
                           port, sizeof(port),
                           NI_NUMERICHOST | NI_NUMERICSERV)) != 0) {
        virReportError(VIR_ERR_SYSTEM_ERROR,
                       _("Cannot convert socket address to string: %s"),
                       gai_strerror(err));
        return NULL;
    }

    if (withService) {
        char *ipv6_host = NULL;
        /* sasl_new_client demands the socket address to be in an odd format:
         * a.b.c.d;port or e:f:g:h:i:j:k:l;port, so use square brackets for
         * IPv6 only if no separator is passed to the function
         */
        if (!separator && VIR_SOCKET_ADDR_FAMILY(addr) == AF_INET6) {
            if (virAsprintf(&ipv6_host, "[%s]", host) < 0)
                goto error;
        }

        if (virAsprintf(&addrstr, "%s%s%s",
                        ipv6_host ? ipv6_host : host,
                        separator ? separator : ":", port) == -1) {
            VIR_FREE(ipv6_host);
            goto error;
        }

        VIR_FREE(ipv6_host);
    } else {
        if (VIR_STRDUP(addrstr, host) < 0)
            goto error;
    }

    return addrstr;

 error:
    return NULL;
}


/*
 * virSocketAddrSetPort:
 * @addr: an initialized virSocketAddrPtr
 * @port: the port number to set
 *
 * Set the transport layer port of the given virtSocketAddr
 *
 * Returns 0 on success, -1 on failure
 */
int
virSocketAddrSetPort(virSocketAddrPtr addr, int port)
{
    if (addr == NULL)
        return -1;

    port = htons(port);

    if (addr->data.stor.ss_family == AF_INET) {
        addr->data.inet4.sin_port = port;
    } else if (addr->data.stor.ss_family == AF_INET6) {
        addr->data.inet6.sin6_port = port;
    } else {
        return -1;
    }

    return 0;
}

/*
 * virSocketGetPort:
 * @addr: an initialized virSocketAddrPtr
 *
 * Returns the transport layer port of the given virtSocketAddr
 * Returns -1 if @addr is invalid
 */
int
virSocketAddrGetPort(virSocketAddrPtr addr)
{
    if (addr == NULL)
        return -1;

    if (addr->data.stor.ss_family == AF_INET) {
        return ntohs(addr->data.inet4.sin_port);
    } else if (addr->data.stor.ss_family == AF_INET6) {
        return ntohs(addr->data.inet6.sin6_port);
    }

    return -1;
}

/**
 * virSocketAddrIsNetmask:
 * @netmask: the netmask address
 *
 * Check that @netmask is a proper network mask
 *
 * Returns 0 in case of success and -1 in case of error
 */
int virSocketAddrIsNetmask(virSocketAddrPtr netmask)
{
    int n = virSocketAddrGetNumNetmaskBits(netmask);
    if (n < 0)
        return -1;
    return 0;
}

/**
 * virSocketAddrMask:
 * @addr: address that needs to be masked
 * @netmask: the netmask address
 * @network: where to store the result, can be same as @addr
 *
 * Mask off the host bits of @addr according to @netmask, turning it
 * into a network address.
 *
 * Returns 0 in case of success, or -1 on error.
 */
int
virSocketAddrMask(const virSocketAddr *addr,
                  const virSocketAddr *netmask,
                  virSocketAddrPtr network)
{
    memset(network, 0, sizeof(*network));
    if (addr->data.stor.ss_family != netmask->data.stor.ss_family) {
        network->data.stor.ss_family = AF_UNSPEC;
        return -1;
    }

    if (addr->data.stor.ss_family == AF_INET) {
        network->data.inet4.sin_addr.s_addr
            = (addr->data.inet4.sin_addr.s_addr
               & netmask->data.inet4.sin_addr.s_addr);
        network->data.inet4.sin_port = 0;
        network->data.stor.ss_family = AF_INET;
        network->len = addr->len;
        return 0;
    }
    if (addr->data.stor.ss_family == AF_INET6) {
        size_t i;
        for (i = 0; i < 16; i++) {
            network->data.inet6.sin6_addr.s6_addr[i]
                = (addr->data.inet6.sin6_addr.s6_addr[i]
                   & netmask->data.inet6.sin6_addr.s6_addr[i]);
        }
        network->data.inet6.sin6_port = 0;
        network->data.stor.ss_family = AF_INET6;
        network->len = addr->len;
        return 0;
    }
    network->data.stor.ss_family = AF_UNSPEC;
    return -1;
}

/**
 * virSocketAddrMaskByPrefix:
 * @addr: address that needs to be masked
 * @prefix: prefix (# of 1 bits) of netmask to apply
 * @network: where to store the result, can be same as @addr
 *
 * Mask off the host bits of @addr according to @prefix, turning it
 * into a network address.
 *
 * Returns 0 in case of success, or -1 on error.
 */
int
virSocketAddrMaskByPrefix(const virSocketAddr *addr,
                          unsigned int prefix,
                          virSocketAddrPtr network)
{
    virSocketAddr netmask;

    if (virSocketAddrPrefixToNetmask(prefix, &netmask,
                                     addr->data.stor.ss_family) < 0) {
        network->data.stor.ss_family = AF_UNSPEC;
        return -1;
    }

    return virSocketAddrMask(addr, &netmask, network);
}

/**
 * virSocketAddrBroadcast:
 * @addr: address that needs to be turned into broadcast address (IPv4 only)
 * @netmask: the netmask address
 * @broadcast: virSocketAddr to receive the broadcast address
 *
 * Mask ON the host bits of @addr according to @netmask, turning it
 * into a broadcast address.
 *
 * Returns 0 in case of success, or -1 on error.
 */
int
virSocketAddrBroadcast(const virSocketAddr *addr,
                       const virSocketAddr *netmask,
                       virSocketAddrPtr broadcast)
{
    memset(broadcast, 0, sizeof(*broadcast));

    if ((addr->data.stor.ss_family != AF_INET) ||
        (netmask->data.stor.ss_family != AF_INET)) {
        broadcast->data.stor.ss_family = AF_UNSPEC;
        return -1;
    }

    broadcast->data.stor.ss_family = AF_INET;
    broadcast->len = addr->len;
    broadcast->data.inet4.sin_addr.s_addr
        = (addr->data.inet4.sin_addr.s_addr
           | ~netmask->data.inet4.sin_addr.s_addr);
    return 0;
}

/**
 * virSocketAddrBroadcastByPrefix:
 * @addr: address that needs to be turned into broadcast address (IPv4 only)
 * @prefix: prefix (# of 1 bits) of netmask to apply
 * @broadcast: virSocketAddr to receive the broadcast address
 *
 * Mask off the host bits of @addr according to @prefix, turning it
 * into a network address.
 *
 * Returns 0 in case of success, or -1 on error.
 */
int
virSocketAddrBroadcastByPrefix(const virSocketAddr *addr,
                               unsigned int prefix,
                               virSocketAddrPtr broadcast)
{
    virSocketAddr netmask;

    if (virSocketAddrPrefixToNetmask(prefix, &netmask,
                                     addr->data.stor.ss_family) < 0)
        return -1;

    return virSocketAddrBroadcast(addr, &netmask, broadcast);
}

/**
 * virSocketCheckNetmask:
 * @addr1: a first network address
 * @addr2: a second network address
 * @netmask: the netmask address
 *
 * Check that @addr1 and @addr2 pertain to the same @netmask address
 * range and returns the size of the range
 *
 * Returns 1 in case of success and 0 in case of failure and
 *         -1 in case of error
 */
int virSocketAddrCheckNetmask(virSocketAddrPtr addr1, virSocketAddrPtr addr2,
                              virSocketAddrPtr netmask)
{
    size_t i;

    if ((addr1 == NULL) || (addr2 == NULL) || (netmask == NULL))
        return -1;
    if ((addr1->data.stor.ss_family != addr2->data.stor.ss_family) ||
        (addr1->data.stor.ss_family != netmask->data.stor.ss_family))
        return -1;

    if (virSocketAddrIsNetmask(netmask) != 0)
        return -1;

    if (addr1->data.stor.ss_family == AF_INET) {
        virSocketAddrIPv4 t1, t2, tm;

        if ((virSocketAddrGetIPv4Addr(addr1, &t1) < 0) ||
            (virSocketAddrGetIPv4Addr(addr2, &t2) < 0) ||
            (virSocketAddrGetIPv4Addr(netmask, &tm) < 0))
            return -1;

        for (i = 0; i < 4; i++) {
            if ((t1[i] & tm[i]) != (t2[i] & tm[i]))
                return 0;
        }

    } else if (addr1->data.stor.ss_family == AF_INET6) {
        virSocketAddrIPv6 t1, t2, tm;

        if ((virSocketAddrGetIPv6Addr(addr1, &t1) < 0) ||
            (virSocketAddrGetIPv6Addr(addr2, &t2) < 0) ||
            (virSocketAddrGetIPv6Addr(netmask, &tm) < 0))
            return -1;

        for (i = 0; i < 8; i++) {
            if ((t1[i] & tm[i]) != (t2[i] & tm[i]))
                return 0;
        }

    } else {
        return -1;
    }
    return 1;
}

/**
 * virSocketGetRange:
 * @start: start of an IP range
 * @end: end of an IP range
 * @network: IP address of network that should completely contain this range
 * @prefix: prefix of the network
 *
 * Check the order of the 2 addresses and compute the range, this will
 * return 1 for identical addresses. Errors can come from incompatible
 * addresses type, excessive range (>= 2^^16) where the two addresses
 * are unrelated, inverted start and end, or a range that is not
 * within network/prefix.
 *
 * Returns the size of the range or -1 in case of failure
 */
int
virSocketAddrGetRange(virSocketAddrPtr start, virSocketAddrPtr end,
                      virSocketAddrPtr network, int prefix)
{
    int ret = 0;
    size_t i;
    virSocketAddr netmask;
    char *startStr = NULL, *endStr = NULL, *netStr = NULL;

    if (start == NULL || end == NULL) {
        virReportError(VIR_ERR_INTERNAL_ERROR,
                       _("NULL argument - %p %p"), start, end);
        goto error;
    }

    startStr = virSocketAddrFormat(start);
    endStr = virSocketAddrFormat(end);
    if (!startStr || !endStr)
        goto error; /*error already reported */

    if (VIR_SOCKET_ADDR_FAMILY(start) != VIR_SOCKET_ADDR_FAMILY(end)) {
        virReportError(VIR_ERR_INTERNAL_ERROR,
                       _("mismatch of address family in range %s - %s"),
                       startStr, endStr);
        goto error;
    }

    if (network) {
        /* some checks can only be done if we have details of the
         * network the range should be within
         */
        if (!(netStr = virSocketAddrFormat(network)))
            goto error;

        if (VIR_SOCKET_ADDR_FAMILY(start) != VIR_SOCKET_ADDR_FAMILY(network)) {
            virReportError(VIR_ERR_INTERNAL_ERROR,
                           _("mismatch of address family in "
                             "range %s - %s for network %s"),
                           startStr, endStr, netStr);
            goto error;
        }

        if (prefix < 0 ||
            virSocketAddrPrefixToNetmask(prefix, &netmask,
                                         VIR_SOCKET_ADDR_FAMILY(network)) < 0) {
            virReportError(VIR_ERR_INTERNAL_ERROR,
                           _("bad prefix %d for network %s when "
                             " checking range %s - %s"),
                           prefix, netStr, startStr, endStr);
            goto error;
        }

        /* both start and end of range need to be within network */
        if (virSocketAddrCheckNetmask(start, network, &netmask) <= 0 ||
            virSocketAddrCheckNetmask(end, network, &netmask) <= 0) {
            virReportError(VIR_ERR_INTERNAL_ERROR,
                           _("range %s - %s is not entirely within "
                             "network %s/%d"),
                           startStr, endStr, netStr, prefix);
            goto error;
        }

        if (VIR_SOCKET_ADDR_IS_FAMILY(start, AF_INET)) {
            virSocketAddr netaddr, broadcast;

            if (virSocketAddrBroadcast(network, &netmask, &broadcast) < 0 ||
                virSocketAddrMask(network, &netmask, &netaddr) < 0) {
                virReportError(VIR_ERR_INTERNAL_ERROR,
                               _("failed to construct broadcast or network "
                                 "address for network %s/%d"),
                               netStr, prefix);
                goto error;
            }

            /* Don't allow the start of the range to be the network
             * address (usually "...0") or the end of the range to be the
             * broadcast address (usually "...255"). (the opposite also
             * isn't allowed, but checking for that is implicit in all the
             * other combined checks) (IPv6 doesn't have broadcast and
             * network addresses, so this check is only done for IPv4)
             */
            if (virSocketAddrEqual(start, &netaddr)) {
                virReportError(VIR_ERR_INTERNAL_ERROR,
                               _("start of range %s - %s in network %s/%d "
                                 "is the network address"),
                               startStr, endStr, netStr, prefix);
                goto error;
            }

            if (virSocketAddrEqual(end, &broadcast)) {
                virReportError(VIR_ERR_INTERNAL_ERROR,
                               _("end of range %s - %s in network %s/%d "
                                 "is the broadcast address"),
                               startStr, endStr, netStr, prefix);
                goto error;
            }
        }
    }

    if (VIR_SOCKET_ADDR_IS_FAMILY(start, AF_INET)) {
        virSocketAddrIPv4 t1, t2;

        if (virSocketAddrGetIPv4Addr(start, &t1) < 0 ||
            virSocketAddrGetIPv4Addr(end, &t2) < 0) {
            virReportError(VIR_ERR_INTERNAL_ERROR,
                           _("failed to get IPv4 address "
                             "for start or end of range %s - %s"),
                           startStr, endStr);
            goto error;
        }

        /* legacy check that everything except the last two bytes
         * are the same
         */
        for (i = 0; i < 2; i++) {
            if (t1[i] != t2[i]) {
            virReportError(VIR_ERR_INTERNAL_ERROR,
                           _("range %s - %s is too large (> 65535)"),
                           startStr, endStr);
            goto error;
            }
        }
        ret = (t2[2] - t1[2]) * 256 + (t2[3] - t1[3]);
        if (ret < 0) {
            virReportError(VIR_ERR_INTERNAL_ERROR,
                           _("range %s - %s is reversed "),
                           startStr, endStr);
            goto error;
        }
        ret++;
    } else if (VIR_SOCKET_ADDR_IS_FAMILY(start, AF_INET6)) {
        virSocketAddrIPv6 t1, t2;

        if (virSocketAddrGetIPv6Addr(start, &t1) < 0 ||
            virSocketAddrGetIPv6Addr(end, &t2) < 0) {
            virReportError(VIR_ERR_INTERNAL_ERROR,
                           _("failed to get IPv6 address "
                             "for start or end of range %s - %s"),
                           startStr, endStr);
            goto error;
        }

        /* legacy check that everything except the last two bytes are
         * the same
         */
        for (i = 0; i < 7; i++) {
            if (t1[i] != t2[i]) {
                virReportError(VIR_ERR_INTERNAL_ERROR,
                               _("range %s - %s is too large (> 65535)"),
                               startStr, endStr);
                goto error;
            }
        }
        ret = t2[7] - t1[7];
        if (ret < 0) {
            virReportError(VIR_ERR_INTERNAL_ERROR,
                           _("range %s - %s start larger than end"),
                           startStr, endStr);
            goto error;
        }
        ret++;
    } else {
        virReportError(VIR_ERR_INTERNAL_ERROR,
                       _("unsupported address family "
                         "for range %s - %s, must be ipv4 or ipv6"),
                       startStr, endStr);
        goto error;
    }

 cleanup:
    VIR_FREE(startStr);
    VIR_FREE(endStr);
    VIR_FREE(netStr);
    return ret;

 error:
    ret = -1;
    goto cleanup;
}


/**
 * virSocketAddrGetNumNetmaskBits
 * @netmask: the presumed netmask
 *
 * Get the number of netmask bits in a netmask.
 *
 * Returns the number of bits in the netmask or -1 if an error occurred
 * or the netmask is invalid.
 */
int virSocketAddrGetNumNetmaskBits(const virSocketAddr *netmask)
{
    size_t i, j;
    int c = 0;

    if (netmask->data.stor.ss_family == AF_INET) {
        virSocketAddrIPv4 tm;
        uint8_t bit;

        if (virSocketAddrGetIPv4Addr(netmask, &tm) < 0)
            return -1;

        for (i = 0; i < 4; i++)
            if (tm[i] == 0xff)
                c += 8;
            else
                break;

        if (c == 8 * 4)
            return c;

        j = i << 3;
        while (j < (8 * 4)) {
            bit = 1 << (7 - (j & 7));
            if ((tm[j >> 3] & bit))
                c++;
            else
                break;
            j++;
        }

        while (j < (8 * 4)) {
            bit = 1 << (7 - (j & 7));
            if ((tm[j >> 3] & bit))
                return -1;
            j++;
        }

        return c;
    } else if (netmask->data.stor.ss_family == AF_INET6) {
        virSocketAddrIPv6 tm;
        uint16_t bit;

        if (virSocketAddrGetIPv6Addr(netmask, &tm) < 0)
            return -1;

        for (i = 0; i < 8; i++)
            if (tm[i] == 0xffff)
                c += 16;
            else
                break;

        if (c == 16 * 8)
            return c;

        j = i << 4;
        while (j < (16 * 8)) {
            bit = 1 << (15 - (j & 0xf));
            if ((tm[j >> 4] & bit))
                c++;
            else
                break;
            j++;
        }

        while (j < (16 * 8)) {
            bit = 1 << (15 - (j & 0xf));
            if ((tm[j >> 4]) & bit)
                return -1;
            j++;
        }

        return c;
    }
    return -1;
}

/**
 * virSocketPrefixToNetmask:
 * @prefix: number of 1 bits to put in the netmask
 * @netmask: address to fill in with the desired netmask
 * @family: family of the address (AF_INET or AF_INET6 only)
 *
 * given @prefix and @family, fill in @netmask with a netmask
 * (eg 255.255.255.0).
 *
 * Returns 0 on success or -1 on error.
 */

int
virSocketAddrPrefixToNetmask(unsigned int prefix,
                             virSocketAddrPtr netmask,
                             int family)
{
    int result = -1;

    netmask->data.stor.ss_family = AF_UNSPEC; /* assume failure */

    if (family == AF_INET) {
        int ip;

        if (prefix > 32)
            goto error;

        ip = prefix ? ~((1 << (32 - prefix)) - 1) : 0;
        netmask->data.inet4.sin_addr.s_addr = htonl(ip);
        netmask->data.stor.ss_family = AF_INET;
        result = 0;

    } else if (family == AF_INET6) {
        size_t i = 0;

        if (prefix > 128)
            goto error;

        while (prefix >= 8) {
            /* do as much as possible an entire byte at a time */
            netmask->data.inet6.sin6_addr.s6_addr[i++] = 0xff;
            prefix -= 8;
        }
        if (prefix > 0) {
            /* final partial byte */
            netmask->data.inet6.sin6_addr.s6_addr[i++]
                = ~((1 << (8 - prefix)) -1);
        }
        while (i < 16) {
            /* zerofill remainder in case it wasn't initialized */
            netmask->data.inet6.sin6_addr.s6_addr[i++] = 0;
        }
        netmask->data.stor.ss_family = AF_INET6;
        result = 0;
    }

 error:
    return result;
 }

/**
 * virSocketAddrGetIPPrefix:
 * @address: network address
 * @netmask: netmask for this network
 * @prefix: prefix if specified instead of netmask
 *
 * Returns prefix value on success or -1 on error.
 */

int
virSocketAddrGetIPPrefix(const virSocketAddr *address,
                         const virSocketAddr *netmask,
                         int prefix)
{
    if (prefix > 0) {
        return prefix;
    } else if (netmask && VIR_SOCKET_ADDR_VALID(netmask)) {
        return virSocketAddrGetNumNetmaskBits(netmask);
    } else if (address && VIR_SOCKET_ADDR_IS_FAMILY(address, AF_INET)) {
        /* Return the natural prefix for the network's ip address.
         * On Linux we could use the IN_CLASSx() macros, but those
         * aren't guaranteed on all platforms, so we just deal with
         * the bits ourselves.
         */
        unsigned char octet
            = ntohl(address->data.inet4.sin_addr.s_addr) >> 24;

        /* If address is 0.0.0.0, we surely want to have 0 prefix for
         * the default route. */
        if (address->data.inet4.sin_addr.s_addr == 0)
            return 0;

        if ((octet & 0x80) == 0) {
            /* Class A network */
            return 8;
        } else if ((octet & 0xC0) == 0x80) {
            /* Class B network */
            return 16;
        } else if ((octet & 0xE0) == 0xC0) {
            /* Class C network */
            return 24;
        }
        return -1;
    } else if (address && VIR_SOCKET_ADDR_IS_FAMILY(address, AF_INET6)) {
        if (virSocketAddrIsWildcard(address))
            return 0;
        return 64;
    }

    /* When none of the three (address/netmask/prefix) is given, 0 is
     * returned rather than error, because this is a valid
     * expectation, e.g. for the address/prefix used for a default
     * route (the destination of a default route is 0.0.0.0/0).
     */
    return 0;
}

/**
 * virSocketAddrNumericFamily:
 * @address: address to check
 *
 * Check if passed address is an IP address in numeric format.
 *
 * Returns: AF_INET or AF_INET6 if @address is an numeric IP address,
 *          -1 otherwise.
 */
int
virSocketAddrNumericFamily(const char *address)
{
    struct addrinfo *res;
    unsigned short family;

    if (virSocketAddrParseInternal(&res, address, AF_UNSPEC, AI_NUMERICHOST, false) < 0)
        return -1;

    family = res->ai_addr->sa_family;
    freeaddrinfo(res);
    return family;
}

/**
 * virSocketAddrIsNumericLocalhost:
 * @address: address to check
 *
 * Check if passed address is a numeric 'localhost' address.
 *
 * Returns: true if @address is a numeric 'localhost' address,
 *          false otherwise
 */
bool
virSocketAddrIsNumericLocalhost(const char *addr)
{
    virSocketAddr res;
    struct in_addr tmp = { .s_addr = htonl(INADDR_LOOPBACK) };

    if (virSocketAddrParse(&res, addr, AF_UNSPEC) < 0)
        return false;

    switch (res.data.stor.ss_family) {
    case AF_INET:
        return memcmp(&res.data.inet4.sin_addr.s_addr, &tmp.s_addr,
                     sizeof(res.data.inet4.sin_addr.s_addr)) == 0;
    case AF_INET6:
        return IN6_IS_ADDR_LOOPBACK(&res.data.inet6.sin6_addr);
    }

    return false;
}


/**
 * virSocketAddrPTRDomain:
 *
 * Create PTR domain which corresponds to @addr/@prefix. Both IPv4 and IPv6
 * addresses are supported, but @prefix must be divisible by 8 for IPv4 and
 * divisible by 4 for IPv6, otherwise -2 will be returned.
 *
 * Returns -2 if the PTR record cannot be automatically created,
 *         -1 on error,
  *         0 on success.
 */
int
virSocketAddrPTRDomain(const virSocketAddr *addr,
                       unsigned int prefix,
                       char **ptr)
{
    virBuffer buf = VIR_BUFFER_INITIALIZER;
    size_t i;
    int ret = -1;

    if (VIR_SOCKET_ADDR_IS_FAMILY(addr, AF_INET)) {
        virSocketAddrIPv4 ip;

        if (prefix == 0 || prefix >= 32 || prefix % 8 != 0)
            goto unsupported;

        if (virSocketAddrGetIPv4Addr(addr, &ip) < 0)
            goto cleanup;

        for (i = prefix / 8; i > 0; i--)
            virBufferAsprintf(&buf, "%u.", ip[i - 1]);

        virBufferAddLit(&buf, VIR_SOCKET_ADDR_IPV4_ARPA);
    } else if (VIR_SOCKET_ADDR_IS_FAMILY(addr, AF_INET6)) {
        virSocketAddrIPv6Nibbles ip;

        if (prefix == 0 || prefix >= 128 || prefix % 4 != 0)
            goto unsupported;

        if (virSocketAddrGetIPv6Nibbles(addr, &ip) < 0)
            goto cleanup;

        for (i = prefix / 4; i > 0; i--)
            virBufferAsprintf(&buf, "%x.", ip[i - 1]);

        virBufferAddLit(&buf, VIR_SOCKET_ADDR_IPV6_ARPA);
    } else {
        goto unsupported;
    }

    if (!(*ptr = virBufferContentAndReset(&buf)))
        goto cleanup;

    ret = 0;

 cleanup:
    virBufferFreeAndReset(&buf);
    return ret;

 unsupported:
    ret = -2;
    goto cleanup;
}
