Feuerfest

Just the private blog of a Linux sysadmin

Fix keepalived error: bind unicast_src - 99 cannot assign requested address

TL;DR: The configured unicast_src IP isn't present on any network interface. In my case DHCPv6 was to blame.

I accidentally unplugged the power cable from my RaspberryPi 4 today. Due to this I learned a few things today.

  1. First that my home DSL router (a FritzBox) doesn't always honor the preferred IPv4/v6 addresses send in DHCP-Requests
    • /etc/dhcpcd.conf did contain static ip_address=... and static ip6_address=...
  2. The FritzBox can't set DHCP reservations for IPv6 addresses - only IPv4 - WHY!?
  3. I have to read the keepalived error message while actually using my brain
    • I stumbled across the cannot assign requested address and thought of DHCP and was confused why the hell keepalived does DHCP things (the word requested mislead me)
    • In the following line the reason is written in plain text...  entering FAULT state (src address not configured)
  4. Static IP-configuration for servers was, is and will always be the best
  5. A mixed static & dynamic IPv6  configuration isn't hard at all once you read a bit about SLAAC

Long story short, this was the keepalived error I got. The VRRP-Instance immediately went into FAULT state and stayed there.

root@raspi:~# systemctl status keepalived.service
[...]
Feb 05 13:14:22 raspi Keepalived_vrrp[1279]: Delaying startup for 5 seconds
Feb 05 13:14:22 raspi Keepalived[1278]: Startup complete
Feb 05 13:14:22 raspi systemd[1]: Started keepalived.service - Keepalive Daemon (LVS and VRRP).
Feb 05 13:14:22 raspi Keepalived_vrrp[1279]: bind unicast_src fd87:f53:25b4:0:231d:4cbb:bca7:10 failed 99 - Cannot assign requested address
Feb 05 13:14:22 raspi Keepalived_vrrp[1279]: (VI_2): entering FAULT state (src address not configured)
Feb 05 13:14:22 raspi Keepalived_vrrp[1279]: (VI_2) Entering FAULT STATE
Feb 05 13:14:22 raspi Keepalived_vrrp[1279]: VRRP_Group(ALL) Syncing instances to FAULT state

At first I skipped the following line:

Feb 05 13:14:22 raspi Keepalived_vrrp[1279]: (VI_2): entering FAULT state (src address not configured)

Hence I searched a bit and found an older GitHub issue where this problem was explained with VRRP trying to do stuff to fast, while the interface wasn't ready. The solution mentioned in keepalived issue #2237: Keepalived entering fault state on reboot was to set vrrp_startup_delay inside the global_defs section of /etc/keepalived/keepalived.conf. However this was already the present in my case.

Yeah, turns out the configured unicast_src IP wasn't present on any interface. As the FritzBox deemed it fit to assign a random one from the configured DHCP-Range. We can verify this quickly by grep'ing for the IPv6 address.

root@raspi:~ # ip -6 a | grep fd87:f53:25b4:0:231d:4cbb:bca7:10
root@raspi:~ #

The solution

In my case I finally switched to a mixed static and dynamic IPv6 setup. Configuring the local ULA address as a static one, but still receive and apply the router advertisement (RA) to get a global IPv6 so my RaspberryPi can still connect to the Internet.

Then it showed up on the interface.

root@raspi:~ # ip -6 a | grep fd87:f53:25b4:0:231d:4cbb:bca7:10
    inet6 fd87:f53:25b4:0:231d:4cbb:bca7:10/64 scope global
root@raspi:~ #

Another viable solution would of course be to just reboot the RaspberryPi and hope your DHCP-Server now assigns the correct IP. However my FritzBox only allows to set an IPv4 reservation in the DHCP settings. IPv6 addresses can't be used for DHCP reservations at all. So this was no solution for me.

If you want to know how to configured a mixed static and dynamic IPv6 read here: Configuring an mixed IPv6 setup - static ULA, dynamic GLA

Comments

Configuring an mixed IPv6 setup - static ULA, dynamic GLA

In Fix keepalived error: bind unicast_src - 99 cannot assign requested address I mentioned that I fixed my problem with a mixed static & dynamic IPv6 setup. Here is how I did it.

Status quo

For a few years I followed the Raspbian recommendation to use DHCP to assign the static IP. And it worked - until it didn't. This was my config. Note that I didn't use a fallback profile. I like to notice when DHCP doesn't work.

root@raspi:~# cat /etc/dhcpcd.conf
[...]
interface eth0
        static ip_address=192.168.1.10/24
        static ip6_address=fd87:f53:25b4:0:231d:4cbb:bca7:10/64
        static routers=192.168.1.1
        static domain_name_servers=127.0.0.1 ::1

# It is possible to fall back to a static IP if DHCP fails:
# define static profile
#profile static_eth0
#static ip_address=192.168.1.23/24
#static routers=192.168.1.1
#static domain_name_servers=192.168.1.1

# fallback to static profile on eth0
#interface eth0
#fallback static_eth0

Due to an accidental power loss my RaspberryPi rebooted and got a new IPv4 and IPv6, totally different from the configured ones.

The changed IPv4 was easily identified. I forgot to set a DHCP reservation for the MAC address in my DSL router. I suspected then that I also forgot this for the IPv6. Only to notice: My FritzBox 7530 doesn't allow to add IP/MAC reservations for IPv6. Only IPv4 addresses are supported.

And that was the moment where I had enough and decided to ditch DHCP all together.

For IPv4 this was easy enough.

root@raspi:~# cat /etc/network/interfaces.d/ipv4
auto eth0
iface eth0 inet static
        address 192.168.1.10
        netmask 255.255.255.0
        gateway 192.168.1.1

However for IPv6 it took me a few minutes. Specify address and netmask, done. Right?

Well, no. Internet access wasn't working. A quick check revealed that the GLA address was missing.

root@raspi:~# ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host noprefixroute
       valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
    link/ether aa:aa:aa:aa:aa:aa brd ff:ff:ff:ff:ff:ff
    inet 192.168.1.10/24 brd 192.168.1.255 scope global eth0
       valid_lft forever preferred_lft forever
    inet6 fd87:f53:25b4:0:231d:4cbb:bca7:10/64 scope global
       valid_lft forever preferred_lft forever
    inet6 fe80::aaaa:bbbb:cccc:dddd/64 scope link
       valid_lft forever preferred_lft forever

Hosts in my LAN were perfectly reachable. A ping to an public IPv6 didn't succeed.

root@raspi:~# ping6 google.de
PING google.de(lcmuca-ah-in-x03.1e100.net (2a00:1450:4016:803::2003)) 56 data bytes
^C
--- 2a00:1450:4016:803::2003 ping statistics ---
13 packets transmitted, 0 received, 100% packet loss, time 12441ms

Turns out, when you configure a static Unique Local Address (ULA), which is the IPv6 equivalent to our beloved RFC1918 IPv4 (192.168.0.0/16, etc.), Linux doesn't listen to Router Advertisements (RAs) anymore. Hence no Global Link Address (GLA).

The small details are to set autoconf 1 and accept_ra 2 for the interface. This is also documented in the Debian Wiki. With that knowledge I changed my config. Defining the ULA IPv6 as static and not relying on DHCP also has other stability advantages, as I run some services on keepalived VIPs.

root@raspi:~# cat /etc/network/interfaces.d/ipv6
# IPv6
auto eth0
iface eth0 inet6 static
        address fd87:f53:25b4:0:231d:4cbb:bca7:10
        netmask 64
        # Mixing static and dynamic IPv6
        # from: https://wiki.debian.org/NetworkConfiguration
        # use SLAAC to get global IPv6 address from the router
        # we may not enable ipv6 forwarding, otherwise SLAAC gets disabled
        #
        # Automatically create IPv6 addresses based on Router Advertisements (RA)
        autoconf 1
        # Always accept RAs, even if a static IPv6 address is configured
        # as normally Linux doesn't listen to RAs anymore when a static IPv6 is assigned
        accept_ra 2

Disabling DHCP

And don't forget to disable the DHCP service.

root@raspi:~# systemctl stop dhcpcd.service
root@raspi:~# systemctl disable dhcpcd.service
Synchronizing state of dhcpcd.service with SysV service script with /lib/systemd/systemd-sysv-install.
Executing: /lib/systemd/systemd-sysv-install disable dhcpcd
Removed "/etc/systemd/system/dhcpcd5.service".
Removed "/etc/systemd/system/multi-user.target.wants/dhcpcd.service".

After all these years?

Once again I am left wondering why I had this problem for the first time in 2026. After all IPv6 is 25 years old..

Comments

Apache 2 stops working when the terminal window is resized?

I encountered an interesting Mastodon post. In it a user describes the problem that the Apache 2, which is running in foreground inside a docker container, stops when the terminal window is resized.

What?

And it is even written to the logfile:

[Mon Jan 12 17:38:14 2026] [notice] caught SIGWINCH, shutting down gracefully

Clearly this must be some strange bug regarding signal handling in Apache, right?

Turns out: No. This is just a misuse of the WINCH signal. Normally it signals a process that the terminal resolution has changed. The process can then adjust output etc.

But Apache uses the signal to initiate a graceful-stop. This is even documented under https://httpd.apache.org/docs/2.4/stopping.html#gracefulstop

"The WINCH or graceful-stop signal causes the parent process to advise the children to exit after their current request [...]"

However, I would prefer if they would explicitly point out that they:

  • Re-Use that signal to achieve a completely different thing
  • Mention the possible implications this has if your Apache process is executed in the foreground
Comments

Ah yes, the reminder to perform a full-backup at least once each year

After ~14 years of service one of the WD Green drives failed. It had a few bad sectors for years, but the count didn't increase. Hence I didn't replace the drive immediately. Now it started reporting I/O errors too a few hours ago.

As the situation was foreseeable I already bought two replacement drives. Now the first one is replaced and the 9TB RAID5 will take roughly more than a day to rebuild.

root@DiskStation:~# cat /proc/mdstat
Personalities : [linear] [raid0] [raid1] [raid10] [raid6] [raid5] [raid4]
md2 : active raid5 sdc3[5] sda3[0] sdd3[4] sdb3[1]
      8776632768 blocks super 1.2 level 5, 64k chunk, algorithm 2 [4/3] [UU_U]
      [>....................]  recovery =  0.0% (280704/2925544256) finish=2605.1min speed=18713K/sec

md1 : active raid1 sdc2[2] sda2[0] sdb2[1] sdd2[3]
      2097088 blocks [4/4] [UUUU]

md0 : active raid1 sdc1[2] sda1[0] sdb1[1] sdd1[3]
      2490176 blocks [4/4] [UUUU]

unused devices: <none>

A few minutes later the estimate already went down by 1100 minutes. I'll see how long it really took in the end.

root@DiskStation:~# cat /proc/mdstat
Personalities : [linear] [raid0] [raid1] [raid10] [raid6] [raid5] [raid4]
md2 : active raid5 sdc3[5] sda3[0] sdd3[4] sdb3[1]
      8776632768 blocks super 1.2 level 5, 64k chunk, algorithm 2 [4/3] [UU_U]
      [>....................]  recovery =  0.3% (9309952/2925544256) finish=1584.8min speed=30667K/sec

md1 : active raid1 sdc2[2] sda2[0] sdb2[1] sdd2[3]
      2097088 blocks [4/4] [UUUU]

md0 : active raid1 sdc1[2] sda1[0] sdb1[1] sdd1[3]
      2490176 blocks [4/4] [UUUU]

unused devices: <none>

After Christmas I will replace the 4th drive also, as this also reported a bad sector as of today. And having swapped out 2 out of 4 drives is somewhat okay-ish for a 4 drive RAID5 with no hotspare. I don't assume the remaining to drives will fail completely within such a short period, that I can't replace at least one (and let the RAID rebuild, of course!).

Luckily I made my full-backup a few days ago. 😁

Comments

Installing & configuring ISSO under Debian Trixie

For some reasons ISSO stopped working after I upgraded to Debian Trixie. Steps which weren't necessary when I first wrote my post Integrating Isso into Bludit via Apache2 and WSGI.

Take this post as an addition to the previous one.

Troubleshooting steps for Debian Trixie

ModuleNotFoundError: No module named 'pkg_resources'

This one was solved with an pip install setuptools. Despite being already installed and shown as installed when pip list is executed.

ModuleNotFoundError: No module named '_cffi_backend'

This one still baffles me. pip list shows the cffi module as being installed. However under Debian the package python3-cffi-backend must be installed for me. I think that I maybe hadn't all needed ffi packages installed, so the compiled /opt/your-venv-here/lib/python3.13/site-packages/_cffi_backend.cpython-313-x86_64-linux-gnu.so isn't fully working. Have to investigate.

If these two things are taken care of ISSO works fine under Debian Trixie.

Conclusion

All in all I suspect that I did get those errors as I did an in-place dist-upgrade from bullseye to bookworm to trixie on one afternoon. No fresh re-install as I currently lack the time for this.

Comments

Using rgxg (ReGular eXpression Generator) to generate a RegEx that matches all IPv4 & IPv6 addresses

In "Little Helper Scripts - Part 3: My Homelab CA Management Scripts", I mention that the regular expressions I use for identifying IPv4 and IPv6 addresses are rather basic. In particular, the IPv6 RegEx simply assumes that anything containing a colon is an IPv6 address.

When I jokingly asked on Mastodon if anyone had a better RegEx, I mentioned my script enhancements. My former colleague Klaus Umbach recommended rgxg (ReGular eXpression Generator) to me. It sounded like it would solve my problem exactly.

Installing rgxg

The installation on Debian is pretty easy as there is a package available.

root@host:~# apt-get install rgxg
Reading package lists... Done
Building dependency tree... Done
Reading state information... Done
The following additional packages will be installed:
  librgxg0
The following NEW packages will be installed:
  librgxg0 rgxg
0 upgraded, 2 newly installed, 0 to remove and 0 not upgraded.
Need to get 24.4 kB of archives.
After this operation, 81.9 kB of additional disk space will be used.
Do you want to continue? [Y/n]
Get:1 http://debian.tu-bs.de/debian bookworm/main amd64 librgxg0 amd64 0.1.2-5 [15.3 kB]
Get:2 http://debian.tu-bs.de/debian bookworm/main amd64 rgxg amd64 0.1.2-5 [9,096 B]
Fetched 24.4 kB in 0s (200 kB/s)
Selecting previously unselected package librgxg0:amd64.
(Reading database ... 40580 files and directories currently installed.)
Preparing to unpack .../librgxg0_0.1.2-5_amd64.deb ...
Unpacking librgxg0:amd64 (0.1.2-5) ...
Selecting previously unselected package rgxg.
Preparing to unpack .../rgxg_0.1.2-5_amd64.deb ...
Unpacking rgxg (0.1.2-5) ...
Setting up librgxg0:amd64 (0.1.2-5) ...
Setting up rgxg (0.1.2-5) ...
Processing triggers for man-db (2.11.2-2) ...
Processing triggers for libc-bin (2.36-9+deb12u10) ...
root@host:~#

Generating a RegEx for IPv6 and IPv4

Klaus already delivered the example for the complete IPv6 address space. For IPv4 it is equally easy:

# RegEx for the complete IPv6 address space
user@host:~$ rgxg cidr ::0/0
((:(:[0-9A-Fa-f]{1,4}){1,7}|::|[0-9A-Fa-f]{1,4}(:(:[0-9A-Fa-f]{1,4}){1,6}|::|:[0-9A-Fa-f]{1,4}(:(:[0-9A-Fa-f]{1,4}){1,5}|::|:[0-9A-Fa-f]{1,4}(:(:[0-9A-Fa-f]{1,4}){1,4}|::|:[0-9A-Fa-f]{1,4}(:(:[0-9A-Fa-f]{1,4}){1,3}|::|:[0-9A-Fa-f]{1,4}(:(:[0-9A-Fa-f]{1,4}){1,2}|::|:[0-9A-Fa-f]{1,4}(::[0-9A-Fa-f]{1,4}|::|:[0-9A-Fa-f]{1,4}(::|:[0-9A-Fa-f]{1,4}))))))))|(:(:[0-9A-Fa-f]{1,4}){0,5}|[0-9A-Fa-f]{1,4}(:(:[0-9A-Fa-f]{1,4}){0,4}|:[0-9A-Fa-f]{1,4}(:(:[0-9A-Fa-f]{1,4}){0,3}|:[0-9A-Fa-f]{1,4}(:(:[0-9A-Fa-f]{1,4}){0,2}|:[0-9A-Fa-f]{1,4}(:(:[0-9A-Fa-f]{1,4})?|:[0-9A-Fa-f]{1,4}(:|:[0-9A-Fa-f]{1,4})))))):(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])(\.(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3})

# RegEx for the complete IPv4 address space
user@host:~$ rgxg cidr 0.0.0.0/0
(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])(\.(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3}

Modifying hostcert.sh

All that is left to is define definite start and endpoints for the RegEx (^ and $) and test it.

user@host:~/git/github/chrlau/scripts/ca$ git diff
diff --git a/ca/hostcert.sh b/ca/hostcert.sh
index f743881..26ec0b0 100755
--- a/ca/hostcert.sh
+++ b/ca/hostcert.sh
@@ -42,16 +42,18 @@ else
        CN="$1.lan"
 fi

-# Check if Altname is an IPv4 or IPv6 (yeah.. very basic check..)
-#  so we can set the proper x509v3 extension
+# Check if Altname is an IPv4 or IPv6 - so we can set the proper x509v3 extension
+# Note: Everything which doesn't match the IPv4 or IPv6 RegEx is treated as DNS altname!
 for ALTNAME in $*; do
-  if [[ $ALTNAME =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ || $ALTNAME =~ \.*:\.* ]]; then
+  if [[ $ALTNAME =~ ^(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])(\.(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3}$ || $ALTNAME =~ ^((:(:[0-9A-Fa-f]{1,4}){1,7}|::|[0-9A-Fa-f]{1,4}(:(:[0-9A-Fa-f]{1,4}){1,6}|::|:[0-9A-Fa-f]{1,4}(:(:[0-9A-Fa-f]{1,4}){1,5}|::|:[0-9A-Fa-f]{1,4}(:(:[0-9A-Fa-f]{1,4}){1,4}|::|:[0-9A-Fa-f]{1,4}(:(:[0-9A-Fa-f]{1,4}){1,3}|::|:[0-9A-Fa-f]{1,4}(:(:[0-9A-Fa-f]{1,4}){1,2}|::|:[0-9A-Fa-f]{1,4}(::[0-9A-Fa-f]{1,4}|::|:[0-9A-Fa-f]{1,4}(::|:[0-9A-Fa-f]{1,4}))))))))|(:(:[0-9A-Fa-f]{1,4}){0,5}|[0-9A-Fa-f]{1,4}(:(:[0-9A-Fa-f]{1,4}){0,4}|:[0-9A-Fa-f]{1,4}(:(:[0-9A-Fa-f]{1,4}){0,3}|:[0-9A-Fa-f]{1,4}(:(:[0-9A-Fa-f]{1,4}){0,2}|:[0-9A-Fa-f]{1,4}(:(:[0-9A-Fa-f]{1,4})?|:[0-9A-Fa-f]{1,4}(:|:[0-9A-Fa-f]{1,4})))))):(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])(\.(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3})$ ]]; then
+  #if [[ $ALTNAME =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ || $ALTNAME =~ \.*:\.* ]]; then
     IP_ALTNAMES+=("$ALTNAME")
   else
     DNS_ALTNAMES+=("$ALTNAME")
   fi
 done

+# TODO: Add DNS check against all DNS Altnames (CN is always part of DNS Altnames)
 echo "CN: $CN"
 echo "DNS ANs: ${DNS_ALTNAMES[@]}"
 echo "IP ANs: ${IP_ALTNAMES[@]}"

And it seems to work fine. Notably all altnames who don't match any of the RegExes are treated as DNS-Altname which can cause trouble hence I think about adding a check to resolve all provided DNS names prior the certificate creation.

root@host:~/ca# ./hostcert.sh service.lan 1.2.3.4 fe80::1234 service-test.lan 2abt::0000 999.5.4.2 2a00:1:2:3:4:5::ffff
CN: service.lan
DNS ANs: service.lan service-test.lan 2abt::0000 999.5.4.2
IP ANs: 1.2.3.4 fe80::1234 2a00:1:2:3:4:5::ffff
Enter to confirm.
^C
root@host:~/ca#
Comments