#!/usr/local/bin/cbsd
#v12.2.0
# Detect first available IPv6 from ippool's
CIXARG=""
CIXOPTARG="cleanup dhcpd_helper ip4pool lease_time lock pass dhcpd_ipv4_exclude"
MYDESC="Detect first available IPv4 from pools"
ADDHELP="

${H3_COLOR}Description${N0_COLOR}:

This script consistently suggests free IP addresses if the user does not specify
a static address. Can pass a request for any external script (see dhcpd.conf config).
Works with 'nodeippool' variable ranges by default (see 'cbsd initenv-tui').

 ${UNDERLINE}Return codes${N0_COLOR}:

   0 - IP found
   1 - Unknown error
   2 - All pools are exhausted

${H3_COLOR}Options${N0_COLOR}:

 ${N2_COLOR}cleanup=${N0_COLOR}      - flush leasetime for cleanup= list, e.g:
                 cleanup=\"10.0.0.1 10.0.0.2\";
 ${N2_COLOR}dhcpd_helper=${N0_COLOR} - <path_to_executable> overwrite dhcpd_helper settings
                 from dhcpd.conf;
 ${N2_COLOR}dhcpd_ipv4_exclude=${N0_COLOR} - Exclude/blacklist IPs;
 ${N2_COLOR}ip4pool=${N0_COLOR}      - use alternative pool, comma-separated if multiple
                 valid value sample:
                   ip4pool=\"192.168.0.0/24\"
                   ip4pool=\"192.168.0.5-10\"
                   ip4pool=\"10.0.0.3 10.0.0.4 10.0.0.250 10.0.0.251\"
                   ip4pool=\"10.0.0.2 10.0.0.3 10.0.0.200-204 192.168.0.20/29\";
 ${N2_COLOR}lease_time=${N0_COLOR}   - lock/lease for X seconds to avoid race/collisions
                 on concurrent request, default is: 30;
 ${N2_COLOR}lock=${N0_COLOR}         - set to '0' to prevent recursive/lock
                 (race/collisions protection);

${H3_COLOR}Examples${N0_COLOR}:

 # cbsd dhcpd
 # cbsd dhcpd ip4pool=\"192.168.0.5-10\"
 # cbsd dhcpd dhcpd_ipv4_exclude=\"192.168.0.5-10 10.0.0.1 10.0.0.254 192.168.0.20/29\"
 # cbsd dhcpd dhcpd_helper=\"/root/bin/myhelper\"

${H3_COLOR}See also${N0_COLOR}:

 # cbsd initenv-tui --help
 # cat ~cbsd/etc/defaults/dhcpd.conf
 External helper sample: https://www.convectix.com/en/13.0.x/wf_ipam_ssi.html

"

. ${subrdir}/nc.subr
lock=1
pass=
lease_time=30
cleanup=
dhcpd_helper=
dhcpd_ipv4_exclude=
odhcpd_ipv4_exclude=
. ${tools}
. ${strings}
cixinit
[ -n "${dhcpd_ipv4_exclude}" ] && odhcpd_ipv4_exclude="${dhcpd_ipv4_exclude=}"
[ -n "${dhcpd_helper}" ] && odhcpd_helper="${dhcpd_helper}"

# dhcpd_helper?
readconf dhcpd.conf

[ -n "${odhcpd_ipv4_exclude}" ] && dhcpd_ipv4_exclude="${odhcpd_ipv4_exclude=}"

#
# ipv4_to_ip10 ipv4 ip10
#	Function converts IPv4 address to decimal address. $1 must be IPv4
#	address. $2 must be name of variable to save decimal address.
#
ipv4_to_ip10()
{
	local __ipv4= __ip10=
	local _ip1= _ip2= _ip3= _ip4= _oct=

	if [ $# -ne 2 -o -z "$1" -o -z "$2" ]; then
		return 1
	fi

	__ipv4="$1"
	__ip10="$2"

	_ip1=${__ipv4%.*.*.*}
	_ip2=${__ipv4%.*.*}; _ip2=${_ip2#*.}
	_ip3=${__ipv4#*.*.}; _ip3=${_ip3%.*}
	_ip4=${__ipv4#*.*.*.}

	for _oct in "${_ip1}" "${_ip2}" "${_ip3}" "${_ip4}"; do
		if [ -z "${_oct}" -o "${_oct}" = "${__ipv4}" ]; then
			return 1
		fi

		if is_number "${_oct}"; then
			return 1
		fi

		if [ "${_oct}" -lt 0 -o "${_oct}" -gt "$(($pow8 - 1))" ]; then
			return 1
		fi
	done

	eval eval \${__ip10}=$((${_ip1} << 24 | ${_ip2} << 16 | ${_ip3} << 8 | ${_ip4}))

	return 0
}

#
# ip10_to_ipv4 ip10 ipv4
#	Function converts decimal address to IPv4 address. $1 must be decimal
#	address. $2 must be name of variable to save IPv4 address.
#
ip10_to_ipv4()
{
	local __ip10= __ipv4=
	local _ip1= _ip2= _ip3= _ip4=

	if [ $# -ne 2 -o -z "$1" -o -z "$2" ]; then
		return 1
	fi

	__ip10="$1"
	__ipv4="$2"

	if is_number "${__ip10}"; then
		return 1
	fi

	if [ "${__ip10}" -lt 0 -o "${__ip10}" -gt "$(($pow32 - 1))" ]; then
		return 1
	fi

	_ip1=$((((${__ip10} >> 24)) & 0xFF))
	_ip2=$((((${__ip10} >> 16)) & 0xFF))
	_ip3=$((((${__ip10} >> 8)) & 0xFF))
	_ip4=$((${__ip10} & 0xFF))

	eval eval \${__ipv4}="${_ip1}.${_ip2}.${_ip3}.${_ip4}"

	return 0
}

#
# is_ipv4_belong_net ipv4 network/mask
#	Function returns 0 if $1 address belongs $2 network, otherwise return 1.
#	$1 must be IPv4 address. $2 must be IPv4 network in CIDR notation.
#
is_ipv4_belong_net()
{
	local _ipv4= _network= _mask= _ip_net=
	local _ip10= _mask10= _ip_net10=

	if [ $# -ne 2 -o -z "$1" -o -z "$2" ]; then
		return 1
	fi

	_ipv4="$1"
	_net="$2"

	_network="${_net%/*}"
	_mask="${_net#*/}"

	if is_number "${_mask}"; then
		return 1
	fi

	if [ "${_mask}" -lt 0 -o "${_mask}" -gt 32 ]; then
		return 1
	fi

	if ! ipv4_to_ip10 "${_ipv4}" "_ip10"; then
		return 1
	fi

	_hosts=$((1 << $((32 - ${_mask}))))
	_mask10=$(($pow32 - ${_hosts}))
	_ip_net10=$((${_ip10} & ${_mask10}))
	if ! ip10_to_ipv4 "${_ip_net10}" "_ip_net"; then
		return 1
	fi

	if [ "${_ip_net}" != "${_network}" ]; then
		return 1
	fi

	return 0
}

#
# get_netmask network/mask netmask
#	Function generates IPv4 netmask from IPv4 network in CIDR notation.
#	$1 must be IPv4 network in CIDR notation. $2 must be name of variable
#	to save netmask.
#
get_netmask()
{
	local _net= _netmask= _network= _mask= _hosts= _mask_tmp=
	local _mask10=

	if [ $# -ne 2 -o -z "$1" -o -z "$2" ]; then
		return 1
	fi

	_net="$1"
	_netmask="$2"

	_network="${_net%/*}"
	_mask="${_net#*/}"

	if is_number "${_mask}"; then
		echo "NOT NUMBER: ${_mask}"
		return 1
	fi

	if [ "${_mask}" -lt 0 -o "${_mask}" -gt 32 ]; then
		return 1
	fi

	_hosts=$((1 << $((32 - ${_mask}))))
	_mask10=$(($pow32 - ${_hosts}))
	if ! ip10_to_ipv4 "${_mask10}" "_mask_tmp"; then
		return 1
	fi

	eval eval \${_netmask}="${_mask_tmp}"

	return 0
}

#
# get_network ipv4/mask network/mask
#	Function generates IPv4 network in CIDR notation from IPv4 address
#	with mask in CIDR notation. $1 must be IPv4 address with mask in CIDR
#	notation. $2 must be name of variable to save network.
#
get_network()
{
	local _ipv4mask= _network= _ipv4= _mask= _hosts= _network_tmp=
	local _ip10= _mask10= _network10=

	if [ $# -ne 2 -o -z "$1" -o -z "$2" ]; then
		return 1
	fi

	_ipv4mask="$1"
	_network="$2"

	_ipv4="${_ipv4mask%/*}"
	_mask="${_ipv4mask#*/}"

	if is_number "${_mask}"; then
		return 1
	fi

	if [ "${_mask}" -lt 0 -o "${_mask}" -gt 32 ]; then
		return 1
	fi

	if ! ipv4_to_ip10 "${_ipv4}" "_ip10"; then
		return 1
	fi
	_hosts=$((1 << $((32 - ${_mask}))))
	_mask10=$(($pow32 - ${_hosts}))
	_network10=$((${_ip10} & ${_mask10}))
	if ! ip10_to_ipv4 "${_network10}" "_network_tmp"; then
		return 1
	fi

	eval eval \${_network}="${_network_tmp}/${_mask}"

	return 0
}

#
# get_minhost network/mask minhost
#	Function generates minimum host for network. $1 must be IPv4 network
#	in CIDR notation. $2 must be name of variable to save minimum host.
#
get_minhost()
{
	local _net= _minhost= _network= _mask= _hosts= _minhost_tmp=
	local _network10= _mask10= _minhost10=

	if [ $# -ne 2 -o -z "$1" -o -z "$2" ]; then
		return 1
	fi

	_net="$1"
	_minhost="$2"

	_network="${_net%/*}"
	_mask="${_net#*/}"

	if is_number "${_mask}"; then
		return 1
	fi

	if [ "${_mask}" -lt 0 -o "${_mask}" -gt 32 ]; then
		return 1
	fi

	# Network with /31 mask has minimum host equal to network
	if [ "${_mask}" -eq 31 ]; then
		eval eval \${_minhost}="${_network}"
		return 0
	fi

	# Network with /32 mask has no minimum host
	if [ "${_mask}" -eq 32 ]; then
		return 0
	fi

	if ! ipv4_to_ip10 "${_network}" "_network10"; then
		return 1
	fi
	_hosts=$((1 << $((32 - ${_mask}))))
	_mask10=$(($pow32 - ${_hosts}))
	_minhost10=$((${_network10} + 1))

	if ! ip10_to_ipv4 "${_minhost10}" "_minhost_tmp"; then
		return 1
	fi

	eval eval \${_minhost}="${_minhost_tmp}"

	return 0
}

#
# get_maxhost network/mask maxhost
#	Function generates maximum host for network. $1 must be IPv4 network
#	in CIDR notation. $2 must be name of variable to save maximum host.
#
get_maxhost()
{
	local _reserve= _net= _maxhost= _network= _mask= _hosts= _maxhost_tmp=
	local _network10= _mask10= _maxhost10=

	if [ $# -ne 2 -o -z "$1" -o -z "$2" ]; then
		return 1
	fi

	_reserve=2

	_net="$1"
	_maxhost="$2"

	_network="${_net%/*}"
	_mask="${_net#*/}"

	if is_number "${_mask}"; then
		return 1
	fi

	if [ "${_mask}" -lt 0 -o "${_mask}" -gt 32 ]; then
		return 1
	fi

	if [ "${_mask}" -ge 31 ]; then
		_reserve=1
	fi

	if ! ipv4_to_ip10 "${_network}" "_network10"; then
		return 1
	fi
	_hosts=$((1 << $((32 - ${_mask}))))
	_mask10=$(($pow32 - ${_hosts}))
	_maxhost10=$((${_network10} + ${_hosts} - ${_reserve}))

	if ! ip10_to_ipv4 "${_maxhost10}" "_maxhost_tmp"; then
		return 1
	fi

	eval eval \${_maxhost}="${_maxhost_tmp}"

	return 0
}

#
# get_broadcast network/mask broadcast
#	Function generates broadcast address for network. $1 must be IPv4
#	network in CIDR notation. $2 must be name of variable to save
#	broadcast address.
#
get_broadcast()
{
	local _net= _broadcast= _network= _mask= _hosts= _broadcast_tmp=
	local _network10= _mask10= _broadcast10=

	if [ $# -ne 2 -o -z "$1" -o -z "$2" ]; then
		return 1
	fi

	_net="$1"
	_broadcast="$2"

	_network="${_net%/*}"
	_mask="${_net#*/}"

	if is_number "${_mask}"; then
		return 1
	fi

	if [ "${_mask}" -lt 0 -o "${_mask}" -gt 32 ]; then
		return 1
	fi

	# Network with /31 or /32 mask has no broadcast address
	if [ "${_mask}" -ge 31 ]; then
		return 0
	fi

	if ! ipv4_to_ip10 "${_network}" "_network10"; then
		return 1
	fi
	_hosts=$((1 << $((32 - ${_mask}))))
	_mask10=$(($pow32 - ${_hosts}))
	_broadcast10=$((${_network10} + ${_hosts} - 1))

	if ! ip10_to_ipv4 "${_broadcast10}" "_broadcast_tmp"; then
		return 1
	fi

	eval eval \${_broadcast}="${_broadcast_tmp}"

	return 0
}

# Export network calc result
# $netmask ( 255.255.255.0 )
# ${cidr_mask} ( 24 )
# $network ( 10.0.0.0/24 )
# $minhost ( 10.0.0.1 )
# $maxhost (  10.0.0.254 )
# $broadcast ( 10.0.0.255 )
# $numhosts ( 254 )
init_network()
{
	local _ip= _hosts= _reserve= _s1= _s2= _s3= _s4=
	local _is_cidr= _is_range= _pos= _s4s= _s4e=

	netmask=
	cidr_mask=
	network=
	minhost=
	maxhost=
	broadcast=

	[ $# -ne 1 ] && err 1 "dhcpd: init_network: usage: $0 ip.ip.ip.ip/mask"

	_ip=${1%/*}
	cidr_mask=${1#*/}

	_is_range=0	# has x.x.x.a-z ?
	_is_cidr=0	# has /prefix

	if [ "${_ip}" = "${1}" -o "${cidr_mask}" = "${1}" ]; then
		_is_cidr=0
	else
		_is_cidr=1
	fi

	sqllistdelimer="."
	sqllist "${_ip}" _s1 _s2 _s3 _s4
	sqllistdelimer=

	strpos --str="${_s4}" --search="-"
	_pos=$?
	if [ ${_pos} -eq 0 ]; then
		_is_range=0
	else
		_s4s=${_s4%-*}
		_s4e=${_s4#*-}
		[ -z "${_s4s}" -o -z "${_s4e}" ] && err 1 "dhcpd: init_network: last octet range error: ${_s4}"
		_is_range=1
	fi

#	[ ${_is_cidr} -eq 0 -a ${_is_range} -eq 0 ] && err 1 "dhcpd: init_network: usage: $0 ip.ip.ip.ip/mask or ip.ip.ip.ip1-ip2"

	if [ ${_is_cidr} -eq 1 ]; then
		if ! get_netmask "${_ip}/${cidr_mask}" "netmask"; then
			err 1 "dhcpd: init_network: bad netmask ${_ip}/${cidr_mask}"
		fi

		if ! get_network "${_ip}/${cidr_mask}" "network"; then
			err 1 "dhcpd: init_network: bad IP ${_ip}/${cidr_mask}"
		fi
		get_minhost "${network}" "minhost"
		get_maxhost "${network}" "maxhost"
		get_broadcast "${network}" "broadcast"
		_hosts=$((1 << $((32 - ${cidr_mask}))))
		_reserve=2
		if [ "${cidr_mask}" -ge 31 ]; then
			_reserve=0
		fi
		numhosts=$(( _hosts - _reserve ))
	elif [ ${_is_range} -eq 1 ]; then
		minhost="${_s1}.${_s2}.${_s3}.${_s4s}"
		maxhost="${_s1}.${_s2}.${_s3}.${_s4e}"
		numhosts=$(( _s4e - _s4s ))
	else
		# just IP sequence: 10.0.0.1 10.0.0.5 10.0.0.100 ..
		minhost="${_s1}.${_s2}.${_s3}.${_s4}"
		maxhost="${_s1}.${_s2}.${_s3}.${_s4}"
		numhosts=1
	fi

	return 0
}

# return 0 when $ip in $network
# where network, e.g:
#  192.168.0.2
#  192.168.0.0/29
#  192.168.0.0-10
ip_in_range()
{
	local _ip="${1}" _in="${2}"
	local _match=0
	local _e1= _e2= _e3= _e4=
 	local _s1= _s2= _s3= _s4=
	local _i1= _i2= _i3= _i4=
	local _tmp=

	# direct match
	[ "${_in}" = "${1}" ] && return 0

	# user ip range
	sqllistdelimer="."
	sqllist "${_ip}" _i1 _i2 _i3 _i4
	sqllistdelimer=

	# /prefix form
	eval $( ${miscdir}/sipcalc ${_in} )
	if [ -n "${_network_range_start}" -a -n "${_network_range_end}" ]; then

		# start range
		sqllistdelimer="."
		sqllist "${_network_range_start}" _s1 _s2 _s3 _s4
		sqllistdelimer=

		# end range
		sqllistdelimer="."
		sqllist "${_network_range_end}" _e1 _e2 _e3 _e4
		sqllistdelimer=


		if [ "${_i1}" = "${_s1}" -a "${_i2}" = "${_s2}" ]; then
			[ ${_i4} -gt ${_s4} -a ${_i4} -lt ${_e4} ] && return 0
		fi
	fi

	# start range
	sqllistdelimer="."
	sqllist "${_in}" _s1 _s2 _s3 _tmp
	sqllistdelimer=

	strpos --str="${_tmp}" --search="-"
	_pos=$?
	if [ ${_pos} -eq 0 ]; then
		return 1
	fi

	_s4=${_tmp%-*}
	_e4=${_tmp#*-}
	[ -z "${_s4}" -o -z "${_e4}" ] && return 1

	if [ "${_i1}" = "${_s1}" -a "${_i2}" = "${_s2}" ]; then
		[ ${_i4} -gt ${_s4} -a ${_i4} -lt ${_e4} ] && return 0
	fi

	return 1
}


LOCKFILE="${ftmpdir}/dhcpd.lock"
LEASE_FILE="${tmpdir}/dhcpd.lease"
# list of locked/skip IPS
LOCKFILE_SKIPLIST=

[ -n "${odhcpd_helper}" ] && dhcpd_helper="${odhcpd_helper}"

if [ "${dhcpd_helper}" != "internal" ]; then
	cbsdlogger NOTICE ${CIX_APP}: use external dhcpd_helper: ${dhcpd_helper}
	[ ! -x "${dhcpd_helper}" ] && log_err 1 "${N1_COLOR}${CIX_APP}: external helper not executable: ${N2_COLOR}${dhcpd_helper}${N0_COLOR}"

	# rebuild arg list ( + add pass )
	# Pass '"' as \" in cmd
	INIT_IFS="${IFS}"
	IFS="~"
	cmd="$@"
	IFS="${INIT_IFS}"
	cmd=$( while [ -n "${1}" ]; do
		IFS="~"
		strpos --str="${1}" --search="="
		_pos=$?
		if [ ${_pos} -eq 0 ]; then
			# not params=value form
			#printf "${1} "         # (printf handles -args (with dashes)
			#echo -n "${1} "
			shift
			continue
		fi
		capture _arg_len strlen "${1}"
		_pref=$(( _arg_len - _pos ))
		capture ARG substr --pos=0 --len=${_pos} --str="${1}"
		capture VAL substr --pos=$(( ${_pos} +2 )) --len=${_pref} --str="${1}"
		if [ -z "${ARG}" -o -z "${VAL}" ]; then
			shift
			continue
		fi
		printf "${ARG}='${VAL}' "
		shift
	done )

	cbsd_lock 60 ${LOCKFILE} ${dhcpd_helper} ${cmd}
	ret=$?
	# should never happen
	exit ${ret}
fi

if [ -n "${cleanup}" ]; then
	[ ! -r ${LEASE_FILE} ] && exit 0
	for i in ${cleanup}; do
		${ARP_CMD} -nd ${i} > /dev/null 2>&1
		${SED_CMD} -i${SED_DELIMER}'' "/${i}|/d" ${LEASE_FILE}
	done
	exit 0
fi


# we need the atomicity of the operation to exclude
# the simultaneous selection of the same free IP
# use file as lock and temp database in
# <ip>:<end_lease_time>
# <ip>:<end_lease_time>
if [ -z "${pass}" ]; then
	if [ "${lock}" = "1" ]; then
		# rebuild arg list ( + add pass )
		# Pass '"' as \" in cmd
		INIT_IFS="${IFS}"
		IFS="~"
		cmd="$@"
		IFS="${INIT_IFS}"
		while [ -n "${1}" ]; do
			IFS="~"
			strpos --str="${1}" --search="="
			_pos=$?
			if [ ${_pos} -eq 0 ]; then
				# not params=value form
				#printf "${1} "         # (printf handles -args (with dashes)
				#echo -n "${1} "
				shift
				continue
			fi
			capture _arg_len strlen "${1}"
			_pref=$(( _arg_len - _pos ))
			capture ARG substr --pos=0 --len=${_pos} --str="${1}"
			capture VAL substr --pos=$(( ${_pos} +2 )) --len=${_pref} --str="${1}"
			if [ -z "${ARG}" -o -z "${VAL}" ]; then
				shift
				continue
			fi
			#printf "${ARG}='${VAL}' "
			shift
		done
		cbsd_lock 60 ${LOCKFILE} /usr/local/bin/cbsd dhcpd ${cmd} pass=1
		ret=$?
		# should never happen
		exit ${ret}
	fi
fi

[ -n "${ip4pool}" ] && nodeippool=$( echo ${ip4pool} | ${TR_CMD} "," " " )

# prune/purge old records
if [ -r ${LEASE_FILE} ]; then
	${TRUNCATE_CMD} -s0 ${LEASE_FILE}.swap
	gettime cur_time
	eval $( ${CAT_CMD} ${LEASE_FILE} | while read items; do
		p1=${items%%|*}
		p2=${items##*|}
		[ -z "${p1}" -o -z "${p2}" ] && continue
		if is_number "${p2}"; then
			continue
		fi
		if [ ${p2} -gt ${cur_time} ]; then
			# still valid
			echo "${items}" >> ${LEASE_FILE}.swap
			if [ -z "${LOCKFILE_SKIPLIST}" ]; then
				LOCKFILE_SKIPLIST="${p1}"
			else
				LOCKFILE_SKIPLIST="${LOCKFILE_SKIPLIST} ${p1}"
			fi
		fi
	echo "LOCKFILE_SKIPLIST=\"${LOCKFILE_SKIPLIST}\""
	done )
	${MV_CMD} ${LEASE_FILE}.swap ${LEASE_FILE}
fi

### MAIN
pow32=$((1 << 32))
pow24=$((1 << 24))
pow16=$((1 << 16))
pow8=$((1 << 8))

# use args network instead of global CBSD settings/variable
#if [ -n "${ip4pool}" ]; then
#	nodeippool=$( echo ${ip4pool} | ${TR_CMD} "," " " )
#fi

[ -z "${nodeippool}" ] && err 1 "${N1_COLOR}no nodeippool${N0_COLOR}"
[ "${nodeippool}" = "0" ] && err 1 "${N1_COLOR}no nodeippool${N0_COLOR}"
[ "${nodeippool}" = "(null)" ] && err 1 "${N1_COLOR}no nodeippool${N0_COLOR}"

# Multiple network loop
for tmpnet in ${nodeippool}; do

	init_network "${tmpnet}"

	# start octets
	s1=
	s2=
	s3=
	s4=

	# end octets
	e1=
	e2=
	e3=
	e4=

	sqllistdelimer="."
	sqllist "${minhost}" s1 s2 s3 s4
	sqllist "${maxhost}" e1 e2 e3 e4
	sqllistdelimer=

	# bhyve doesn't have ip4_addr anymore, selectom from jails only
	existing_ipjail=$( cbsdsqlro local "SELECT DISTINCT ip4_addr FROM jails WHERE ip4_addr != '0' AND ip4_addr != 'DHCP'" | ${TR_CMD} "," " " | ${XARGS_CMD} )
#	cbsdsqlro_vars local "SELECT DISTINCT ip4_addr FROM jails WHERE ip4_addr != '0' AND ip4_addr != 'DHCP'"

	skip_ip="${nodeip}"

	# prepare skip ip list
	for i in ${existing_ipjail}; do
		ipwmask ${i}
		[ -z "${IWM}" ] && continue
		iptype ${IWM}
		[ $? -eq 1 ] && skip_ip="${skip_ip} ${IWM}"
	done

	found=0

	[ -z "${s1}" -o -z "${e2}" ] && break

	# assign wanted octet by starting octet
	for ((w1=${s1}; w1<=${e1}; w1++)); do
		[ -z "${s2}" -o -z "${e2}" ] && break
		for ((w2=${s2}; w2<=${e2}; w2++)); do
			[ -z "${s3}" -o -z "${e3}" ] && break
			for ((w3=${s3}; w3<=${e3}; w3++)); do
				# last wanted octet loop
				[ -z "${s4}" -o -z "${e4}" ] && break
				for ((w4=${s4}; w4<=${e4}; w4++)); do
					skip=0
					tmpip="${w1}.${w2}.${w3}.${w4}"
					iptype ${tmpip} >/dev/null 2>&1
					[ $? -ne 1 ] && continue

					for i in ${dhcpd_ipv4_exclude}; do
						if ip_in_range ${tmpip} ${i}; then
							skip=1
						fi
					done
					if [ ${skip} -eq 0 ]; then
						for n in ${skip_ip} ${LOCKFILE_SKIPLIST}; do
							[ "${n}" = "${tmpip}" ] && skip=1
						done
					fi
					[ ${skip} -eq 1 ] && continue
					# regulate via conf ?
					${ARP_CMD} -dn ${tmpip} > /dev/null 2>&1
					case "${platform}" in
						Linux|NetBSD)
							checkip ip="${tmpip}" check=1
							_ret=$?
							if [ ${_ret} -eq 2 ]; then
								# found
								_ret=1
							else
								_ret=0
							fi
							;;
						DragonFly)
							checkip ip="${tmpip}" check=1
							_ret=$?
							if [ ${_ret} -eq 2 ]; then
								# found
								_ret=1
							else
								_ret=0
							fi
							;;
						*)
							${miscdir}/chk_arp_byip --pingnum=1 --pingtimeout=0.001 --ip=${tmpip}
							_ret=$?
							;;
					esac
					case ${_ret} in
						0)
							found=1
							#break 4
							break
							;;
						*)
							continue
							;;
					esac
				done # w4
				[ ${found} -eq 1 ] && break
			done # w3
			[ ${found} -eq 1 ] && break
		[ ${found} -eq 1 ] && break
		done # w2
	[ ${found} -eq 1 ] && break
	done # w1
[ ${found} -eq 1 ] && break
done # multiple network

if [ -n "${tmpip}" -a ${found} -eq 1 ]; then
	if [ -n "${mycidr}" ]; then
		cbsdlogger NOTICE ${CIX_APP}: found next available IP: ${tmpip}/${mycidr}
		echo "${tmpip}/${mycidr}"
	else
		cbsdlogger NOTICE ${CIX_APP}: found next available IP: ${tmpip}
		echo ${tmpip}
	fi
else
	cbsdlogger WARNING ${CIX_APP}: no free IP: all pools are exhausted? ${nodeippool}
	# looks like all pools are exhausted
	exit 2
fi

gettime cur_time
lease_time_end=$(( cur_time + lease_time ))
echo "${tmpip}|${lease_time_end}" >> ${LEASE_FILE}

exit 0
