From 854739b32c7f2e56e5b79ee049749e60faece8f5 Mon Sep 17 00:00:00 2001 From: Philip Prindeville Date: Sun, 22 Oct 2023 13:27:50 -0600 Subject: [PATCH] base-files: ipcalc.sh: Rewrite in pure shell Also add better error checking on input. Signed-off-by: Philip Prindeville --- package/base-files/files/bin/ipcalc.sh | 199 +++++++++--------- .../base-files/files/lib/functions/ipv4.sh | 140 ++++++++++++ 2 files changed, 244 insertions(+), 95 deletions(-) create mode 100644 package/base-files/files/lib/functions/ipv4.sh diff --git a/package/base-files/files/bin/ipcalc.sh b/package/base-files/files/bin/ipcalc.sh index 9b5e5accdc..0bd6edd07a 100755 --- a/package/base-files/files/bin/ipcalc.sh +++ b/package/base-files/files/bin/ipcalc.sh @@ -1,100 +1,109 @@ -#!/usr/bin/awk -f - -function bitcount(c) { - c=and(rshift(c, 1),0x55555555)+and(c,0x55555555) - c=and(rshift(c, 2),0x33333333)+and(c,0x33333333) - c=and(rshift(c, 4),0x0f0f0f0f)+and(c,0x0f0f0f0f) - c=and(rshift(c, 8),0x00ff00ff)+and(c,0x00ff00ff) - c=and(rshift(c,16),0x0000ffff)+and(c,0x0000ffff) - return c -} +#!/bin/sh -function ip2int(ip) { - ret=0 - n=split(ip,a,"\\.") - for (x=1;x<=n;x++) - ret=or(lshift(ret,8),a[x]) - return ret -} +. /lib/functions/ipv4.sh -function int2ip(ip,ret,x) { - ret=and(ip,255) - ip=rshift(ip,8) - for(;x<3;x++) { - ret=and(ip,255)"."ret - ip=rshift(ip,8) - } - return ret -} +PROG="$(basename "$0")" -function compl32(v) { - ret=xor(v, 0xffffffff) - return ret +usage() { + echo "Usage: $PROG address/prefix [ start limit ]" >&2 + exit 1 } -BEGIN { - slpos=index(ARGV[1],"/") - if (slpos != 0) { - # rearrange arguments to not use compound notation - ARGV[4]=ARGV[3] - ARGV[3]=ARGV[2] - ARGV[2]=substr(ARGV[1],slpos+1) - ARGV[1]=substr(ARGV[1],0,slpos-1) - } - ipaddr=ip2int(ARGV[1]) - dotpos=index(ARGV[2],".") - if (dotpos == 0) - netmask=compl32(2**(32-int(ARGV[2]))-1) - else - netmask=ip2int(ARGV[2]) - - network=and(ipaddr,netmask) - prefix=32-bitcount(compl32(netmask)) - - print "IP="int2ip(ipaddr) - print "NETMASK="int2ip(netmask) - print "NETWORK="int2ip(network) - if (prefix<=30) { - broadcast=or(network,compl32(netmask)) - print "BROADCAST="int2ip(broadcast) - } - print "PREFIX="prefix - - # range calculations: - # ipcalc - - if (ARGC <= 3) - exit(0) - - if (prefix<=30) - limit=network+1 - else - limit=network - - start=or(network,and(ip2int(ARGV[3]),compl32(netmask))) - if (startlimit) end=limit - if (end==ipaddr) end=ipaddr-1 - - if (start>end) { - print "network ("int2ip(network)"/"prefix") too small" > "/dev/stderr" - exit(1) - } - - if (ipaddr >= start && ipaddr <= end) { - print "warning: ipaddr inside range - this might not be supported in future releases of Openwrt" > "/dev/stderr" - # turn this into an error after Openwrt 24 has been released - # exit(1) - } - - print "START="int2ip(start) - print "END="int2ip(end) -} +if [ $# -eq 0 ]; then + usage +fi + +case "$1" in +*/*.*) + str2ip ipaddr "${1%/*}" || exit 1 + str2ip netmask "${1#*/}" || exit 1 + shift + ;; +*/*) + str2ip ipaddr "${1%/*}" || exit 1 + prefix="${1#*/}" + assert_uint32 "$prefix" || exit 1 + if [ "$prefix" -gt 32 ]; then + printf "Prefix out of range (%s)\n" "$prefix" >&2 + exit 1 + fi + netmask=$(((0xffffffff << (32 - prefix)) & 0xffffffff)) + shift + ;; +*) + str2ip ipaddr "$1" || exit 1 + str2ip netmask "$2" || exit 1 + shift 2 + ;; +esac + +if [ $# -ne 0 ] && [ $# -ne 2 ]; then + usage +fi + +if ! bitcount prefix "$netmask"; then + printf "Invalid netmask (%s)\n" "$netmask" >&2 + exit 1 +fi + +# complement of the netmask, i.e. the hostmask +hostmask=$((netmask ^ 0xffffffff)) +network=$((ipaddr & netmask)) +broadcast=$((network | hostmask)) + +ip2str IP "$ipaddr" +ip2str NETMASK "$netmask" +ip2str NETWORK "$network" + +echo "IP=$IP" +echo "NETMASK=$NETMASK" +if [ "$prefix" -le 30 ]; then + ip2str BROADCAST "$broadcast" + echo "BROADCAST=$BROADCAST" +fi +echo "NETWORK=$NETWORK" +echo "PREFIX=$prefix" + +[ $# -eq 0 ] && exit 0 + +if [ "$prefix" -le 30 ]; then + lower=$((network + 1)) +else + lower="$network" +fi + +start="$1" +assert_uint32 "$start" || exit 1 +start=$((network | (start & hostmask))) +[ "$start" -lt "$lower" ] && start="$lower" +[ "$start" -eq "$ipaddr" ] && start=$((start + 1)) + +if [ "$prefix" -le 30 ]; then + upper=$(((network | hostmask) - 1)) +else + upper="$network" +fi + +range="$2" +assert_uint32 "$range" || exit 1 +end=$((start + range - 1)) +[ "$end" -gt "$upper" ] && end="$upper" +[ "$end" -eq "$ipaddr" ] && end=$((end - 1)) + +if [ "$start" -gt "$end" ]; then + echo "network ($NETWORK/$prefix) too small" >&2 + exit 1 +fi + +ip2str START "$start" +ip2str END "$end" + +if [ "$start" -le "$ipaddr" ] && [ "$ipaddr" -le "$end" ]; then + echo "error: address $IP inside range $START..$END" >&2 + exit 1 +fi + +echo "START=$START" +echo "END=$END" + +exit 0 diff --git a/package/base-files/files/lib/functions/ipv4.sh b/package/base-files/files/lib/functions/ipv4.sh new file mode 100644 index 0000000000..30ae480305 --- /dev/null +++ b/package/base-files/files/lib/functions/ipv4.sh @@ -0,0 +1,140 @@ +uint_max=4294967295 + +assert_uint32() { + local __n="$1" + + if [ -z "$__n" -o -n "${__n//[0-9]/}" ]; then + printf "Not a decimal integer (%s)\n" "$__n ">&2 + return 1 + fi + + if [ "$__n" -gt $uint_max ]; then + printf "Out of range (%s)\n" "$__n" >&2 + return 1 + fi + + if [ "$((__n + 0))" != "$__n" ]; then + printf "Not normalized notation (%s)\n" "$__n" >&2 + return 1 + fi + + return 0 +} + +bitcount() { + local __var="$1" __c="$2" + assert_uint32 "$__c" || return 1 + + __c=$((((__c >> 1) & 0x55555555) + (__c & 0x55555555))) + __c=$((((__c >> 2) & 0x33333333) + (__c & 0x33333333))) + __c=$((((__c >> 4) & 0x0f0f0f0f) + (__c & 0x0f0f0f0f))) + __c=$((((__c >> 8) & 0x00ff00ff) + (__c & 0x00ff00ff))) + __c=$((((__c >> 16) & 0x0000ffff) + (__c & 0x0000ffff))) + + export -- "$__var=$__c" +} + +# tedious but portable with busybox's limited shell +str2ip() { + local __var="$1" __ip="$2" __n __val=0 + + case "$__ip" in + [0-9].*) + __n="${__ip:0:1}" + __ip="${__ip:2}" + ;; + [1-9][0-9].*) + __n="${__ip:0:2}" + __ip="${__ip:3}" + ;; + 1[0-9][0-9].*|2[0-4][0-9].*|25[0-5].*) + __n="${__ip:0:3}" + __ip="${__ip:4}" + ;; + *) + printf "Not a dotted quad (%s)\n" "$2" >&2 + return 1 + ;; + esac + + __val=$((__n << 24)) + + case "$__ip" in + [0-9].*) + __n="${__ip:0:1}" + __ip="${__ip:2}" + ;; + [1-9][0-9].*) + __n="${__ip:0:2}" + __ip="${__ip:3}" + ;; + 1[0-9][0-9].*|2[0-4][0-9].*|25[0-5].*) + __n="${__ip:0:3}" + __ip="${__ip:4}" + ;; + *) + printf "Not a dotted quad (%s)\n" "$2" >&2 + return 1 + ;; + esac + + __val=$((__val + (__n << 16))) + + case "$__ip" in + [0-9].*) + __n="${__ip:0:1}" + __ip="${__ip:2}" + ;; + [1-9][0-9].*) + __n="${__ip:0:2}" + __ip="${__ip:3}" + ;; + 1[0-9][0-9].*|2[0-4][0-9].*|25[0-5].*) + __n="${__ip:0:3}" + __ip="${__ip:4}" + ;; + *) + printf "Not a dotted quad (%s)\n" "$2" >&2 + return 1 + ;; + esac + + __val=$((__val + (__n << 8))) + + case "$__ip" in + [0-9]) + __n="${__ip:0:1}" + __ip="${__ip:1}" + ;; + [1-9][0-9]) + __n="${__ip:0:2}" + __ip="${__ip:2}" + ;; + 1[0-9][0-9]|2[0-4][0-9]|25[0-5]) + __n="${__ip:0:3}" + __ip="${__ip:3}" + ;; + *) + printf "Not a dotted quad (%s)\n" "$2" >&2 + return 1 + ;; + esac + + __val=$((__val + __n)) + + if [ -n "$__ip" ]; then + printf "Not a dotted quad (%s)\n" "$2" >&2 + return 1 + fi + + export -- "$__var=$__val" + return 0 +} + +ip2str() { + local __var="$1" __n="$2" + assert_uint32 "$__n" || return 1 + + export -- "$__var=$((__n >> 24)).$(((__n >> 16) & 255)).$(((__n >> 8) & 255)).$((__n & 255))" +} + -- 2.30.2