On Fri, Feb 06, 2026 at 14:52:35 +0100, Michal Privoznik via Devel wrote:
> From: Michal Privoznik <[email protected]>
>
> The aim of this helper is to convert subnet mask to prefix. For
> instance for input "255.0.0.0" to return 0. Additionally, if the
> input string is already a prefix (with optional leading slash
> character) just return that number parsed.
>
> Signed-off-by: Michal Privoznik <[email protected]>
> ---
> src/libvirt_private.syms | 1 +
> src/util/virsocketaddr.c | 51 ++++++++++++++++++++++++++++++++++++++++
> src/util/virsocketaddr.h | 2 ++
> tests/sockettest.c | 29 +++++++++++++++++++++++
> 4 files changed, 83 insertions(+)
>
> diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms
> index d81b30f0b6..035eccf70a 100644
> --- a/src/libvirt_private.syms
> +++ b/src/libvirt_private.syms
> @@ -3461,6 +3461,7 @@ virSocketAddrSetIPv4AddrNetOrder;
> virSocketAddrSetIPv6Addr;
> virSocketAddrSetIPv6AddrNetOrder;
> virSocketAddrSetPort;
> +virSocketAddrSubnetToPrefix;
>
>
> # util/virstoragefile.h
> diff --git a/src/util/virsocketaddr.c b/src/util/virsocketaddr.c
> index 1f203fb50d..89d41d7656 100644
> --- a/src/util/virsocketaddr.c
> +++ b/src/util/virsocketaddr.c
> @@ -21,6 +21,7 @@
> #include "virsocketaddr.h"
> #include "virerror.h"
> #include "virbuffer.h"
> +#include "virstring.h"
>
> #define VIR_FROM_THIS VIR_FROM_NONE
>
> @@ -1263,6 +1264,56 @@ virSocketAddrNumericFamily(const char *address)
> return family;
> }
>
> +/**
> + * virSocketAddrSubnetToPrefix:
> + * @subnet: address to convert
> + *
> + * Converts subnet mask to prefix. If @subnet is of an IPv4
> + * format (NNN.NNN.NNN.NNN) then corresponding prefix length is
> + * returned (i.e. number of leading bits.). If @subnet is just a
> + * number (optionally prefixed with '/') then the number is
> + * parsed and returned.
This doesn't say any caveats if e.g. the IPv4 string you pass in isn't a
subnet mask at all ...
> + * Returns: prefix corresponding to @subnet,
> + * -1 otherwise.
> + */
> +int
> +virSocketAddrSubnetToPrefix(const char *subnet)
> +{
> + struct addrinfo *ai = NULL;
> + unsigned int prefix = 0;
> + struct sockaddr_in in;
> + int ret = -1;
> +
> + if (*subnet == '/') {
> + /* /NN format */
> + if (virStrToLong_ui(subnet + 1, NULL, 10, &prefix) < 0)
> + return -1;
> + return prefix;
> + }
> +
> + if (virStrToLong_ui(subnet, NULL, 10, &prefix) >= 0) {
> + /* plain NN format */
> + return prefix;
> + }
> +
> + if (virSocketAddrParseInternal(&ai, subnet, AF_INET, AI_NUMERICHOST,
> false) < 0)
> + return -1;
> +
> + if (ai->ai_family != AF_INET) {
> + /* huh? */
> + goto cleanup;
> + }
> +
> + memcpy(&in, ai->ai_addr, sizeof(in));
> + prefix = __builtin_popcount(in.sin_addr.s_addr);
... so if you don't pass a mask it gives not really documented answers.
With the impl above the following predicates of the test added later
are also true:
DO_TEST_SUBNET_TO_PREFIX("192.168.0.1", 6);
DO_TEST_SUBNET_TO_PREFIX("129.0.0.1", 3);
Depending on the expectations either document the expectation of what
happens argument isn't a subnet or add some checks
E.g. you could use __builtin_ffs to figure out the number of leading
bits and then compare with __builtin_popcount to see if other bits
aren't set.
> diff --git a/tests/sockettest.c b/tests/sockettest.c
> index 5cb8a9fb72..df62dc6f3b 100644
> --- a/tests/sockettest.c
> +++ b/tests/sockettest.c
> @@ -257,6 +257,21 @@ testIsLocalhostHelper(const void *opaque)
> return 0;
> }
>
> +struct testSubnetToPrefixData {
> + const char *addr;
> + int prefix;
> +};
> +
> +static int
> +testSubnetToPrefixHelper(const void *opaque)
> +{
> + const struct testSubnetToPrefixData *data = opaque;
> +
> + if (virSocketAddrSubnetToPrefix(data->addr) != data->prefix)
> + return -1;
Consider adding:
diff --git a/tests/sockettest.c b/tests/sockettest.c
index df62dc6f3b..37f654880a 100644
--- a/tests/sockettest.c
+++ b/tests/sockettest.c
@@ -266,9 +266,13 @@ static int
testSubnetToPrefixHelper(const void *opaque)
{
const struct testSubnetToPrefixData *data = opaque;
+ int val = virSocketAddrSubnetToPrefix(data->addr);
- if (virSocketAddrSubnetToPrefix(data->addr) != data->prefix)
+ if (val != data->prefix) {
+ fprintf(stderr, "actual: '%d', expected: '%d'", val, data->prefix);
return -1;
+ }
+
return 0;
}
for debuggability