Skip to content

IP-Tag Redesign #172

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
MickLesk opened this issue Apr 3, 2025 · 33 comments
Open

IP-Tag Redesign #172

MickLesk opened this issue Apr 3, 2025 · 33 comments

Comments

@MickLesk
Copy link
Member

MickLesk commented Apr 3, 2025

First Update:

Image

Second Execute:

Image

@DesertGamer can you take a look?

@MickLesk
Copy link
Member Author

MickLesk commented Apr 3, 2025

Service output node1 (has 1 vm included)

Image

Service output node 2 (has no vms):

Image

@MickLesk MickLesk added bugfix and removed bugfix labels Apr 3, 2025
@DesertGamer
Copy link
Contributor

@MickLesk Yes, no problem, I'll try to fix this error now.

@DesertGamer
Copy link
Contributor

@MickLesk #180

MickLesk added a commit that referenced this issue Apr 7, 2025
IP-Tag Redesign: Resolve issues #172
@MickLesk
Copy link
Member Author

MickLesk commented Apr 7, 2025

looks better for me.

@community-scripts/contributor has everyone the old script installed and can test the new ?

@tremor021
Copy link
Member

I do, gimme hour or two to get back home.

@tremor021
Copy link
Member

Image

Image

Nothing gets tagged @MickLesk @DesertGamer

@DesertGamer
Copy link
Contributor

@tremor021 Can you send me a screenshot of your services, since I personally use my service and everything works fine, are you sure you uploaded the latest version?

@DesertGamer
Copy link
Contributor

Also, please provide the output of the command:
cat /opt/iptag/iptag.conf

@tremor021
Copy link
Member

[Unit]
Description=IP-Tag service
After=network.target

[Service]
Type=simple
ExecStart=/opt/iptag/iptag
Restart=always

[Install]
WantedBy=multi-user.target
root@chizaro:~# cat /opt/iptag/iptag.conf
# Configuration file for LXC IP tagging

# List of allowed CIDRs
CIDR_LIST=(
  192.168.0.0/16
  172.16.0.0/12
  10.0.0.0/8
  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
FW_NET_INTERFACE_CHECK_INTERVAL=60
LXC_STATUS_CHECK_INTERVAL=60
FORCE_UPDATE_INTERVAL=1800

@DesertGamer
Copy link
Contributor

There is a CIDR_LIST in the config, where you need to enter the internal IP address of the machines, perhaps it differs in format, which is why the script does not try to set tags for you.

@DesertGamer
Copy link
Contributor

And not just the internal one, but any IP mask so that you can be assigned tags only for the right vms.

@tremor021
Copy link
Member

why would i need to enter any IPs there? isn't the purpose of the script to find the ip of the lxc/vm and set it as a tag?

@tremor021
Copy link
Member

bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVED/refs/heads/main/tools/addon/add-iptag.sh)"

this is the command used to install

@tremor021
Copy link
Member

Btw, my ip range is in 192.168.0.0/16 range

@DesertGamer
Copy link
Contributor

This parameter is also available in the original script and allows you not to assign an IP, for example, if you use a large number of interfaces for 1 machine, then here you specify 192.168.0.0/24, but on your car, for example, "pihole" there is also an interface with 192.168.1.0/24, then you will not see the tag from this interface, but only from what you need.

You just need to specify the IP mask, and then the script will find out the IP itself and set the tags.

@DesertGamer
Copy link
Contributor

I'm very confused by your logs, because you don't have an important line about the system finding LXC and VM. It should be like this

Apr 10 01:41:48 alpha iptag[201114]: Checking LXC status...
Apr 10 01:41:52 alpha iptag[201114]: Found 11 LXC containers
Apr 10 01:41:54 alpha iptag[201114]: Lxc 100: added IP tags: 0.12

@tremor021
Copy link
Member

tremor021 commented Apr 9, 2025

I have one LXC with IP of 192.168.1.11 and one VM with IP of 192.168.1.10 and both are not getting tagged.

Image

I'm very confused by your logs, because you don't have an important line about the system finding LXC and VM. It should be like this

Apr 10 01:41:48 alpha iptag[201114]: Checking LXC status...
Apr 10 01:41:52 alpha iptag[201114]: Found 11 LXC containers
Apr 10 01:41:54 alpha iptag[201114]: Lxc 100: added IP tags: 0.12

thats more of a question for you i guess :)

@tremor021
Copy link
Member

Apr 10 00:46:01 chizaro systemd[1]: Stopping iptag.service - IP-Tag service...
Apr 10 00:46:01 chizaro systemd[1]: iptag.service: Deactivated successfully.
Apr 10 00:46:01 chizaro systemd[1]: Stopped iptag.service - IP-Tag service.
Apr 10 00:46:01 chizaro systemd[1]: iptag.service: Consumed 3min 10.099s CPU time.
Apr 10 00:46:01 chizaro systemd[1]: Started iptag.service - IP-Tag service.
Apr 10 00:46:01 chizaro iptag[71597]: Checking LXC status...
Apr 10 00:46:04 chizaro iptag[71608]: /opt/iptag/iptag: line 243: update_all_tags: command not found
Apr 10 00:46:04 chizaro iptag[71597]: Checking VM status...

service restart gives the same error. update_all_tags does not exist

@DesertGamer
Copy link
Contributor

DesertGamer commented Apr 9, 2025

Well, that's right, you don't have this range in your config, you need to specify 192.168.1.0/16.

Add it

# Configuration file for LXC IP tagging

# List of allowed CIDRs
CIDR_LIST=(
  192.168.1.0/16
)

# 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
FW_NET_INTERFACE_CHECK_INTERVAL=60
LXC_STATUS_CHECK_INTERVAL=60
FORCE_UPDATE_INTERVAL=1800

@DesertGamer
Copy link
Contributor

I initially explained that you should set the IP range yourself so that the script can assign tags to you.

@tremor021
Copy link
Member

192.168.1.0/24 is in 192.168.0.0/16... its either my english or i'm not getting this at all

@DesertGamer
Copy link
Contributor

Wait a second, I'm going to check the mask verification algorithm, it's good that you noticed this)

@tremor021
Copy link
Member

tremor021 commented Apr 9, 2025

Update got it to tag the LXC

Image

Apr 10 00:57:55 chizaro systemd[1]: Stopping iptag.service - IP-Tag service...
Apr 10 00:57:55 chizaro systemd[1]: iptag.service: Deactivated successfully.
Apr 10 00:57:55 chizaro systemd[1]: Stopped iptag.service - IP-Tag service.
Apr 10 00:57:55 chizaro systemd[1]: iptag.service: Consumed 24.078s CPU time.
Apr 10 00:57:57 chizaro systemd[1]: Started iptag.service - IP-Tag service.
Apr 10 00:57:57 chizaro iptag[73983]: Checking LXC status...
Apr 10 00:58:03 chizaro iptag[73983]: Found 1 LXC containers
Apr 10 00:58:06 chizaro iptag[73983]: Lxc 101: added IP tags: 192.168.1.11
Apr 10 00:58:09 chizaro iptag[73983]: Checking VM status...
Apr 10 00:58:15 chizaro iptag[73983]: Found 1 virtual machines
Apr 10 00:58:24 chizaro iptag[73983]: Checking network interfaces...
Apr 10 00:58:27 chizaro iptag[73983]: Found 1 LXC containers
Apr 10 00:58:30 chizaro iptag[73983]: Lxc 101: added IP tags: 192.168.1.11
Apr 10 00:58:33 chizaro iptag[73983]: Found 1 virtual machines

VM is not getting tagged tho

@DesertGamer
Copy link
Contributor

DesertGamer commented Apr 9, 2025

I actually found a small error, fixed it, try it, everything worked as it should.

This must be uploaded to /opt/iptag/iptag

#!/bin/bash
# =============== CONFIGURATION =============== #
CONFIG_FILE="/opt/iptag/iptag.conf"

# Load the configuration file if it exists
if [ -f "$CONFIG_FILE" ]; then
  # shellcheck source=./iptag.conf
  source "$CONFIG_FILE"
fi

# Convert IP to integer for comparison
ip_to_int() {
  local ip="$1"
  local a b c d
  IFS=. read -r a b c d <<< "${ip}"
  echo "$((a << 24 | b << 16 | c << 8 | d))"
}

# Check if IP is in CIDR
ip_in_cidr() {
  local ip="$1"
  local cidr="$2"

  # 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="$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
}

# Check if IP is valid
is_valid_ipv4() {
  local ip="$1"
  [[ "$ip" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]] || return 1
  local IFS='.'
  read -ra parts <<< "$ip"
  for part in "${parts[@]}"; do
    [[ "$part" =~ ^[0-9]+$ ]] && ((part >= 0 && part <= 255)) || return 1
  done
  return 0
}

lxc_status_changed() {
  current_lxc_status=$(pct list 2>/dev/null)
  if [ "${last_lxc_status}" == "${current_lxc_status}" ]; then
    return 1
  else
    last_lxc_status="${current_lxc_status}"
    return 0
  fi
}

vm_status_changed() {
  current_vm_status=$(qm list 2>/dev/null)
  if [ "${last_vm_status}" == "${current_vm_status}" ]; then
    return 1
  else
    last_vm_status="${current_vm_status}"
    return 0
  fi
}

fw_net_interface_changed() {
  current_net_interface=$(ifconfig | grep "^fw")
  if [ "${last_net_interface}" == "${current_net_interface}" ]; then
    return 1
  else
    last_net_interface="${current_net_interface}"
    return 0
  fi
}

# Get VM IPs using MAC addresses and ARP table
get_vm_ips() {
  local vmid=$1
  local ips=""

  # Check if VM is running
  qm status "$vmid" 2>/dev/null | grep -q "status: running" || return

  # Get MAC addresses from VM configuration
  local macs
  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 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
  done

  echo "$ips"
}

# Update tags for container or VM
update_tags() {
  local type="$1"
  local vmid="$2"
  local config_cmd="pct"
  [[ "$type" == "vm" ]] && config_cmd="qm"

  # Get current IPs
  local current_ips_full
  if [[ "$type" == "lxc" ]]; then
    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

  # Parse current tags
  local current_tags=()
  local next_tags=()
  mapfile -t current_tags < <($config_cmd config "${vmid}" 2>/dev/null | grep tags | awk '{print $2}' | sed 's/;/\n/g')

  # Сохраняем не-IP теги и валидные IP теги из CIDR_LIST
  for tag in "${current_tags[@]}"; do
    if ! is_valid_ipv4 "${tag}" && ! [[ "$tag" =~ ^[0-9]+(\.[0-9]+)*$ ]]; then
      next_tags+=("${tag}")
    else
      # Проверяем, соответствует ли IP тег CIDR_LIST
      if [[ "$tag" =~ ^[0-9]+(\.[0-9]+)*$ ]]; then
        # Для тегов в формате last_octet или last_two_octets
        for cidr in ${CIDR_LIST[*]}; do
          local network_prefix=$(echo "$cidr" | cut -d/ -f1 | cut -d. -f1-2)
          if [[ "$network_prefix" == "192.168" ]]; then
            next_tags+=("${tag}")
            break
          fi
        done
      elif is_valid_ipv4 "${tag}" && ip_in_cidrs "${tag}" "${CIDR_LIST[*]}"; then
        # Для полных IP адресов
        next_tags+=("${tag}")
      fi
    fi
  done

  # Добавляем новые IP
  local added_ips=()
  local skipped_ips=()
  local needs_update=false

  for ip in ${current_ips_full}; do
    if is_valid_ipv4 "${ip}"; then
      if ip_in_cidrs "${ip}" "${CIDR_LIST[*]}"; then
        local formatted_ip=$(format_ip_tag "$ip")
        if [[ ! " ${next_tags[*]} " =~ " ${formatted_ip} " ]]; then
          next_tags+=("${formatted_ip}")
          added_ips+=("${formatted_ip}")
          needs_update=true
        fi
      else
        skipped_ips+=("${ip}")
      fi
    fi
  done

  # Обновляем теги только если есть изменения
  if [ "$needs_update" = true ]; then
    echo "${type^} ${vmid}: added IP tags: ${added_ips[*]}"
    $config_cmd set "${vmid}" -tags "$(IFS=';'; echo "${next_tags[*]}")" &>/dev/null
  else
    echo "${type^} ${vmid}: IP tags are up to date: ${next_tags[*]}"
  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() {
  current_time=$(date +%s)

  # Check LXC status
  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..."
    last_lxc_status_check_time=${current_time}
    if check_status_changed "lxc"; then
      update_all_tags "lxc"
      last_update_lxc_time=${current_time}
    fi
  fi

  # Check VM status
  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..."
    last_vm_status_check_time=${current_time}
    if check_status_changed "vm"; then
      update_all_tags "vm"
      last_update_vm_time=${current_time}
    fi
  fi

  # Check network interface changes
  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 network interfaces..."
    last_fw_net_interface_check_time=${current_time}
    if check_status_changed "fw"; then
      update_all_tags "lxc"
      update_all_tags "vm"
      last_update_lxc_time=${current_time}
      last_update_vm_time=${current_time}
    fi
  fi

  # Force update if needed
  for type in "lxc" "vm"; do
    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} tags..."
      update_all_tags "$type"
      eval "${last_update_var}=${current_time}"
    fi
  done
}

# Initialize time variables
last_lxc_status_check_time=0
last_vm_status_check_time=0
last_fw_net_interface_check_time=0
last_update_lxc_time=0
last_update_vm_time=0

# Update tags for all containers or VMs of specified type
update_all_tags() {
  local type="$1"
  local list_cmd="pct"
  [[ "$type" == "vm" ]] && list_cmd="qm"

  # Get list of running containers/VMs
  local vmids
  vmids=$($list_cmd list 2>/dev/null | grep -v VMID | awk '{print $1}')
  
  # Подсчет количества
  local count=0
  for vmid in $vmids; do
    ((count++))
  done
  echo "Found ${count} running ${type}s"

  # Update tags for each container/VM
  for vmid in $vmids; do
    update_tags "$type" "$vmid"
  done
}

# main: Set the IP tags for all LXC containers and VMs
main() {
  while true; do
    check
    sleep "${LOOP_INTERVAL}"
  done
}

main

@tremor021
Copy link
Member

Checking LXC status...
Checking VM status...
Checking network interfaces...
Force updating lxc tags...
Found 1 running lxcs
Lxc 101: IP tags are up to date: 11
Force updating vm tags...
Found 1 running vms
Vm 100: IP tags are up to date: 

VM is not tagged though

@DesertGamer
Copy link
Contributor

Can you tell me, please, is the VM running? Does it have an interface?

@tremor021
Copy link
Member

Yes it runs

Image

@DesertGamer
Copy link
Contributor

Oh, you provided the logs above and the script apparently thinks that you have an empty IP address for the machine, I have now processed this exception, let's see what the logs will give.

#!/bin/bash
# =============== CONFIGURATION =============== #
readonly CONFIG_FILE="/opt/iptag/iptag.conf"
readonly DEFAULT_TAG_FORMAT="full"
readonly DEFAULT_CHECK_INTERVAL=60

# Load the configuration file if it exists
if [ -f "$CONFIG_FILE" ]; then
    # shellcheck source=./iptag.conf
    source "$CONFIG_FILE"
fi

# Convert IP to integer for comparison
ip_to_int() {
    local ip="$1"
    local a b c d
    IFS=. read -r a b c d <<< "${ip}"
    echo "$((a << 24 | b << 16 | c << 8 | d))"
}

# Check if IP is in CIDR
ip_in_cidr() {
    local ip="$1"
    local cidr="$2"

    if ! ipcalc -c "$ip" "$cidr" >/dev/null 2>&1; then
        return 1
    fi

    local network prefix
    network=$(echo "$cidr" | cut -d/ -f1)
    prefix=$(echo "$cidr" | cut -d/ -f2)

    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"

    # Optimize network check using prefix
    case $prefix in
        8)  [[ "$ip_a" == "$net_a" ]] ;;
        16) [[ "$ip_a.$ip_b" == "$net_a.$net_b" ]] ;;
        24) [[ "$ip_a.$ip_b.$ip_c" == "$net_a.$net_b.$net_c" ]] ;;
        32) [[ "$ip" == "$network" ]] ;;
        *)  return 1 ;;
    esac
    return $?
}

# Format IP address according to the configuration
format_ip_tag() {
    local ip="$1"
    local format="${TAG_FORMAT:-$DEFAULT_TAG_FORMAT}"

    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="$2"

    [[ -z "$cidrs" ]] && return 1

    local IFS=' '
    for cidr in $cidrs; do
        ip_in_cidr "$ip" "$cidr" && return 0
    done
    return 1
}

# Check if IP is valid
is_valid_ipv4() {
    local ip="$1"
    [[ "$ip" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]] || return 1
    
    local IFS='.' parts
    read -ra parts <<< "$ip"
    for part in "${parts[@]}"; do
        (( part >= 0 && part <= 255 )) || return 1
    done
    return 0
}

lxc_status_changed() {
  current_lxc_status=$(pct list 2>/dev/null)
  if [ "${last_lxc_status}" == "${current_lxc_status}" ]; then
    return 1
  else
    last_lxc_status="${current_lxc_status}"
    return 0
  fi
}

vm_status_changed() {
  current_vm_status=$(qm list 2>/dev/null)
  if [ "${last_vm_status}" == "${current_vm_status}" ]; then
    return 1
  else
    last_vm_status="${current_vm_status}"
    return 0
  fi
}

fw_net_interface_changed() {
  current_net_interface=$(ifconfig | grep "^fw")
  if [ "${last_net_interface}" == "${current_net_interface}" ]; then
    return 1
  else
    last_net_interface="${current_net_interface}"
    return 0
  fi
}

# Get VM IPs using MAC addresses and ARP table
get_vm_ips() {
    local vmid=$1
    local ips=""

    # Check if VM is running and log status
    if ! qm status "$vmid" 2>/dev/null | grep -q "status: running"; then
        echo "VM ${vmid}: not running"
        return 1
    fi

    # Get MAC addresses and log them
    local macs
    macs=$(qm config "$vmid" 2>/dev/null | grep -E 'net[0-9]+' | grep -oE '[a-fA-F0-9]{2}(:[a-fA-F0-9]{2}){5}')
    if [[ -z "$macs" ]]; then
        echo "VM ${vmid}: no network interfaces found"
        return 1
    fi

    # Look up IPs from ARP table using MAC addresses
    local found_ip=false
    for mac in $macs; do
        local ip
        ip=$(arp -an 2>/dev/null | grep -i "$mac" | grep -oE '([0-9]{1,3}\.){3}[0-9]{1,3}')
        if [[ -n "$ip" ]]; then
            ips+="$ip "
            found_ip=true
        fi
    done

    if [[ "$found_ip" == false ]]; then
        echo "VM ${vmid}: no IPs found in ARP table for MACs: ${macs}"
        # Попробуем получить IP напрямую из qemu-guest-agent
        local agent_ip
        agent_ip=$(qm agent "$vmid" network-get-interfaces 2>/dev/null | grep -oE '([0-9]{1,3}\.){3}[0-9]{1,3}' || true)
        if [[ -n "$agent_ip" ]]; then
            ips+="$agent_ip "
            echo "VM ${vmid}: found IP via qemu-guest-agent: ${agent_ip}"
        fi
    fi

    echo "${ips% }" # Remove trailing space
}

# Update tags for container or VM
update_tags() {
    local type="$1"
    local vmid="$2"
    local config_cmd="pct"
    [[ "$type" == "vm" ]] && config_cmd="qm"

    # Get current IPs
    local current_ips_full
    if [[ "$type" == "lxc" ]]; then
        current_ips_full=$(lxc-info -n "${vmid}" -i 2>/dev/null | grep -E "^IP:" | awk '{print $2}')
        if [[ -z "$current_ips_full" ]]; then
            echo "${type^} ${vmid}: no IPs found"
            return
        fi
    else
        current_ips_full=$(get_vm_ips "${vmid}")
    fi

    [[ -z "$current_ips_full" ]] && return

    # Parse current tags and create arrays for IP and non-IP tags
    local current_tags=() next_tags=() current_ip_tags=()
    mapfile -t current_tags < <($config_cmd config "${vmid}" 2>/dev/null | grep tags | awk '{print $2}' | sed 's/;/\n/g')

    # Separate IP and non-IP tags
    for tag in "${current_tags[@]}"; do
        if is_valid_ipv4 "${tag}" || [[ "$tag" =~ ^[0-9]+(\.[0-9]+)*$ ]]; then
            current_ip_tags+=("${tag}")
        else
            next_tags+=("${tag}")
        fi
    done

    # Create array of formatted IPs from current IPs
    local formatted_ips=()
    for ip in ${current_ips_full}; do
        if is_valid_ipv4 "${ip}" && ip_in_cidrs "${ip}" "${CIDR_LIST[*]}"; then
            formatted_ips+=("$(format_ip_tag "$ip")")
        fi
    done

    if [[ ${#formatted_ips[@]} -eq 0 ]]; then
        echo "${type^} ${vmid}: no valid IPs found in CIDR range: ${current_ips_full}"
        return
    fi

    # Compare arrays to detect changes
    local needs_update=false
    local added_ips=()

    # Check if all formatted IPs are in current tags
    for ip in "${formatted_ips[@]}"; do
        local found=false
        for tag in "${current_ip_tags[@]}"; do
            if [[ "$ip" == "$tag" ]]; then
                found=true
                break
            fi
        done
        if [[ "$found" == false ]]; then
            needs_update=true
            added_ips+=("$ip")
            next_tags+=("$ip")
        fi
    done

    # Add existing IP tags that are still valid
    for tag in "${current_ip_tags[@]}"; do
        if [[ " ${formatted_ips[*]} " =~ " ${tag} " ]]; then
            if [[ ! " ${next_tags[*]} " =~ " ${tag} " ]]; then
                next_tags+=("$tag")
            fi
        fi
    done

    # Update tags or show current status
    if [[ "$needs_update" == true ]]; then
        echo "${type^} ${vmid}: adding IP tags: ${added_ips[*]}"
        $config_cmd set "${vmid}" -tags "$(IFS=';'; echo "${next_tags[*]}")" &>/dev/null
    else
        if [[ ${#current_ip_tags[@]} -gt 0 ]]; then
            echo "${type^} ${vmid}: IP tags already set: ${current_ip_tags[*]}"
        else
            echo "${type^} ${vmid}: setting initial IP tags: ${formatted_ips[*]}"
            $config_cmd set "${vmid}" -tags "$(IFS=';'; echo "${formatted_ips[*]}")" &>/dev/null
        fi
    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
}

# Update tags for all containers or VMs of specified type
update_all_tags() {
    local type="$1"
    local list_cmd="pct"
    [[ "$type" == "vm" ]] && list_cmd="qm"

    local vmids count=0
    vmids=$($list_cmd list 2>/dev/null | grep -v VMID | awk '{print $1}')
    
    for vmid in $vmids; do
        ((count++))
    done
    echo "Found ${count} running ${type}s"

    for vmid in $vmids; do
        update_tags "$type" "$vmid"
    done
}

# Check status changes and update tags
check() {
    local current_time
    current_time=$(date +%s)

    # Process each type (lxc, vm) with their intervals
    for type in "lxc" "vm"; do
        local interval_var="${type^^}_STATUS_CHECK_INTERVAL"
        local last_check_var="last_${type}_status_check_time"
        local last_update_var="last_update_${type}_time"
        
        if [[ "${!interval_var}" -gt 0 ]] && \
           (( current_time - ${!last_check_var} >= ${!interval_var} )); then
            echo "Checking ${type^^} status..."
            eval "${last_check_var}=\$current_time"
            if check_status_changed "$type"; then
                update_all_tags "$type"
                eval "${last_update_var}=\$current_time"
            fi
        fi
    done

    # Check network interfaces
    if [[ "${FW_NET_INTERFACE_CHECK_INTERVAL}" -gt 0 ]] && \
       (( current_time - last_fw_net_interface_check_time >= FW_NET_INTERFACE_CHECK_INTERVAL )); then
        echo "Checking network interfaces..."
        last_fw_net_interface_check_time=$current_time
        if check_status_changed "fw"; then
            update_all_tags "lxc"
            update_all_tags "vm"
            last_update_lxc_time=$current_time
            last_update_vm_time=$current_time
        fi
    fi

    # Force updates if needed
    for type in "lxc" "vm"; do
        local last_update_var="last_update_${type}_time"
        if (( current_time - ${!last_update_var} >= FORCE_UPDATE_INTERVAL )); then
            echo "Force updating ${type} tags..."
            update_all_tags "$type"
            eval "${last_update_var}=\$current_time"
        fi
    done
}

# Initialize time variables
declare -g last_lxc_status_check_time=0
declare -g last_vm_status_check_time=0
declare -g last_fw_net_interface_check_time=0
declare -g last_update_lxc_time=0
declare -g last_update_vm_time=0

# Main loop
main() {
    while true; do
        check
        sleep "${LOOP_INTERVAL:-$DEFAULT_CHECK_INTERVAL}"
    done
}

main

@DesertGamer
Copy link
Contributor

DesertGamer commented Apr 9, 2025

I will clarify in advance that I never caught this error during my tests and am surprised that it happened at all.

I also saw that according to your previous logs, you had the XXX.XXX format, and now XXX, have you changed it?

If you changed it in the iptag.conf file, then try using a different format, for example last_two_octets, maybe then this error won't happen and it's about the format.

@MickLesk
Copy link
Member Author

@DesertGamer can you push all latest changes into live PR?

@DesertGamer
Copy link
Contributor

@MickLesk I'm sorry, I don't understand much English and the word "PR" is unclear to me, can you explain what I need to do?

@MickLesk
Copy link
Member Author

Push all of your Changes from VED in VE in your Draft PR (Pull Request): community-scripts/ProxmoxVE#3531

@DesertGamer
Copy link
Contributor

@MickLesk Thanks for explaining, I sent the commit.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants