网络聚合命令
网络配置命令(IP/网关/DNS/NTP/聚合)
逻辑:备份 → 写配置文件 → NTP → 重启网络生效 → 输出。不读旧配置内容。
网口:IFACE='*'自动探测(优先 bond/team > eth > ens/enp,排除 docker/k8s 等)。
Ubuntu netplan:备份后重写原 yaml(含该网口的文件,否则取第一个),删除其余 yaml 避免冲突。
CentOS/Rocky:写最小 ifcfg(仅 IP/网关/DNS)→ 配 NTP → 生效。CentOS 7 优先nmcli device reapply读 ifcfg;无 NM 时用restart network;兜底用ip命令。
DNS:默认留空,空时自动使用 GW 作为 DNS。
说明:以下四条命令均 ≤2000 字符,可直接复制;IP/GW各一行,其余参数共一行。
1. Ubuntu/Debian 单网口(netplan)
sudo bash <<'EOF'
IP='10.10.10.10'
GW='10.10.10.254'
DNS='';NTP='';MASK='24';IF='*'
[ -z "$DNS" ]&&DNS=$GW
P(){ ip -br -4 a|awk '$1!~"^(lo|docker|veth|kube|cni|br-|flannel)"&&$2=="UP"&&$3~/\//{print($1~/^bond/?100:$1~/^eth/?90:$1~/^ens/?80:10),$1}'|sort -rn|head -1|awk '{print $2}'; }
[ "$IF"="*" ]&&IF=$(P);[ -n "$IF" ]||{ echo FAIL:iface;exit 1; }
echo "CFG: $IF $IP/$MASK gw=$GW dns=$DNS${NTP:+ ntp=$NTP}"
D=/root/nb-$(date +%Y%m%d%H%M%S);mkdir -p $D;cp -a /etc/netplan $D/ 2>/dev/null&&echo OK:backup:$D||echo FAIL:backup
shopt -s nullglob;Y=(/etc/netplan/*.yaml);T=$(printf '%s\n' "${Y[@]}"|sort|head -1)
for f in "${Y[@]}";do grep -q " $IF:" "$f"&&T=$f;done;[ -z "$T" ]&&T=/etc/netplan/01-netcfg.yaml
R=$(grep -h renderer: $D/netplan/*.yaml 2>/dev/null|awk '{print $2}'|head -1);R=${R:-networkd}
for f in /etc/netplan/*.yaml;do [ "$f"!="$T" ]&&rm -f "$f";done;shopt -u nullglob
cat>$T<<E
network:
version: 2
renderer: $R
ethernets:
$IF:
dhcp4: no
addresses: [$IP/$MASK]
routes: [{to: default, via: $GW}]
nameservers: {addresses: [$DNS]}
E
echo OK:file:$T
[ -n "$NTP" ]&&{ grep -q '^\[Time\]' /etc/systemd/timesyncd.conf||echo '[Time]'>>/etc/systemd/timesyncd.conf; sed -i "s/^NTP=.*/NTP=$NTP/" /etc/systemd/timesyncd.conf 2>/dev/null||sed -i "/^\[Time\]/a NTP=$NTP" /etc/systemd/timesyncd.conf; systemctl restart systemd-timesyncd 2>/dev/null; systemctl is-active systemd-timesyncd&>/dev/null&&echo OK:ntp||echo FAIL:ntp; }||echo SKIP:ntp
netplan apply&&echo OK:apply||{ echo FAIL:apply;exit 1; }
a=$(ip -4 -o a show dev $IF|awk '{print $4}'|head -1);g=$(ip route show default|awk '{print $3}'|head -1)
[ "$a"="$IP/$MASK" ]&&echo OK:ip||echo FAIL:ip:$a;[ "$g"="$GW" ]&&echo OK:gw||echo FAIL:gw:$g
EOF
2. CentOS/Rocky 单网口(ifcfg)
sudo bash <<'EOF'
IP='10.10.10.10'
GW='10.10.10.254'
DNS='';NTP='';MASK='24';IF='*'
[ -z "$DNS" ]&&DNS=$GW
P(){ ip -br -4 a|awk '$1!~"^(lo|docker|veth|kube|cni|br-|flannel)"&&$2=="UP"&&$3~/\//{print($1~/^bond/?100:$1~/^team/?100:$1~/^eth/?90:$1~/^ens/?80:10),$1}'|sort -rn|head -1|awk '{print $2}'; }
[ "$IF"="*" ]&&IF=$(P);[ -n "$IF" ]||{ echo FAIL:iface;exit 1; }
echo "CFG: $IF $IP/$MASK gw=$GW dns=$DNS${NTP:+ ntp=$NTP}"
D=/root/nb-$(date +%Y%m%d%H%M%S);mkdir -p $D;cp -a /etc/sysconfig/network-scripts/ifcfg-* $D/ 2>/dev/null&&echo OK:backup:$D||echo FAIL:backup
cat>/etc/sysconfig/network-scripts/ifcfg-$IF<<C
DEVICE=$IF
ONBOOT=yes
BOOTPROTO=none
IPADDR=$IP
PREFIX=$MASK
GATEWAY=$GW
DNS1=$DNS
C
echo OK:file:ifcfg-$IF
[ -n "$NTP" ]&&{ mkdir -p /etc/chrony.d;echo "server $NTP iburst">/etc/chrony.d/99-custom.conf;systemctl restart chronyd 2>/dev/null; pgrep chronyd&>/dev/null&&echo OK:ntp||echo FAIL:ntp; }||echo SKIP:ntp
if nmcli dev status&>/dev/null;then nmcli con reload 2>/dev/null;nmcli dev reapply $IF&&echo OK:apply||echo FAIL:apply;else systemctl restart network 2>/dev/null&&echo OK:apply||{ ip link set $IF up;ip addr flush dev $IF 2>/dev/null;ip addr add $IP/$MASK dev $IF;ip route replace default via $GW dev $IF; echo OK:apply:ip; };fi
a=$(ip -4 -o a show dev $IF|awk '{print $4}'|head -1);g=$(ip route show default|awk '{print $3}'|head -1)
[ "$a"="$IP/$MASK" ]&&echo OK:ip||echo FAIL:ip:$a;[ "$g"="$GW" ]&&echo OK:gw||echo FAIL:gw:$g
EOF
3. Ubuntu/Debian 双网口聚合 bond0(netplan)
sudo bash <<'EOF'
IP='10.10.10.10'
GW='10.10.10.254'
DNS='';NTP='';MASK='24';S1='*';S2='*';BO=bond0
[ -z "$DNS" ]&&DNS=$GW
L(){ ip -br link|awk '$1!~"^(lo|docker|veth|kube|cni|br-|bond|team)"&&$2=="UP"{print($1~/^eth/?90:$1~/^ens/?80:50),$1}'|sort -rn|awk '{print $2}'; }
if [ "$S1"="*" ];then
[ -r /sys/class/net/$BO/bonding/slaves ]&&read S1 S2 _ </sys/class/net/$BO/bonding/slaves
[ -z "$S1" ]&&{ A=($(L));S1=${A[0]};S2=${A[1]}; }
[ -n "$S1" ]&&[ -n "$S2" ]||{ echo FAIL:iface;exit 1; }
fi
echo "CFG: $BO($S1+$S2) $IP/$MASK gw=$GW dns=$DNS${NTP:+ ntp=$NTP}"
D=/root/nb-$(date +%Y%m%d%H%M%S);mkdir -p $D;cp -a /etc/netplan $D/ 2>/dev/null&&echo OK:backup:$D||echo FAIL:backup
shopt -s nullglob;Y=(/etc/netplan/*.yaml);T=$(printf '%s\n' "${Y[@]}"|sort|head -1)
for f in "${Y[@]}";do grep -qE "bonds:| $BO:" "$f"&&T=$f;done
R=$(grep -h renderer: $D/netplan/*.yaml 2>/dev/null|awk '{print $2}'|head -1);R=${R:-networkd}
for f in /etc/netplan/*.yaml;do [ "$f"!="$T" ]&&rm -f "$f";done;shopt -u nullglob
cat>$T<<E
network:
version: 2
renderer: $R
ethernets:
$S1: {dhcp4: no}
$S2: {dhcp4: no}
bonds:
$BO:
dhcp4: no
interfaces: [$S1, $S2]
parameters: {mode: active-backup}
addresses: [$IP/$MASK]
routes: [{to: default, via: $GW}]
nameservers: {addresses: [$DNS]}
E
echo OK:file:$T
[ -n "$NTP" ]&&{ grep -q '^\[Time\]' /etc/systemd/timesyncd.conf||echo '[Time]'>>/etc/systemd/timesyncd.conf; sed -i "s/^NTP=.*/NTP=$NTP/" /etc/systemd/timesyncd.conf 2>/dev/null||sed -i "/^\[Time\]/a NTP=$NTP" /etc/systemd/timesyncd.conf; systemctl restart systemd-timesyncd 2>/dev/null; systemctl is-active systemd-timesyncd&>/dev/null&&echo OK:ntp||echo FAIL:ntp; }||echo SKIP:ntp
netplan apply&&echo OK:apply||{ echo FAIL:apply;exit 1; }
a=$(ip -4 -o a show dev $BO|awk '{print $4}'|head -1);g=$(ip route show default|awk '{print $3}'|head -1)
[ "$a"="$IP/$MASK" ]&&echo OK:ip||echo FAIL:ip:$a;[ "$g"="$GW" ]&&echo OK:gw||echo FAIL:gw:$g
EOF
4. CentOS/Rocky 双网口聚合 team0(ifcfg)
前置:yum install -y teamd NetworkManager-team
sudo bash <<'EOF'
IP='10.10.10.10'
GW='10.10.10.254'
DNS='';NTP='';MASK='24';S1='*';S2='*';TM=team0
[ -z "$DNS" ]&&DNS=$GW
L(){ ip -br link|awk '$1!~"^(lo|docker|veth|kube|cni|br-|bond|team)"&&$2=="UP"{print($1~/^eth/?90:$1~/^ens/?80:50),$1}'|sort -rn|awk '{print $2}'; }
if [ "$S1"="*" ];then
[ -d /sys/class/net/$TM ]&&{ A=($(ls /sys/class/net/$TM/lower_* 2>/dev/null|xargs -n1 basename|sed 's/^lower_//')); S1=${A[0]};S2=${A[1]}; }
[ -z "$S1" ]&&{ A=($(L));S1=${A[0]};S2=${A[1]}; }
[ -n "$S1" ]&&[ -n "$S2" ]||{ echo FAIL:iface;exit 1; }
fi
echo "CFG: $TM($S1+$S2) $IP/$MASK gw=$GW dns=$DNS${NTP:+ ntp=$NTP}"
D=/root/nb-$(date +%Y%m%d%H%M%S);mkdir -p $D;cp -a /etc/sysconfig/network-scripts/ifcfg-* $D/ 2>/dev/null&&echo OK:backup:$D||echo FAIL:backup
cat>/etc/sysconfig/network-scripts/ifcfg-$TM<<C
DEVICE=$TM
DEVICETYPE=Team
TEAM_CONFIG={"runner": {"name": "activebackup"}}
BOOTPROTO=none
ONBOOT=yes
IPADDR=$IP
PREFIX=$MASK
GATEWAY=$GW
DNS1=$DNS
C
for S in $S1 $S2;do cat>/etc/sysconfig/network-scripts/ifcfg-$S<<C
DEVICE=$S
DEVICETYPE=TeamPort
TEAM_MASTER=$TM
BOOTPROTO=none
ONBOOT=yes
C
done
echo OK:file:ifcfg-$TM,$S1,$S2
[ -n "$NTP" ]&&{ mkdir -p /etc/chrony.d;echo "server $NTP iburst">/etc/chrony.d/99-custom.conf;systemctl restart chronyd 2>/dev/null; pgrep chronyd&>/dev/null&&echo OK:ntp||echo FAIL:ntp; }||echo SKIP:ntp
if nmcli dev status&>/dev/null;then nmcli con reload 2>/dev/null;nmcli dev reapply $TM&&echo OK:apply||echo FAIL:apply;else systemctl restart network&&echo OK:apply||echo FAIL:apply;fi
a=$(ip -4 -o a show dev $TM|awk '{print $4}'|head -1);g=$(ip route show default|awk '{print $3}'|head -1)
[ "$a"="$IP/$MASK" ]&&echo OK:ip||echo FAIL:ip:$a;[ "$g"="$GW" ]&&echo OK:gw||echo FAIL:gw:$g
EOF
主机与网络信息查看命令
一键输出主机 OS、物理机/虚拟机、网卡类型,以及业务网卡的 IP、掩码、网关、DNS、NTP、MAC 等网络配置,用于现场排查或填写网络工单。自动识别业务网卡(优先 bond > eth > ens/enp),排除 Docker、K8s、Flannel 等内部网卡;可通过 IFACE 指定接口。DNS 依次尝试 NetworkManager、systemd-resolved、resolv.conf、ifcfg;NTP 识别 chronyd / ntpd / systemd-timesyncd,未显式配置时标注系统默认值。
sudo -E bash <<'EOF'
set -eo pipefail
IFACE='*'
_os(){ . /etc/os-release 2>/dev/null && echo "${PRETTY_NAME:-$NAME} | kernel $(uname -r)" || uname -sr; }
_host(){
v=$(systemd-detect-virt 2>/dev/null | head -1 | tr -d ' \t\r\n' || true)
s=$(cat /sys/class/dmi/id/sys_vendor 2>/dev/null || echo -)
p=$(cat /sys/class/dmi/id/product_name 2>/dev/null || echo -)
if [ "$v" = none ] || [ -z "$v" ]; then
case "$s $p" in *[Vv]irtual*|*VMware*|*KVM*|*QEMU*|*Hyper-V*|*Xen*|*OpenStack*) echo "VM | $s $p";; *) echo "physical | $s $p";; esac
else echo "VM ($v) | $s $p"; fi
}
_nic(){
if=$1; [ -r "/sys/class/net/$if/bonding/slaves" ] && { echo "$if | bonded NIC (bonding: $(tr ' ' '+' < /sys/class/net/$if/bonding/slaves))"; return; }
d=$(ethtool -i "$if" 2>/dev/null | awk '/^driver:/{print $2;exit}')
case "$d" in virtio_net|vmxnet3|veth|tap|tun|dummy) echo "$if | virtual NIC ($d)";; ""|unknown) echo "$if | unknown";; *) echo "$if | physical NIC ($d)";; esac
}
_pick(){ ip -br -4 a 2>/dev/null | awk '
$1!~"^(lo|docker|cni|flannel|nodelocaldns|veth|kube|calico|tun|tap|virbr|br-|podman)"&&$2=="UP"&&$3~/\//{
ip=$3;sub(/\/.*/,"",ip); if(ip~/^10\.|^192\.168\.|^172\.(1[6-9]|2[0-9]|3[0-1])\./) print ($1~/^bond/?100:$1~/^eth/?90:$1~/^(ens|enp)/?80:10),$1,$3
}' | sort -rn | head -1 | awk '{print $2,$3}'; }
_dns(){
if=$1; ns=""; dom=""; src=""
if command -v nmcli &>/dev/null; then
ns=$(nmcli -t -f IP4.DNS dev show "$if" 2>/dev/null | awk -F: '$2~/^[0-9]/{print $2}' | sort -u | paste -sd ', ' -)
dom=$(nmcli -t -f IP4.DOMAIN dev show "$if" 2>/dev/null | awk -F: '$2!=""{print $2}' | paste -sd ', ' -)
[ -n "$ns" ] && src=NetworkManager
fi
if [ -z "$ns" ] && command -v resolvectl &>/dev/null; then
ns=$(resolvectl dns "$if" 2>/dev/null | grep -oE '([0-9]{1,3}\.){3}[0-9]{1,3}' | sort -u | paste -sd ', ' -)
[ -z "$dom" ] && dom=$(resolvectl domain "$if" 2>/dev/null | awk '{$1="";gsub(/^ +/,"");if($0&&$0!~"~.")print}' | paste -sd ', ' -)
[ -n "$ns" ] && src=systemd-resolved
fi
if [ -z "$ns" ]; then
ns=$(awk '/^nameserver /{print $2}' /etc/resolv.conf 2>/dev/null | grep -Ev '^127\.|^::1' | paste -sd ', ' -)
[ -n "$ns" ] && src=/etc/resolv.conf
fi
if [ -z "$ns" ] && [ -f "/etc/sysconfig/network-scripts/ifcfg-$if" ]; then
ns=$(grep -E '^DNS[0-9]+=' "/etc/sysconfig/network-scripts/ifcfg-$if" | cut -d= -f2 | paste -sd ', ' -)
[ -z "$dom" ] && dom=$(grep '^DOMAIN=' "/etc/sysconfig/network-scripts/ifcfg-$if" | cut -d= -f2)
[ -n "$ns" ] && src=ifcfg
fi
[ -z "$dom" ] && dom=$(awk '/^search /{$1="";print}' /etc/resolv.conf 2>/dev/null | tr ' ' ', ')
echo "${ns:-—}|${dom:-—}|${src:-—}"
}
_ntp(){
svc=""; cfg=""; srv=""; cur=""; st=""; note=""
st=$(timedatectl show -p NTPSynchronized --value 2>/dev/null)
[ -z "$st" ] && st=$(timedatectl status 2>/dev/null | awk -F': *' '/System clock synchronized/{v=$2} /NTP synchronized/{v=$2} END{print v}')
st=${st:-unknown}
_csrv(){ grep -Erh '^(server|pool)[[:space:]]' "$@" 2>/dev/null | grep -v '^#' | awk '{print $2}' | grep -E '^[a-zA-Z0-9._-]+\.' | sort -u | awk 'NR>1{printf ", "}{printf $0}'; }
_crun(){ systemctl is-active --quiet chronyd 2>/dev/null || pidof chronyd &>/dev/null; }
if [ -f /etc/chrony.conf ] || [ -f /etc/chrony/chrony.conf ] || command -v chronyc &>/dev/null; then
for f in /etc/chrony.conf /etc/chrony/chrony.conf; do [ -f "$f" ] && cfg=$f && break; done
srv=$(_csrv /etc/chrony.conf /etc/chrony/chrony.conf /etc/chrony.d/)
if _crun; then
svc="chronyd (running)"
cur=$(chronyc -n tracking 2>/dev/null | grep -oE '\([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+\)' | head -1 | tr -d '()')
[ -z "$cur" ] && cur=$(chronyc -n sources 2>/dev/null | awk '/^\^\* /{print $2;exit}')
[ "$st" = "unknown" ] && st=$(chronyc tracking 2>/dev/null | awk '/Leap status/{print ($2~/Normal/?"yes":"no");exit}')
else
svc="chronyd (stopped)"
note="chronyd not running; run systemctl start chronyd"
[ -z "$srv" ] && { srv="(not configured)"; note="no server/pool in chrony.conf and service stopped"; }
fi
elif [ -f /etc/ntp.conf ] || command -v ntpq &>/dev/null; then
svc="ntpd"; cfg=/etc/ntp.conf; srv=$(_csrv /etc/ntp.conf)
if systemctl is-active --quiet ntpd 2>/dev/null || pidof ntpd &>/dev/null; then
svc="ntpd (running)"; cur=$(ntpq -pn 2>/dev/null | awk '/^\*/{print $2;exit}')
[ "$st" = "unknown" ] && st=$([ -n "$cur" ] && echo yes || echo no)
else
svc="ntpd (stopped)"; note="ntpd not running"; [ -z "$srv" ] && srv="(not configured)"
fi
elif systemctl is-active --quiet systemd-timesyncd 2>/dev/null || [ -f /etc/systemd/timesyncd.conf ]; then
cfg=/etc/systemd/timesyncd.conf
systemctl is-active --quiet systemd-timesyncd 2>/dev/null && svc="systemd-timesyncd (running)" || svc="systemd-timesyncd (stopped)"
n=$(awk -F= '/^NTP=/{gsub(/ /,"",$2);if($2)print $2}' "$cfg" 2>/dev/null)
fb=$(awk -F= '/^FallbackNTP=/{gsub(/ /,"",$2);if($2)print $2}' "$cfg" 2>/dev/null)
if [ -n "$n" ]; then srv=$(echo "$n" | tr ' ' ', ')
elif [ -n "$fb" ]; then srv=$(echo "$fb" | tr ' ' ', '); note="NTP= unset, using FallbackNTP"
else srv="ntp.ubuntu.com, 0~3.ubuntu.pool.ntp.org"; note="NTP=/FallbackNTP= unset, using Ubuntu defaults"; fi
cur=$(timedatectl show-timesync -p ServerName --value 2>/dev/null)
else
svc="(not configured)"; note="no chrony/ntp/timesyncd config or service found"
fi
echo "${svc}|${cfg:-—}|${srv:-—}|${cur:-—}|${st}|${note:-—}"
}
if [ "$IFACE" = "*" ]; then read IF CIDR _ < <(_pick); else
CIDR=$(ip -br -4 a show dev "$IFACE" 2>/dev/null | awk '$2=="UP"{print $3;exit}')
[ -n "$CIDR" ] || { echo "[ERROR] interface $IFACE not found or not UP"; exit 1; }; IF=$IFACE
fi
[ -n "${IF:-}" ] || { echo "[ERROR] no private business interface found"; exit 1; }
ADDR=${CIDR%%/*}; PFX=${CIDR#*/}
MASK=$(awk -v p="$PFX" 'BEGIN{for(i=0;i<4;i++){n=p-i*8;if(n>=8)m[i]=255;else if(n<=0)m[i]=0;else m[i]=256-2^(8-n)}printf "%d.%d.%d.%d",m[0],m[1],m[2],m[3]}')
GW=$(ip -4 route show default dev "$IF" 2>/dev/null | awk '{print $3;exit}'); GW=${GW:-$(ip -4 route show default 2>/dev/null | awk '{print $3;exit}')}; GW=${GW:-—}
MAC=$(cat /sys/class/net/$IF/address 2>/dev/null || echo —); MTU=$(cat /sys/class/net/$IF/mtu 2>/dev/null || echo —)
IFS='|' read -r DNS DOM DNS_SRC <<< "$(_dns "$IF")"
IFS='|' read -r NTP_SVC NTP_CFG NTP_SRV NTP_CUR NTP_SYNC NTP_NOTE <<< "$(_ntp)"
echo "=== Host Info ==="
echo "OS: $(_os)"
echo "Host type: $(_host)"
echo "Hostname: $(hostname -f 2>/dev/null || hostname)"
echo
echo "=== Network Info ==="
echo "NIC type: $(_nic "$IF")"
echo "MAC/MTU: $MAC | $MTU"
echo "IPv4: $ADDR"
echo "Netmask: $MASK (/$PFX)"
echo "Gateway: $GW"
echo "DNS: $DNS"
echo "Search domain: $DOM"
echo "DNS source: $DNS_SRC"
echo
echo "=== NTP ==="
echo "NTP service: $NTP_SVC"
echo "Config file: $NTP_CFG"
echo "NTP servers: $NTP_SRV"
echo "Current source: ${NTP_CUR:-—}"
echo "Synchronized: $NTP_SYNC"
[ "$NTP_NOTE" != "—" ] && [ -n "$NTP_NOTE" ] && echo "Note: $NTP_NOTE"
echo
echo "=== Default Route ==="
ip -4 route show default 2>/dev/null | sed 's/^/ /' || echo " (none)"
echo
echo "=== Local IPv4 ==="
ip -br -4 a 2>/dev/null | awk '$1!~"^lo$"{print " "$0}'
EOF
ping 网段可用 IP 探测命令
在内网服务器上快速探测本机所在私网 /24 网段中哪些 IP 可能可用,用于申请或分配 IP 前的初步筛选。
- 自动识别业务网卡(优先
bond>eth>ens/enp),排除 Docker、K8s、Flannel 等内部网卡 - 仅探测与本机同网段地址,避免跨网段误扫
- 有 ping 响应 → 已占用;无响应 → 可能可用(目标可能禁 ping,需二次确认)
- 输出主机 OS、物理机/虚拟机、网卡类型等简要信息
- 连续 IP 自动合并显示(如
10.129.243.31-40)
sudo -E bash <<'EOF'
set -euo pipefail
PATTERN='*'
IFACE='*'
TIMEOUT=10
W=${PING_WAIT:-1}; P=${PRL:-30}
_os(){ [ -r /etc/os-release ] && . /etc/os-release && echo "${PRETTY_NAME:-$NAME} | kernel $(uname -r)" || echo "$(uname -s) $(uname -r)"; }
_host(){
local v s p; v=$(systemd-detect-virt 2>/dev/null | head -1 | tr -d ' \t\r\n' || true)
s=$(cat /sys/class/dmi/id/sys_vendor 2>/dev/null | tr -d '\r\n' || echo -)
p=$(cat /sys/class/dmi/id/product_name 2>/dev/null | tr -d '\r\n' || echo -)
if [ "$v" = none ] || [ -z "$v" ]; then
case "$s $p" in
*[Vv]irtual*|*VMware*|*KVM*|*QEMU*|*Hyper-V*|*Xen*|*Bochs*|*VirtualBox*|*OpenStack*)
echo "VM | $s $p"; return ;;
esac
echo "physical | $s $p"; return
fi
echo "VM ($v) | $s $p"
}
_nic(){
local if=$1 d sl
if [ -r "/sys/class/net/$if/bonding/slaves" ]; then
sl=$(tr ' ' '+' < "/sys/class/net/$if/bonding/slaves" | sed 's/+$//')
echo "$if | bonded NIC (bonding${sl:+: $sl})"; return
fi
d=$(ethtool -i "$if" 2>/dev/null | awk '/^driver:/{print $2;exit}')
case "$d" in
virtio_net|vmxnet3|veth|tap|tun|dummy|ifb) echo "$if | virtual NIC ($d)";;
""|unknown) [ -d "/sys/class/net/$if/bridge" ] && echo "$if | virtual NIC (bridge)" || echo "$if | unknown";;
*) echo "$if | physical NIC ($d)";;
esac
}
_collapse(){ awk -F. '
function sr(a,b,pa,pb){split(a,pa,".");split(b,pb,".")
if(pa[1]==pb[1]&&pa[2]==pb[2]&&pa[3]==pb[3])return a"-"pb[4]; return a"-"b}
{n++;o[n]=$4+0;ip[n]=$0} END{
if(!n){print "(none)";exit} s=1
for(i=2;i<=n;i++) if(o[i]!=o[i-1]+1){if(s==i-1)print ip[s];else print sr(ip[s],ip[i-1]); s=i}
if(s==n)print ip[s]; else print sr(ip[s],ip[n])
}'; }
_pick_if(){ ip -br -4 a 2>/dev/null | awk '
$1!~"^(lo|docker|cni|flannel|nodelocaldns|veth|kube|calico|tun|tap|virbr|br-|podman)" &&
$2=="UP" && $3~/\/24$/ {
ip=$3; sub(/\/.*/,"",ip)
if(ip~/^10\.|^192\.168\.|^172\.(1[6-9]|2[0-9]|3[0-1])\./)
print ($1~/^bond/?100:$1~/^eth/?90:$1~/^(ens|enp)/?80:10),$1,ip
}' | sort -rn | head -1 | awk '{print $2,$3}'; }
if [ "$IFACE" = "*" ]; then IF=$(_pick_if); else
IP=$(ip -br -4 a show dev "$IFACE" 2>/dev/null | awk '$2=="UP"{print $3;exit}')
[ -n "$IP" ] || { echo "[ERROR] interface $IFACE not found or not UP"; exit 1; }
IF="$IFACE ${IP%%/*}"
fi
[ -n "$IF" ] || { echo "[ERROR] no private /24 interface found"; exit 1; }
read IF IP _ <<< "$IF"
IFS=. read -r a b c _ <<< "$IP"; SUB="$a.$b.$c"
case "$PATTERN" in
"*"|".*") PAT="${SUB}.*" ;;
*.*.*.*) PAT="$PATTERN" ;;
*) PAT="${SUB}.${PATTERN}" ;;
esac
IFS=. read -r p1 p2 p3 p4 <<< "$PAT"
[ "$p1.$p2.$p3" = "$SUB" ] || { echo "[ERROR] PATTERN=${PAT} not in same subnet as ${IF}(${IP}/24)"; exit 1; }
ips=()
if [ "$p4" = "*" ]; then for i in $(seq 1 254); do ips+=("$SUB.$i"); done
elif case "$p4" in *'*') true;; *) false;; esac; then
x="${p4%\*}"; s=$((x*10)); e=$((s+9)); [ $s -lt 1 ] && s=1; [ $e -gt 254 ] && e=254
for i in $(seq $s $e); do ips+=("$SUB.$i"); done
elif [[ "$p4" =~ ^[0-9]{1,3}$ ]]; then ips+=("$SUB.$p4")
else echo "[ERROR] invalid 4th octet: $p4"; exit 1; fi
f=$(mktemp); trap 'rm -f "$f"' EXIT
par=$P; [ ${#ips[@]} -lt $par ] && par=${#ips[@]}; [ $par -lt 1 ] && par=1
echo "=== Host Info ==="
echo "OS: $(_os)"
echo "Host type: $(_host)"
echo "NIC type: $(_nic "$IF")"
echo
echo "=== Ping Scan ==="
echo "Iface: $IF ($IP/24) | Pattern: $PAT | Count: ${#ips[@]} | Timeout: ${TIMEOUT}s"
echo
to=0
timeout "$TIMEOUT" bash -c '
printf "%s\n" '"$(printf "'%s' " "${ips[@]}")"' | xargs -P '"$par"' -I{} bash -c '\''
ip="$1"
( ping -c1 -W'"$W"' -q "$ip" || sudo ping -c1 -W'"$W"' -q "$ip" ) &>/dev/null \
&& echo "u $ip" || echo "f $ip"
'\'' _ {}
' > "$f" || to=1
sort -t. -k1,1n -k2,2n -k3,3n -k4,4n "$f" -o "$f"
u=$(grep -c '^u ' "$f" 2>/dev/null || true); v=$(grep -c '^f ' "$f" 2>/dev/null || true)
u=${u:-0}; v=${v:-0}; done=$((u+v)); miss=$((${#ips[@]}-done))
echo "=== In Use ($u) ==="
grep '^u ' "$f" | awk '{print $2}' | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n | _collapse
echo
echo "=== Possibly Free ($v) ==="
grep '^f ' "$f" | awk '{print $2}' | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n | _collapse
echo
echo "=== Summary ==="
echo "In use: $u | Possibly free: $v | Done: $done/${#ips[@]} | Pending: $miss"
[ $to -eq 1 ] && echo "Note: ${TIMEOUT}s timeout; narrow PATTERN (e.g. 1*) or increase TIMEOUT"
if [ "$v" -gt 0 ]; then
echo "All candidates:"
grep '^f ' "$f" | awk '{print $2}' | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n | _collapse | sed 's/^/ → /'
fi
EOF