Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions debian/changelog
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
ipaddrcheck (1.4) unstable; urgency=medium

* Do not disallow all-zero host part IPv6 addresses for host and interface address checks (Daniil Baturin).

-- Daniil Baturin <daniil@vyos.io> Fri, 05 Dec 2025 15:04:03 +0000

ipaddrcheck (1.3) unstable; urgency=medium

* New option --range-prefix-length to require ranges to be within a subnet (Daniil Baturin).
Expand Down
24 changes: 5 additions & 19 deletions src/ipaddrcheck_functions.c
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* ipaddrcheck_functions.c: IPv4/IPv6 validation functions for ipaddrcheck
*
* Copyright (C) 2013 Daniil Baturin
* Copyright (C) 2018-2024 VyOS maintainers and contributors
* Copyright (C) 2018-2025 VyOS maintainers and contributors
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
Expand Down Expand Up @@ -325,29 +325,15 @@ int is_ipv6(CIDR *address)
return(result);
}

/* Is it a correct IPv6 host address? */
/* Is it a correct IPv6 host address?
Everything except the unspecified address is.
*/
int is_ipv6_host(CIDR *address)
{
int result;

/* We reuse the same logic that prevents IPv4 network addresses
from being assigned to interfaces (address == network_address),
but the reason is slightly differnt.

As per https://www.rfc-editor.org/rfc/rfc4291 section 2.6.1,
>[Subnet-Router anycast address] is syntactically
>the same as a unicast address for an interface on the link with the
>interface identifier set to zero.

So, the first address of the subnet must not be used for link addresses,
even if the semantic reason is different.
There's absolutely nothing wrong with assigning the last address, though,
since there's no broadcast in IPv6.
*/

if( (cidr_get_proto(address) == CIDR_IPV6) &&
((cidr_equals(address, cidr_addr_network(address)) < 0) ||
(cidr_get_pflen(address) >= 127)) )
(cidr_equals(address, cidr_from_str(IPV6_UNSPECIFIED)) != 0) )
{
result = RESULT_SUCCESS;
}
Expand Down
3 changes: 2 additions & 1 deletion src/ipaddrcheck_functions.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* ipaddrcheck_functions.h: macros and prototypes for ipaddrcheck
*
* Copyright (C) 2013 Daniil Baturin
* Copyright (C) 2018-2024 VyOS maintainers and contributors
* Copyright (C) 2018-2025 VyOS maintainers and contributors
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
Expand Down Expand Up @@ -50,6 +50,7 @@
#define IPV6_MULTICAST "ff00::/8"
#define IPV6_LINKLOCAL "fe80::/64"
#define IPV6_LOOPBACK "::1/128"
#define IPV6_UNSPECIFIED "::/0"

#define NO_LOOPBACK 0
#define LOOPBACK_ALLOWED 1
Expand Down
20 changes: 10 additions & 10 deletions tests/check_ipaddrcheck.c
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* check_ipaddrcheck.c: ipaddrcheck unit tests
*
* Copyright (C) 2013 Daniil Baturin
* Copyright (C) 2018-2024 VyOS maintainers and contributors
* Copyright (C) 2018-2025 VyOS maintainers and contributors
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 or later as
Expand Down Expand Up @@ -289,11 +289,6 @@ START_TEST (test_is_ipv6_host)
CIDR* good_address_cidr = cidr_from_str(good_address_str_cidr);
ck_assert_int_eq(is_ipv6_host(good_address_cidr), RESULT_SUCCESS);
cidr_free(good_address_cidr);

char* bad_address_str = "2001:db8:f::/48";
CIDR* bad_address = cidr_from_str(bad_address_str);
ck_assert_int_eq(is_ipv6_host(bad_address), RESULT_FAILURE);
cidr_free(bad_address);
}
END_TEST

Expand Down Expand Up @@ -367,15 +362,20 @@ START_TEST (test_is_any_host)
ck_assert_int_eq(is_any_host(good_address_v6), RESULT_SUCCESS);
cidr_free(good_address_v6);

char* good_address_str_v6_all_zero = "2001:db8::/32";
CIDR* good_address_v6_all_zero = cidr_from_str(good_address_str_v6_all_zero);
ck_assert_int_eq(is_any_host(good_address_v6_all_zero), RESULT_SUCCESS);
cidr_free(good_address_v6_all_zero);

char* bad_address_str_v4 = "192.0.2.0/24";
CIDR* bad_address_v4 = cidr_from_str(bad_address_str_v4);
ck_assert_int_eq(is_any_host(bad_address_v4), RESULT_FAILURE);
cidr_free(bad_address_v4);

char* bad_address_str_v6 = "2001:db8::/32";
CIDR* bad_address_v6 = cidr_from_str(bad_address_str_v6);
ck_assert_int_eq(is_any_host(bad_address_v6), RESULT_FAILURE);
cidr_free(bad_address_v6);
char* bad_address_str_v6_unspec = "::/0";
CIDR* bad_address_v6_unspec = cidr_from_str(bad_address_str_v6_unspec);
ck_assert_int_eq(is_any_host(bad_address_v6_unspec), RESULT_FAILURE);
cidr_free(bad_address_v6_unspec);
}
END_TEST

Expand Down
51 changes: 49 additions & 2 deletions tests/integration_tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
# integration_tests.sh: ipaddrcheck integration tests
#
# Copyright (C) 2013 Daniil Baturin
# Copyright (C) 2018-2024 VyOS maintainers and contributors
# Copyright (C) 2018-2025 VyOS maintainers and contributors
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 or later as
Expand Down Expand Up @@ -59,6 +59,28 @@ ipv4_range_negative=(
192.0.2.1-192.0.2.500
)

ipv4_interface_address_positive=(
192.0.2.1/24
192.168.1.0/23
)

ipv4_interface_address_negative=(
192.0.2.0/24 # Network address
192.0.2.255/24 # Broadcast
224.0.0.1/24 # Multicast
0.0.0.0/0 # Unspecified
)

ipv6_interface_address_positive=(
2001:db8::1/64
2001:db8::/64
)

ipv6_interface_address_negative=(
ff02::1:2 # Multicast
::/0 # Unspecified
)

ipv6_range_positive=(
2001:db8::1-2001:db8::99
)
Expand Down Expand Up @@ -111,10 +133,11 @@ ipv4_host_negative=(
ipv6_host_positive=(
2001:db8:1::1/64
2001:db8:1::1/127
2001:db8:1::/64 # All-zero IPv6 addresses are valid host addresses
)

ipv6_host_negative=(
2001:db8::/32
::/0
)

string="garbage"
Expand Down Expand Up @@ -272,7 +295,31 @@ done
# --is-ipv6-net
# --is-ipv6-multicast
# --is-ipv6-link-local

# --is-valid-intf-address
for address in \
${ipv4_interface_address_positive[*]}
do
assert_raises "$IPADDRCHECK --is-valid-intf-address $address" 0
done

for address in \
${ipv4_interface_address_negative[*]}
do
assert_raises "$IPADDRCHECK --is-valid-intf-address $address" 1
done

for address in \
${ipv6_interface_address_positive[*]}
do
assert_raises "$IPADDRCHECK --is-valid-intf-address $address" 0
done

for address in \
${ipv6_interface_address_negative[*]}
do
assert_raises "$IPADDRCHECK --is-valid-intf-address $address" 1
done

# --is-ipv4-range
for range in \
Expand Down