Merge pull request #180 from DesertGamer/patch-2

IP-Tag Redesign: Resolve issues #172
This commit is contained in:
CanbiZ 2025-04-07 08:45:19 +02:00 committed by GitHub
commit c0da3ff2cd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -6,8 +6,8 @@
# Source: https://github.com/gitsang/iptag
function header_info {
clear
cat <<"EOF"
clear
cat <<"EOF"
___ ____ _____
|_ _| _ \ _ |_ _|_ _ __ _
| || |_) (_) | |/ _` |/ _` |
@ -40,7 +40,9 @@ catch_errors() {
# This function is called when an error occurs. It receives the exit code, line number, and command that caused the error, and displays an error message.
error_handler() {
if [ -n "$SPINNER_PID" ] && ps -p $SPINNER_PID >/dev/null; then kill $SPINNER_PID >/dev/null; fi
if [ -n "$SPINNER_PID" ] && ps -p $SPINNER_PID >/dev/null; then
kill $SPINNER_PID >/dev/null
fi
printf "\e[?25h"
local exit_code="$?"
local line_number="$1"
@ -59,9 +61,9 @@ spinner() {
local color="${YWB}"
while true; do
printf "\r ${color}%s${CL}" "${frames[spin_i]}"
spin_i=$(((spin_i + 1) % ${#frames[@]}))
sleep "$interval"
printf "\r ${color}%s${CL}" "${frames[spin_i]}"
spin_i=$(((spin_i + 1) % ${#frames[@]}))
sleep "$interval"
done
}
@ -75,7 +77,9 @@ msg_info() {
# This function displays a success message with a green color.
msg_ok() {
if [ -n "$SPINNER_PID" ] && ps -p $SPINNER_PID >/dev/null; then kill $SPINNER_PID >/dev/null; fi
if [ -n "$SPINNER_PID" ] && ps -p $SPINNER_PID >/dev/null; then
kill $SPINNER_PID >/dev/null
fi
printf "\e[?25h"
local msg="$1"
echo -e "${BFR}${CM}${GN}${msg}${CL}"
@ -83,7 +87,9 @@ msg_ok() {
# This function displays a error message with a red color.
msg_error() {
if [ -n "$SPINNER_PID" ] && ps -p $SPINNER_PID >/dev/null; then kill $SPINNER_PID >/dev/null; fi
if [ -n "$SPINNER_PID" ] && ps -p $SPINNER_PID >/dev/null; then
kill $SPINNER_PID >/dev/null
fi
printf "\e[?25h"
local msg="$1"
echo -e "${BFR}${CROSS}${RD}${msg}${CL}"
@ -119,6 +125,11 @@ update_installation() {
msg_info "Updating IP-Tag Scripts"
systemctl stop iptag.service &>/dev/null
# Create directory if it doesn't exist
if [[ ! -d "/opt/iptag" ]]; then
mkdir -p /opt/iptag
fi
# Migrate config if needed
migrate_config
@ -146,17 +157,65 @@ ip_to_int() {
ip_in_cidr() {
local ip="$1"
local cidr="$2"
local ip_int=$(ip_to_int "$ip")
local netmask_int=$(ip_to_int "$(ipcalc -b "$cidr" | grep Broadcast | awk '{print $2}')")
[[ $((ip_int & netmask_int)) -eq $((ip_int & netmask_int)) ]] && return 0 || return 1
# Use ipcalc with the -c option (check), which returns 0 if the IP is in the network
if ipcalc -c "$ip" "$cidr" >/dev/null 2>&1; then
# Get network address and mask from CIDR
local network prefix
network=$(echo "$cidr" | cut -d/ -f1)
prefix=$(echo "$cidr" | cut -d/ -f2)
# Check if IP is in the network
local ip_a ip_b ip_c ip_d net_a net_b net_c net_d
IFS=. read -r ip_a ip_b ip_c ip_d <<< "$ip"
IFS=. read -r net_a net_b net_c net_d <<< "$network"
# Check octets match based on prefix length
local result=0
if (( prefix >= 8 )); then
[[ "$ip_a" != "$net_a" ]] && result=1
fi
if (( prefix >= 16 )); then
[[ "$ip_b" != "$net_b" ]] && result=1
fi
if (( prefix >= 24 )); then
[[ "$ip_c" != "$net_c" ]] && result=1
fi
return $result
fi
return 1
}
# Format IP address according to the configuration
format_ip_tag() {
local ip="$1"
local format="${TAG_FORMAT:-full}"
case "$format" in
"last_octet")
echo "${ip##*.}"
;;
"last_two_octets")
echo "${ip#*.*.}"
;;
*)
echo "$ip"
;;
esac
}
# Check if IP is in any CIDRs
ip_in_cidrs() {
local ip="$1"
local cidrs=()
mapfile -t cidrs < <(echo "$2" | tr ' ' '\n')
for cidr in "${cidrs[@]}"; do
local cidrs="$2"
# Check that cidrs is not empty
[[ -z "$cidrs" ]] && return 1
local IFS=' '
for cidr in $cidrs; do
ip_in_cidr "$ip" "$cidr" && return 0
done
return 1
@ -210,16 +269,16 @@ get_vm_ips() {
local ips=""
# Check if VM is running
qm status "$vmid" | grep -q "status: running" || return
qm status "$vmid" 2>/dev/null | grep -q "status: running" || return
# Get MAC addresses from VM configuration
local macs
macs=$(qm config "$vmid" | grep -E 'net[0-9]+' | grep -o -E '[a-fA-F0-9]{2}(:[a-fA-F0-9]{2}){5}')
macs=$(qm config "$vmid" 2>/dev/null | grep -E 'net[0-9]+' | grep -o -E '[a-fA-F0-9]{2}(:[a-fA-F0-9]{2}){5}')
# Look up IPs from ARP table using MAC addresses
for mac in $macs; do
local ip
ip=$(arp -an | grep -i "$mac" | grep -o -E '([0-9]{1,3}\.){3}[0-9]{1,3}')
ip=$(arp -an 2>/dev/null | grep -i "$mac" | grep -o -E '([0-9]{1,3}\.){3}[0-9]{1,3}')
if [ -n "$ip" ]; then
ips+="$ip "
fi
@ -238,7 +297,8 @@ update_tags() {
# Get current IPs
local current_ips_full
if [[ "$type" == "lxc" ]]; then
current_ips_full=$(lxc-info -n "${vmid}" -i | awk '{print $2}')
# Redirect error output to suppress AppArmor warnings
current_ips_full=$(lxc-info -n "${vmid}" -i 2>/dev/null | grep -E "^IP:" | awk '{print $2}')
else
current_ips_full=$(get_vm_ips "${vmid}")
fi
@ -246,20 +306,40 @@ update_tags() {
# Parse current tags and get valid IPs
local current_tags=()
local next_tags=()
mapfile -t current_tags < <($config_cmd config "${vmid}" | grep tags | awk '{print $2}' | sed 's/;/\n/g')
mapfile -t current_tags < <($config_cmd config "${vmid}" 2>/dev/null | grep tags | awk '{print $2}' | sed 's/;/\n/g')
for tag in "${current_tags[@]}"; do
is_valid_ipv4 "${tag}" || next_tags+=("${tag}")
# Skip tag if it looks like an IP (full or partial)
if ! is_valid_ipv4 "${tag}" && ! [[ "$tag" =~ ^[0-9]+(\.[0-9]+)*$ ]]; then
next_tags+=("${tag}")
fi
done
# Add valid IPs to tags
local added_ips=()
local skipped_ips=()
for ip in ${current_ips_full}; do
is_valid_ipv4 "${ip}" && ip_in_cidrs "${ip}" "${CIDR_LIST[*]}" && next_tags+=("${ip}")
if is_valid_ipv4 "${ip}"; then
if ip_in_cidrs "${ip}" "${CIDR_LIST[*]}"; then
local formatted_ip=$(format_ip_tag "$ip")
next_tags+=("${formatted_ip}")
added_ips+=("${formatted_ip}")
else
skipped_ips+=("${ip}")
fi
fi
done
# Log only if there are changes
if [ ${#added_ips[@]} -gt 0 ]; then
echo "${type^} ${vmid}: added IP tags: ${added_ips[*]}"
fi
# Update if changed
[[ "$(IFS=';'; echo "${current_tags[*]}")" != "$(IFS=';'; echo "${next_tags[*]}")" ]] && \
$config_cmd set "${vmid}" -tags "$(IFS=';'; echo "${next_tags[*]}")"
if [[ "$(IFS=';'; echo "${current_tags[*]}")" != "$(IFS=';'; echo "${next_tags[*]}")" ]]; then
$config_cmd set "${vmid}" -tags "$(IFS=';'; echo "${next_tags[*]}")" &>/dev/null
fi
}
# Check if status changed
@ -269,17 +349,17 @@ check_status_changed() {
case "$type" in
"lxc")
current_status=$(pct list 2>/dev/null)
current_status=$(pct list 2>/dev/null | grep -v VMID)
[[ "${last_lxc_status}" == "${current_status}" ]] && return 1
last_lxc_status="${current_status}"
;;
"vm")
current_status=$(qm list 2>/dev/null)
current_status=$(qm list 2>/dev/null | grep -v VMID)
[[ "${last_vm_status}" == "${current_status}" ]] && return 1
last_vm_status="${current_status}"
;;
"fw")
current_status=$(ifconfig | grep "^fw")
current_status=$(ifconfig 2>/dev/null | grep "^fw")
[[ "${last_net_interface}" == "${current_status}" ]] && return 1
last_net_interface="${current_status}"
;;
@ -287,6 +367,25 @@ check_status_changed() {
return 0
}
# Update tags for all containers/VMs of specified type
update_all_tags() {
local type="$1"
local vmid_list=""
if [[ "$type" == "lxc" ]]; then
# Redirect stderr to /dev/null to suppress AppArmor messages
vmid_list=$(pct list 2>/dev/null | grep -v VMID | awk '{print $1}')
echo "Found $(echo "$vmid_list" | wc -w) LXC containers"
else
vmid_list=$(qm list 2>/dev/null | grep -v VMID | awk '{print $1}')
echo "Found $(echo "$vmid_list" | wc -w) virtual machines"
fi
for vmid in $vmid_list; do
update_tags "$type" "$vmid"
done
}
check() {
current_time=$(date +%s)
@ -294,10 +393,10 @@ check() {
time_since_last_lxc_status_check=$((current_time - last_lxc_status_check_time))
if [[ "${LXC_STATUS_CHECK_INTERVAL}" -gt 0 ]] \
&& [[ "${time_since_last_lxc_status_check}" -ge "${LXC_STATUS_CHECK_INTERVAL}" ]]; then
echo "Checking lxc status..."
echo "Checking LXC status..."
last_lxc_status_check_time=${current_time}
if check_status_changed "lxc"; then
update_tags "lxc" "${vmid}"
update_all_tags "lxc"
last_update_lxc_time=${current_time}
fi
fi
@ -306,10 +405,10 @@ check() {
time_since_last_vm_status_check=$((current_time - last_vm_status_check_time))
if [[ "${VM_STATUS_CHECK_INTERVAL}" -gt 0 ]] \
&& [[ "${time_since_last_vm_status_check}" -ge "${VM_STATUS_CHECK_INTERVAL}" ]]; then
echo "Checking vm status..."
echo "Checking VM status..."
last_vm_status_check_time=${current_time}
if check_status_changed "vm"; then
update_tags "vm" "${vmid}"
update_all_tags "vm"
last_update_vm_time=${current_time}
fi
fi
@ -318,11 +417,11 @@ check() {
time_since_last_fw_net_interface_check=$((current_time - last_fw_net_interface_check_time))
if [[ "${FW_NET_INTERFACE_CHECK_INTERVAL}" -gt 0 ]] \
&& [[ "${time_since_last_fw_net_interface_check}" -ge "${FW_NET_INTERFACE_CHECK_INTERVAL}" ]]; then
echo "Checking fw net interface..."
echo "Checking network interfaces..."
last_fw_net_interface_check_time=${current_time}
if check_status_changed "fw"; then
update_tags "lxc" "${vmid}"
update_tags "vm" "${vmid}"
update_all_tags "lxc"
update_all_tags "vm"
last_update_lxc_time=${current_time}
last_update_vm_time=${current_time}
fi
@ -333,11 +432,8 @@ check() {
local last_update_var="last_update_${type}_time"
local time_since_last_update=$((current_time - ${!last_update_var}))
if [ ${time_since_last_update} -ge ${FORCE_UPDATE_INTERVAL} ]; then
echo "Force updating ${type} iptags..."
case "$type" in
"lxc") update_tags "lxc" "${vmid}" ;;
"vm") update_tags "vm" "${vmid}" ;;
esac
echo "Force updating ${type} tags..."
update_all_tags "$type"
eval "${last_update_var}=${current_time}"
fi
done
@ -395,7 +491,9 @@ if check_service_exists; then
msg_error "Installation cancelled."
exit 0
;;
*) msg_error "Please answer yes or no." ;;
*)
msg_error "Please answer yes or no."
;;
esac
done
fi
@ -403,12 +501,16 @@ fi
while true; do
read -p "This will install ${APP} on ${hostname}. Proceed? (y/n): " yn
case $yn in
[Yy]*) break ;;
[Nn]*)
msg_error "Installation cancelled."
exit
;;
*) msg_error "Please answer yes or no." ;;
[Yy]*)
break
;;
[Nn]*)
msg_error "Installation cancelled."
exit
;;
*)
msg_error "Please answer yes or no."
;;
esac
done
@ -451,6 +553,12 @@ CIDR_LIST=(
100.64.0.0/10
)
# Tag format options:
# - "full": full IP address (e.g., 192.168.0.100)
# - "last_octet": only the last octet (e.g., 100)
# - "last_two_octets": last two octets (e.g., 0.100)
TAG_FORMAT="full"
# Interval settings (in seconds)
LOOP_INTERVAL=60
VM_STATUS_CHECK_INTERVAL=60
@ -488,17 +596,65 @@ ip_to_int() {
ip_in_cidr() {
local ip="$1"
local cidr="$2"
local ip_int=$(ip_to_int "$ip")
local netmask_int=$(ip_to_int "$(ipcalc -b "$cidr" | grep Broadcast | awk '{print $2}')")
[[ $((ip_int & netmask_int)) -eq $((ip_int & netmask_int)) ]] && return 0 || return 1
# Use ipcalc with the -c option (check), which returns 0 if the IP is in the network
if ipcalc -c "$ip" "$cidr" >/dev/null 2>&1; then
# Get network address and mask from CIDR
local network prefix
network=$(echo "$cidr" | cut -d/ -f1)
prefix=$(echo "$cidr" | cut -d/ -f2)
# Check if IP is in the network
local ip_a ip_b ip_c ip_d net_a net_b net_c net_d
IFS=. read -r ip_a ip_b ip_c ip_d <<< "$ip"
IFS=. read -r net_a net_b net_c net_d <<< "$network"
# Check octets match based on prefix length
local result=0
if (( prefix >= 8 )); then
[[ "$ip_a" != "$net_a" ]] && result=1
fi
if (( prefix >= 16 )); then
[[ "$ip_b" != "$net_b" ]] && result=1
fi
if (( prefix >= 24 )); then
[[ "$ip_c" != "$net_c" ]] && result=1
fi
return $result
fi
return 1
}
# Format IP address according to the configuration
format_ip_tag() {
local ip="$1"
local format="${TAG_FORMAT:-full}"
case "$format" in
"last_octet")
echo "${ip##*.}"
;;
"last_two_octets")
echo "${ip#*.*.}"
;;
*)
echo "$ip"
;;
esac
}
# Check if IP is in any CIDRs
ip_in_cidrs() {
local ip="$1"
local cidrs=()
mapfile -t cidrs < <(echo "$2" | tr ' ' '\n')
for cidr in "${cidrs[@]}"; do
local cidrs="$2"
# Check that cidrs is not empty
[[ -z "$cidrs" ]] && return 1
local IFS=' '
for cidr in $cidrs; do
ip_in_cidr "$ip" "$cidr" && return 0
done
return 1
@ -552,16 +708,16 @@ get_vm_ips() {
local ips=""
# Check if VM is running
qm status "$vmid" | grep -q "status: running" || return
qm status "$vmid" 2>/dev/null | grep -q "status: running" || return
# Get MAC addresses from VM configuration
local macs
macs=$(qm config "$vmid" | grep -E 'net[0-9]+' | grep -o -E '[a-fA-F0-9]{2}(:[a-fA-F0-9]{2}){5}')
macs=$(qm config "$vmid" 2>/dev/null | grep -E 'net[0-9]+' | grep -o -E '[a-fA-F0-9]{2}(:[a-fA-F0-9]{2}){5}')
# Look up IPs from ARP table using MAC addresses
for mac in $macs; do
local ip
ip=$(arp -an | grep -i "$mac" | grep -o -E '([0-9]{1,3}\.){3}[0-9]{1,3}')
ip=$(arp -an 2>/dev/null | grep -i "$mac" | grep -o -E '([0-9]{1,3}\.){3}[0-9]{1,3}')
if [ -n "$ip" ]; then
ips+="$ip "
fi
@ -580,7 +736,8 @@ update_tags() {
# Get current IPs
local current_ips_full
if [[ "$type" == "lxc" ]]; then
current_ips_full=$(lxc-info -n "${vmid}" -i | awk '{print $2}')
# Redirect error output to suppress AppArmor warnings
current_ips_full=$(lxc-info -n "${vmid}" -i 2>/dev/null | grep -E "^IP:" | awk '{print $2}')
else
current_ips_full=$(get_vm_ips "${vmid}")
fi
@ -588,20 +745,65 @@ update_tags() {
# Parse current tags and get valid IPs
local current_tags=()
local next_tags=()
mapfile -t current_tags < <($config_cmd config "${vmid}" | grep tags | awk '{print $2}' | sed 's/;/\n/g')
mapfile -t current_tags < <($config_cmd config "${vmid}" 2>/dev/null | grep tags | awk '{print $2}' | sed 's/;/\n/g')
for tag in "${current_tags[@]}"; do
is_valid_ipv4 "${tag}" || next_tags+=("${tag}")
# Skip tag if it looks like an IP (full or partial)
if ! is_valid_ipv4 "${tag}" && ! [[ "$tag" =~ ^[0-9]+(\.[0-9]+)*$ ]]; then
next_tags+=("${tag}")
fi
done
# Add valid IPs to tags
local added_ips=()
local skipped_ips=()
for ip in ${current_ips_full}; do
is_valid_ipv4 "${ip}" && ip_in_cidrs "${ip}" "${CIDR_LIST[*]}" && next_tags+=("${ip}")
if is_valid_ipv4 "${ip}"; then
if ip_in_cidrs "${ip}" "${CIDR_LIST[*]}"; then
local formatted_ip=$(format_ip_tag "$ip")
next_tags+=("${formatted_ip}")
added_ips+=("${formatted_ip}")
else
skipped_ips+=("${ip}")
fi
fi
done
# Log only if there are changes
if [ ${#added_ips[@]} -gt 0 ]; then
echo "${type^} ${vmid}: added IP tags: ${added_ips[*]}"
fi
# Update if changed
[[ "$(IFS=';'; echo "${current_tags[*]}")" != "$(IFS=';'; echo "${next_tags[*]}")" ]] && \
$config_cmd set "${vmid}" -tags "$(IFS=';'; echo "${next_tags[*]}")"
if [[ "$(IFS=';'; echo "${current_tags[*]}")" != "$(IFS=';'; echo "${next_tags[*]}")" ]]; then
$config_cmd set "${vmid}" -tags "$(IFS=';'; echo "${next_tags[*]}")" &>/dev/null
fi
}
# Check if status changed
check_status_changed() {
local type="$1"
local current_status
case "$type" in
"lxc")
current_status=$(pct list 2>/dev/null | grep -v VMID)
[[ "${last_lxc_status}" == "${current_status}" ]] && return 1
last_lxc_status="${current_status}"
;;
"vm")
current_status=$(qm list 2>/dev/null | grep -v VMID)
[[ "${last_vm_status}" == "${current_status}" ]] && return 1
last_vm_status="${current_status}"
;;
"fw")
current_status=$(ifconfig 2>/dev/null | grep "^fw")
[[ "${last_net_interface}" == "${current_status}" ]] && return 1
last_net_interface="${current_status}"
;;
esac
return 0
}
check() {
@ -611,10 +813,10 @@ check() {
time_since_last_lxc_status_check=$((current_time - last_lxc_status_check_time))
if [[ "${LXC_STATUS_CHECK_INTERVAL}" -gt 0 ]] \
&& [[ "${time_since_last_lxc_status_check}" -ge "${LXC_STATUS_CHECK_INTERVAL}" ]]; then
echo "Checking lxc status..."
echo "Checking LXC status..."
last_lxc_status_check_time=${current_time}
if check_status_changed "lxc"; then
update_tags "lxc" "${vmid}"
update_all_tags "lxc"
last_update_lxc_time=${current_time}
fi
fi
@ -623,10 +825,10 @@ check() {
time_since_last_vm_status_check=$((current_time - last_vm_status_check_time))
if [[ "${VM_STATUS_CHECK_INTERVAL}" -gt 0 ]] \
&& [[ "${time_since_last_vm_status_check}" -ge "${VM_STATUS_CHECK_INTERVAL}" ]]; then
echo "Checking vm status..."
echo "Checking VM status..."
last_vm_status_check_time=${current_time}
if check_status_changed "vm"; then
update_tags "vm" "${vmid}"
update_all_tags "vm"
last_update_vm_time=${current_time}
fi
fi
@ -635,11 +837,11 @@ check() {
time_since_last_fw_net_interface_check=$((current_time - last_fw_net_interface_check_time))
if [[ "${FW_NET_INTERFACE_CHECK_INTERVAL}" -gt 0 ]] \
&& [[ "${time_since_last_fw_net_interface_check}" -ge "${FW_NET_INTERFACE_CHECK_INTERVAL}" ]]; then
echo "Checking fw net interface..."
echo "Checking network interfaces..."
last_fw_net_interface_check_time=${current_time}
if check_status_changed "fw"; then
update_tags "lxc" "${vmid}"
update_tags "vm" "${vmid}"
update_all_tags "lxc"
update_all_tags "vm"
last_update_lxc_time=${current_time}
last_update_vm_time=${current_time}
fi
@ -650,11 +852,8 @@ check() {
local last_update_var="last_update_${type}_time"
local time_since_last_update=$((current_time - ${!last_update_var}))
if [ ${time_since_last_update} -ge ${FORCE_UPDATE_INTERVAL} ]; then
echo "Force updating ${type} iptags..."
case "$type" in
"lxc") update_tags "lxc" "${vmid}" ;;
"vm") update_tags "vm" "${vmid}" ;;
esac
echo "Force updating ${type} tags..."
update_all_tags "$type"
eval "${last_update_var}=${current_time}"
fi
done