Network Address Parameter verification failed. (400)

millerwissen

New Member
Feb 21, 2024
13
2
3
Hello Everyone!

I've been trying Proxmox recently on a server and I'm having a rather unusual problem with the web management interface.

I created a 'Linux Bridge' to have an internal switch (similar to what i'm used to in Hyper-V) and I want to assign a local ipv4 and ipv6 to that interface that will be routed through a complex internal vpn network that has dual-stack internal ips.

If i try to setup a 10.0.0.x ip address alone it will accept it just fine but whenever I try to setup a local ipv6 i get this error:

Parameter verification failed. (400)

address: fc:99::a:1:1 with type 'RESERVED', cannot be used as host IPv6 address

This makes no sense to me as it stops me from using a local ipv6 range but that's the entire point of it..

Is there any way to override this behaviour or is this a bug to be reported?
 
I forgot to update here, but basically if you use NAT66 or internal IPv6 it's simply a /usr/share/perl5/PVE/API2/Network.pm part of the code that does that check, you have 2 options obviously you can edit /etc/network/interfaces manually to bypass but it may not be as convenient as using the GUI.

So here's a script that basically kills that function altogether for Proxmox VE 8.3 but keep in mind this may be changed in future updates and the file can be overwritten so either adapt the script as needed or hopefully the devs that be will finally understand some people don't want to use public IPv6 on their hypervisors, and remember to use the code with caution don't brick a live production server please :)

One more trick you can also do, if you don't want to use the script, is a mix of nano + gui, just add your internal ip such as f5::3:1/64 as 21f5::3:1/64 then save and manually edit the file to remove the 21 but it's quicker than typing the whole thing for every interface and bridge etc

Code:
Copy the following to a server using ssh:

nano fix-ipv6.sh
=============================================================================================
#!/bin/bash

# IPv6 Fix Script for Proxmox
set -e

# File Paths
TARGET_FILE="/usr/share/perl5/PVE/API2/Network.pm"
BACKUP_FILE="${TARGET_FILE}.bak"

# Validate the existence of the target file
if [ ! -f "$TARGET_FILE" ]; then
    echo "Error: Target file $TARGET_FILE does not exist."
    exit 1
fi

# Create a backup if it doesn't already exist
if [ ! -f "$BACKUP_FILE" ]; then
    echo "Creating a backup of the original file..."
    cp "$TARGET_FILE" "$BACKUP_FILE"
    echo "Backup created at $BACKUP_FILE"
else
    echo "Backup already exists at $BACKUP_FILE"
fi

# Locate and modify the IPv6 validation logic
echo "Patching IPv6 validation logic to allow RESERVED addresses..."
awk '
/my \$check_ipv6_settings = sub {/ { inside_function = 1; print; next }
/^\};$/ && inside_function { inside_function = 0; print; next }
inside_function && /if \(defined\(\$type\)/ {
    print "    # Allow RESERVED IPv6 addresses explicitly";
    print "    if (defined($type) && $type eq \"RESERVED\") { return; }";
    print; next;
}
{ print }
' "$TARGET_FILE" > "${TARGET_FILE}.tmp" && mv "${TARGET_FILE}.tmp" "$TARGET_FILE"

# Validate the patched file
echo "Validating syntax of the patched file..."
if ! perl -c "$TARGET_FILE"; then
    echo "Syntax error detected in $TARGET_FILE. Restoring the backup."
    cp "$BACKUP_FILE" "$TARGET_FILE"
    exit 1
fi

# Restart Proxmox services
echo "Restarting Proxmox services..."
systemctl restart pveproxy || { echo "Failed to restart pveproxy. Check logs for details."; exit 1; }
systemctl restart pvedaemon || { echo "Failed to restart pvedaemon. Check logs for details."; exit 1; }

echo "IPv6 validation patch applied successfully!"


=============================================================================================
Save

bash fix-ipv6.sh
 

Attachments

Last edited:
Sorry everyone been busy but here's the new updated code, this really shouldn't be happening and I can only hope the proxmox devs will stop doing this because it is irritating and people will go out of their way to stop nonsense like this and this is how you start conflicts in the world.

Also for people interested more on Local IPv6 and NAT66 please read this: https://forum.opnsense.org/index.php?topic=47644.0 on the OPNSense forums.

As always

nano fix-ipv6-8.4.14.sh

(paste the contents inside the file) and run with:

bash fix-ipv6-8.4.14.sh

Code:
#!/bin/bash
# Proxmox 8.4.14: bypass IPv6 "type" gating in GUI (allow all syntactically valid IPv6)
# Reluctantly made by Miller Wissen with AI help and syntax check
# Keeps only basic IPv6 syntax and 0..128 mask checks in $check_ipv6_settings
# This should not exist to begin with, it's ridiculous users have to do this and devs waste their time blocking people who use internal f000::/4 ULA and non-ULA GUA NAT66/Local IPv6
# To whoever cares stop doing this or the Proxmox project will end up being forked to address these very problems you keep introducing for no reason if your paying
# customers want this nonsense for whatever reason then let this be an option somewhere and not the default either, this is a perfectly valid use case even for Enterprise customers.
# STOP TELLING PEOPLE HOW THEY'RE ALLOWED TO USE THEIR fking SERVERS WITH OPEN SOURCE SOFTWARE AND FOCUS ON FIXING BUGS AND OPTIMISING VIRTUALISATION INSTEAD
# Sadly this may not work in future versions of proxmox so if it doesn't come back to the forum and eventually i'll post a follow up or a link to the new Fork..
# Last Update: 11 Oct 2025

set -euo pipefail

TARGET="/usr/share/perl5/PVE/API2/Network.pm"
BACKUP="${TARGET}.bak"

[ -f "$TARGET" ] || { echo "ERR: $TARGET not found"; exit 1; }

# One-time backup
if [ ! -f "$BACKUP" ]; then
  cp -a "$TARGET" "$BACKUP"
  echo "Backup: $BACKUP"
else
  echo "Backup already exists: $BACKUP"
fi

# Skip if already patched
if grep -q 'bypass type gating (Proxmox)' "$TARGET"; then
  echo "Already patched; nothing to do."
  exit 0
fi

# Ensure we start from a known-good baseline (in case of previous failed edits)
if ! grep -q 'my \$check_ipv6_settings = sub' "$TARGET"; then
  echo "Unexpected file contents; restoring backup for safety."
  cp -a "$BACKUP" "$TARGET"
fi

# Insert early return between the syntax check and the first use of $binip.
# We DO NOT remove 'my $binip = ...', we only add a return before it.
perl -0777 -pe '
  my $ok = 0;
  $ok ||= s{
    (raise_param_exc\(\{\s*address\s*=>\s*"\$address\s+is\s+not\s+a\s+valid\s+host\s+IPv6\s+address\."\s*\}\)\s*\n\s*if\s*!Net::IP::ip_is_ipv6\(\$address\);\s*\n)
    (\s*my\s+\$binip\s*=\s*ipv6_tobin\(\$address\)\s*;)
  }{$1    return; # bypass type gating (Proxmox)\n$2}xms;

  # Fallback: if the exact context changed, insert before first my $binip line.
  $ok ||= s{
    (\n\s*)my\s+\$binip\s*=\s*ipv6_tobin\(\$address\)\s*;
  }{$1return; # bypass type gating (Proxmox)\n$1my $binip = ipv6_tobin($address);\n}ms;

  $ok or die "Patch marker not found; aborting without changes.\n";
' "$TARGET" > "${TARGET}.tmp"

mv "${TARGET}.tmp" "$TARGET"
echo "Patch inserted."

# Verify syntax
perl -c "$TARGET" >/dev/null || { echo "Perl syntax failed; restoring backup."; cp -a "$BACKUP" "$TARGET"; perl -c "$TARGET" || true; exit 1; }
echo "Perl syntax OK."

# Restart GUI daemons
systemctl restart pveproxy
systemctl restart pvedaemon
echo "Done. GUI now accepts any syntactically valid IPv6 (including f000::/4, e.g. f117::/64 f35::/64 etc ...)."
 

Attachments

This is the updated script for Proxmox VE 9.14 i used AI to create some extra fallback and error handling conditions to self heal and make it a bit more robust, i tested this code on a few machines and it's working perfectly so now i'm sharing here.

I don't understand why proxmox team just refuses to fix this and allow people to use whatever custom IPv6 ranges they may be using via the GUI this is extremely frustrating and pointless waste of time from both sides, remember this is OPEN SOURCE software.

This should handle any subnet 0000::/4 f000::/4 a000::/4 etc anything else other than just the strict RFC ULAs and GUAs. Whether you use IPv6 on your SAN/NAT66 or any "non RFC compliant" subnet on your own private network of which you have absolutely no reason to be told not use if you wish to do so this will now behave as it should.

And as a last reminder this only bypasses the Web GUI check, you can always just nano /etc/network/interfaces and do as you please, proxmox is just debian under the hood.

Bash:
#!/bin/bash
# Proxmox 9.1.x: bypass IPv6 "type" gating in GUI/API (accept any syntactically valid IPv6)
# Self-healing + rerunnable ensure-state patcher for:
#   /usr/share/perl5/PVE/API2/Network.pm
#
# Behaviour:
# - If Network.pm is broken, restore it to a compilable baseline first.
# - Remove any previous "bypass type gating (Proxmox...)" lines (including bad older patches).
# - Insert exactly one early return in check_ipv6_settings after ip_is_ipv6() syntax check.
# - Always restart pveproxy + pvedaemon.
#
# Last Update: 2026-01-08

set -euo pipefail

# Avoid perl locale warnings on nodes without generated locales
export LC_ALL=C
export LANG=C

TARGET="/usr/share/perl5/PVE/API2/Network.pm"
MARKER="bypass type gating (Proxmox)"
TS="$(date -u +%Y%m%dT%H%M%SZ)"
BACKUP="${TARGET}.bak-ipv6.${TS}"

TMPFILE=""
cleanup() {
  if [ -n "${TMPFILE:-}" ] && [ -f "$TMPFILE" ]; then
    rm -f "$TMPFILE"
  fi
}
trap cleanup EXIT INT TERM

[ -f "$TARGET" ] || { echo "ERR: $TARGET not found"; exit 1; }

owning_pkg() {
  dpkg -S "$TARGET" 2>/dev/null | head -n1 | cut -d: -f1 || true
}

restore_from_cached_deb() {
  local pkg="$1"
  local deb=""
  deb="$(ls -1t "/var/cache/apt/archives/${pkg}"_*".deb" 2>/dev/null | head -n1 || true)"
  [ -n "$deb" ] || return 1

  echo "Attempting restore from cached deb: $deb"
  TMPFILE="${TARGET}.restore.$$"
  dpkg-deb --fsys-tarfile "$deb" | tar -xOf - "./usr/share/perl5/PVE/API2/Network.pm" > "$TMPFILE"
  mv "$TMPFILE" "$TARGET"
  TMPFILE=""
  return 0
}

restore_from_reinstall() {
  local pkg="$1"
  echo "Attempting restore via reinstall: apt-get install --reinstall -y $pkg"
  apt-get update
  apt-get install --reinstall -y "$pkg"
}

restore_from_compilable_backup() {
  local cand=""

  for cand in $(ls -1t "${TARGET}".bak-ipv6.* 2>/dev/null || true); do
    [ -f "$cand" ] || continue
    if perl -c "$cand" >/dev/null 2>&1; then
      echo "Restoring from compilable backup: $cand"
      cp -a "$cand" "$TARGET"
      return 0
    fi
  done

  for cand in "${TARGET}.bak" "${TARGET}.orig" "${TARGET}.dpkg-dist"; do
    [ -f "$cand" ] || continue
    if perl -c "$cand" >/dev/null 2>&1; then
      echo "Restoring from compilable backup: $cand"
      cp -a "$cand" "$TARGET"
      return 0
    fi
  done

  return 1
}

ensure_target_compiles_or_restore() {
  if perl -c "$TARGET" >/dev/null 2>&1; then
    return 0
  fi

  echo "WARN: $TARGET does not compile; attempting self-heal restore."

  if restore_from_compilable_backup; then
    perl -c "$TARGET" >/dev/null 2>&1
    return 0
  fi

  local pkg
  pkg="$(owning_pkg)"
  [ -n "$pkg" ] || { echo "ERR: cannot determine owning dpkg package for $TARGET"; exit 1; }

  if restore_from_cached_deb "$pkg"; then
    if perl -c "$TARGET" >/dev/null 2>&1; then
      echo "Restore from cached deb succeeded."
      return 0
    fi
    echo "WARN: cached-deb restore did not compile; continuing."
  fi

  restore_from_reinstall "$pkg"

  perl -c "$TARGET" >/dev/null 2>&1 || {
    echo "ERR: restore failed; $TARGET still does not compile after reinstall."
    exit 1
  }

  echo "Restore via reinstall succeeded."
}

apply_patch_ensure_state() {
  cp -a "$TARGET" "$BACKUP"
  echo "Backup: $BACKUP"

  TMPFILE="${TARGET}.tmp.$$"
  export PVE_IPV6_BYPASS_MARKER="$MARKER"

  perl - "$TARGET" > "$TMPFILE" <<'PERL'
use strict;
use warnings;

my $file = shift @ARGV or die "ERR: missing target file arg\n";
open(my $fh, "<", $file) or die "ERR: cannot read $file: $!\n";
my @lines = <$fh>;
close($fh);

my $marker = $ENV{PVE_IPV6_BYPASS_MARKER} // 'bypass type gating (Proxmox)';
my $marker_re = qr/bypass type gating \(Proxmox/;

# Find the check_ipv6_settings sub start
my $start = -1;
for (my $i = 0; $i <= $#lines; $i++) {
    if ($lines[$i] =~ /my\s+\$check_ipv6_settings\s*=\s*sub\s*\{\s*$/) {
        $start = $i;
        last;
    }
}
die "ERR: check_ipv6_settings sub start not found\n" if $start < 0;

# Find the sub end by brace depth
my $depth = 0;
my $end = -1;
for (my $i = $start; $i <= $#lines; $i++) {
    my $l = $lines[$i];
    my $opens  = ($l =~ tr/{/{/);
    my $closes = ($l =~ tr/}/}/);
    $depth += ($opens - $closes);
    if ($depth == 0) { $end = $i; last; }
}
die "ERR: check_ipv6_settings sub end not found\n" if $end < 0;

# Extract block and remove any prior bypass marker lines (self-heal)
my @blk = @lines[$start..$end];
@blk = grep { $_ !~ $marker_re } @blk;

my $inserted = 0;

# Primary insertion point: right after "if !Net::IP::ip_is_ipv6($address);"
for (my $i = 0; $i <= $#blk; $i++) {
    if ($blk[$i] =~ /^(\s*)if\s*!Net::IP::ip_is_ipv6\(\$address\);\s*$/) {
        my $indent = $1;
        my $line = $indent . "return; # $marker\n";
        splice(@blk, $i+1, 0, $line);
        $inserted = 1;
        last;
    }
}

# Fallback: insert before first "my $binip = ipv6_tobin($address);"
if (!$inserted) {
    for (my $i = 0; $i <= $#blk; $i++) {
        if ($blk[$i] =~ /^(\s*)my\s+\$binip\s*=\s*ipv6_tobin\(\$address\)\s*;\s*$/) {
            my $indent = $1;
            my $line = $indent . "return; # $marker\n";
            splice(@blk, $i, 0, $line);
            $inserted = 1;
            last;
        }
    }
}

die "ERR: insertion point not found in check_ipv6_settings\n" if !$inserted;

# Reassemble full file
splice(@lines, $start, ($end - $start + 1), @blk);

# Ensure exactly one marker exists
my $count = 0;
for my $l (@lines) { $count++ if $l =~ $marker_re; }
die "ERR: unexpected marker count after patch: $count\n" if $count != 1;

print @lines;
PERL

  mv "$TMPFILE" "$TARGET"
  TMPFILE=""

  # Verify Perl syntax; restore from this run's backup if broken
  if ! perl -c "$TARGET" >/dev/null 2>&1; then
    echo "ERR: Perl syntax failed after patch; restoring from: $BACKUP"
    cp -a "$BACKUP" "$TARGET"
    perl -c "$TARGET" >/dev/null 2>&1 || { echo "ERR: restored backup also fails to compile"; exit 1; }
  fi
}

restart_services() {
  systemctl restart pveproxy
  systemctl restart pvedaemon
}

# 1) Self-heal to a compilable baseline if needed
ensure_target_compiles_or_restore

# 2) Ensure-state patch (rerunnable, self-heals prior marker edits)
apply_patch_ensure_state

# 3) Final verify + restart
perl -c "$TARGET" >/dev/null 2>&1
restart_services

echo "OK: IPv6 type gating bypass ensured (syntactic IPv6 accepted)."
echo "Marker line:"
grep -n "bypass type gating (Proxmox" "$TARGET" || true

After updates etc you may need to rerun this script as usual, and if future updates once again change the logic I will make new scripts and post the updated scripts here.

You can just "nano fix-ipv6.sh" and run with "bash fix-ipv6.sh".
 
Last edited: