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
22 changes: 20 additions & 2 deletions docs/shell-ip-address.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ shell-ip-address(3)

# NAME

ipv4_ip_subnet, ipv4_mask2prefix, ipv4_prefix2mask, ipv4_ptonx, ipv6_addr_type, ipv6_ptonx, valid_ipv4 - functions to manipulate IP addresses
ipv4_ip_subnet, ipv4_mask2prefix, ipv4_prefix2mask, ipv4_ptonx, ipv6_addr_type, ipv6_ip_matches, ipv6_ptonx, valid_ipv4 - functions to manipulate IP addresses

# SYNOPSIS

Expand All @@ -13,12 +13,13 @@ ipv4_ip_subnet, ipv4_mask2prefix, ipv4_prefix2mask, ipv4_ptonx, ipv6_addr_type,
- ipv4_ptonx ipaddr

- ipv6_addr_type ipaddr
- ipv6_ip_matches ipaddr prefix
- ipv6_ptonx ipaddr

# DESCRIPTION

## ipv4_ip_subnet
Function checks that IP address is in subnet.
Function checks that IPv4 address is in subnet.

Example:
```
Expand Down Expand Up @@ -133,6 +134,23 @@ done
The intended purpose of *ipv6_addr_type* is to prevent misuse of special-use addresses.
For example, since link-local unicast addresses only make sense within a link and are incomplete without a scope identifier, it generally makes no sense to specify them in DNS AAAA records.

## ipv6_ip_matches
Checks that an IPv6 address is in a prefix (IOW, belongs in a range with common leading bits).

Example:
```
ipv6_ip_matches 3fff:e:b:1::2 3fff:e:b:1::/64; echo res=$?
res=0

ipv6_ip_matches 3fff:e:b:3::2 3fff:e:b:1::/64; echo res=$?
res=1

ipv6_ip_matches 3fff:e:b:3::2 3fff:e:b::/48; echo res=$?
res=0
```

Remember that an IPv6 prefix (netmask of leading 1 bits), unlike an IPv4 prefix, does not necessarily have routing significance; it might or might not be reachable "on-link" in any subnet.

## ipv6_ptonx
This function interprets the given option value as an IPv6 address similarly to inet_pton(3), and outputs each octet in network byte order as 16 adjacent 2-digit hexadecimal numbers.
This form is useful to perform bitwise operations on the address.
Expand Down
54 changes: 53 additions & 1 deletion shell-ip-address
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ __len2mask_32()
printf '%s' "$mask"
}

### Checks that IP address is in subnet
### Checks that IPv4 address is in subnet
### Usage example:
### ipv4_ip_subnet 172.16.1.2 172.16.1.0/24; echo res=$?
### res=0
Expand Down Expand Up @@ -345,4 +345,56 @@ ipv6_addr_type()
return 0
}

### Checks that IPv6 address is in a prefix.
### Usage example:
### ipv6_ip_matches 3fff:e:b:1::2 3fff:e:b:1::/64; echo res=$?
### res=0
###
### ipv6_ip_matches 3fff:e:b:3::2 3fff:e:b:1::/64; echo res=$?
### res=1
###
### ipv6_ip_matches 3fff:e:b:3::2 3fff:e:b::/48; echo res=$?
### res=0
ipv6_ip_matches()
{
local addr pref preflen
addr="${1-}"; shift
pref="${1-}"; shift
preflen="${pref##*/}"

[ -n "$preflen" ] ||
return 2
[ "$preflen" = 0 ] || shell_var_is_number "$preflen" ||
return 2
[ "$preflen" -le 128 ] ||
return 2

local hex_addr hex_pref hex_mask

hex_addr="$(ipv6_ptonx "$addr")" &&
hex_pref="$(ipv6_ptonx "${pref%%/*}")" ||
return 2

local i_addr i_pref w_addr w_pref
while [ "$preflen" -gt 32 ]; do
i_addr="${hex_addr#????????}"
i_pref="${hex_pref#????????}"
w_addr="${hex_addr%$i_addr}"
w_pref="${hex_pref%$i_pref}"
[ "$w_pref" = "$w_addr" ] ||
return 1

hex_addr="$i_addr"
hex_pref="$i_pref"
preflen="$(($preflen - 32))"
done
i_addr="${hex_addr#????????}"
i_pref="${hex_pref#????????}"
w_addr="${hex_addr%$i_addr}"
w_pref="${hex_pref%$i_pref}"
hex_mask="$(__len2mask_32 "$preflen")"
[ "$((0x$w_pref & $hex_mask))" -eq "$((0x$w_addr & $hex_mask))" ] ||
return 1
}

fi #__included_shell_ip_address
180 changes: 180 additions & 0 deletions tests/ip_address
Original file line number Diff line number Diff line change
Expand Up @@ -890,3 +890,183 @@ ip_address_test6t21() { # UnitTest
assertFalse "malformed address: $repr" $rc
assertNull "empty output" "$(ipv6_addr_type "$repr")"
}

ip_address_test_match600() { # UnitTest
. ../shell-ip-address

local rc=0
local addr=2001:7::23:443
local prefix=::/0
ipv6_ip_matches "$addr" "$prefix" || rc=1
assertTrue "matches 'default route': $addr" $rc
}

ip_address_test_match602() { # UnitTest
. ../shell-ip-address

local rc=0
local addr=::ffff:c000:2
local prefix=::/64
ipv6_ip_matches "$addr" "$prefix" || rc=1
assertTrue "enough common bits: $addr -> $prefix" $rc
}

ip_address_test_match603() { # UnitTest
. ../shell-ip-address

local rc=0
local addr=::ffff:c000:2
local prefix=::ffff:0:0/96
ipv6_ip_matches "$addr" "$prefix" || rc=1
assertTrue "$addr -> $prefix" $rc
}

ip_address_test_match604() { # UnitTest
. ../shell-ip-address

local rc=0
local addr=2001:db8:7:560a::c000:204
local prefix=2001:db8:7:5608::/61
ipv6_ip_matches "$addr" "$prefix" || rc=1
assertTrue "non-4bit-aligned prefix: $addr -> $prefix" $rc
}

ip_address_test_match605() { # UnitTest
. ../shell-ip-address

local rc=0
local addr=2001:db8:7:560a::c000:204
local prefix=2001:db8:7:5600::/60
ipv6_ip_matches "$addr" "$prefix" || rc=1
assertTrue "non-32bit-aligned prefix: $addr -> $prefix" $rc
}

ip_address_test_match606() { # UnitTest
. ../shell-ip-address

local rc=0
local addr=2001:db8:7:560a::c000:204
local prefix=2001:db8:7:5000::/52
ipv6_ip_matches "$addr" "$prefix" || rc=1
assertTrue "non-32bit-aligned prefix: $addr -> $prefix" $rc
}

ip_address_test_match607() { # UnitTest
. ../shell-ip-address

local rc=0
local addr=2001:db8:7:500a::c000:205
local prefix=2001:db8:7:500a::c000:204/127
ipv6_ip_matches "$addr" "$prefix" || rc=1
assertTrue "127-bit prefix: $addr -> $prefix" $rc
}

ip_address_test_match608() { # UnitTest
. ../shell-ip-address

local rc=0
local addr=2001:db8:7:500a::c000:204
local prefix=2001:db8:7:500a::c000:204/128
ipv6_ip_matches "$addr" "$prefix" || rc=1
assertTrue "128-bit prefix: $addr -> $prefix" $rc
}

ip_address_test_match616() { # UnitTest
. ../shell-ip-address

local rc=0
local addr=3fff:f:e:4::c
local prefix=3fff:f:e:4::/64
ipv6_ip_matches "$addr" "$prefix" || rc=1
assertTrue "$addr -> $prefix" $rc
}

ip_address_test_match617() { # UnitTest
. ../shell-ip-address

local rc=0
local addr=3fff:f:e:4::c
local prefix=3fff:f:e:4::/48
ipv6_ip_matches "$addr" "$prefix" || rc=1
assertTrue "$addr -> $prefix" $rc
}

ip_address_test_match620() { # UnitTest
. ../shell-ip-address

local rc=0
local addr=::ffff:c000:2
local prefix=::/96
ipv6_ip_matches "$addr" "$prefix" || rc=1
assertFalse "common bits too short: $addr -> $prefix" $rc
}

ip_address_test_match621() { # UnitTest
. ../shell-ip-address

local rc=0
local addr=2001::ffff:c000:2
local prefix=::ffff:0:0/96
ipv6_ip_matches "$addr" "$prefix" || rc=1
assertFalse "common low bits: $addr -> $prefix" $rc
}

ip_address_test_match624() { # UnitTest
. ../shell-ip-address

local rc=0
local addr=2001:db8:7:560a::c000:204
local prefix=2001:db8::/60
ipv6_ip_matches "$addr" "$prefix" || rc=1
assertFalse "! $addr -> $prefix" $rc
}

ip_address_test_match636() { # UnitTest
. ../shell-ip-address

local rc=0
local addr=3fff:f:e:4::c
local prefix=3fff:f:e:1::/64
ipv6_ip_matches "$addr" "$prefix" || rc=1
assertFalse "! $addr -> $prefix" $rc
}

ip_address_test_match640() { # UnitTest
. ../shell-ip-address

local rc=0
local addr=xxx
local prefix=2002::/16
ipv6_ip_matches "$addr" "$prefix" || rc=$?
assertEquals "garbage addr: $addr" 2 $rc
}

ip_address_test_match641() { # UnitTest
. ../shell-ip-address

local rc=0
local addr=2001:db8:7:e8::c0
local prefix=yyy/16
ipv6_ip_matches "$addr" "$prefix" || rc=$?
assertEquals "garbage prefix: $prefix" 2 $rc
}

ip_address_test_match642() { # UnitTest
. ../shell-ip-address

local rc=0
local addr=2001:db8:7:e8::c0
local prefix=3fff:1::/zzz
ipv6_ip_matches "$addr" "$prefix" || rc=$?
assertEquals "garbage prefix length: $prefix" 2 $rc
}

ip_address_test_match643() { # UnitTest
. ../shell-ip-address

local rc=0
local addr=2001:db8:7:e8::c0
local prefix=3fff:1::/100500
ipv6_ip_matches "$addr" "$prefix" || rc=$?
assertEquals "garbage prefix length: $prefix" 2 $rc
}