Public bug reported:

### Impact

Any restart of `ubuntu-fan.service` kills the fan `dnsmasq` and does not 
respawn it, while the
unit reports success. The fan bridge and routes stay up, so nothing appears 
wrong until the
existing DHCP leases expire roughly an hour later — at which point every 
container on the fan
loses its IPv4 address at once and cannot get a new one.

On our hosts this is triggered with no operator action at all, by the glibc 
restart sweep of an
`unattended-upgrades` run. Two of three hosts in a Ceph cluster lost their 
containers on the same
morning, ~20 minutes apart, matching the times each host installed the libc6 
update. The delayed
onset makes the cause very hard to attribute: by the time anything breaks, the 
upgrade is an hour
in the past and the service claims to be healthy.

This appears to be the `ubuntu-fan`-side counterpart of the still-open LP: 
#1903520, where the
same dnsmasq-in-the-wrong-cgroup problem is triggered by restarting 
`jujud-machine`. Here the
cgroup owner is `ubuntu-fan.service` itself and the trigger is an ordinary 
security update, so no
Juju involvement is needed to hit it.

### Environment

- Ubuntu 24.04 (noble), kernel 6.8.0-136-generic
- ubuntu-fan 0.12.16+24.04.1
- Fan config (`/etc/network/fan` line 40, written by fanatic):
  `10.10.104.0/24 252.0.0.0/8`
- `fanctl show`:
  ```
  Bridge           Underlay             Overlay              Flags
  fan-252          10.10.104.60/24      252.0.0.0/8          enable dhcp
  ```
- LXD containers attached to `fan-252`, addressed by the fan dnsmasq.


### Steps to reproduce

1. Configure a fan with `dhcp` enabled and attach at least one running 
container to the bridge.
2. Confirm dnsmasq is serving: `systemd-cgls -u ubuntu-fan.service`
3. `systemctl restart ubuntu-fan` (or let a libc6 upgrade's restart sweep do it)
4. `pgrep -af 'interface=fan-252'` → no dnsmasq
5. Wait for the DHCP lease time to elapse → containers lose their IPv4 
addresses.

### What happens

dnsmasq runs with PPID 1 but is a member of the unit's cgroup:

```
# systemd-cgls -u ubuntu-fan.service
Unit ubuntu-fan.service (/system.slice/ubuntu-fan.service):
└─432679 dnsmasq -u dnsmasq --strict-order --bind-interfaces \
    --pid-file=/run/ubuntu-fan/dnsmasq-fan-252.pid --conf-file= \
    --listen-address 252.62.0.1 --dhcp-range 252.62.0.2,252.62.255.254 ...
```

So the default `KillMode=control-group` kills it on any stop of the unit. The 
subsequent start does
not bring it back. From the libc6-triggered event on one host:

```
Jul 28 06:41:48 systemd[1]: Starting apt-daily-upgrade.service ...
   (unattended-upgrades: libc-bin libc-dev-bin libc-devtools libc6 libc6-dev 
locales)
Jul 28 06:41:55 dnsmasq[1565]: exiting on receipt of SIGTERM
Jul 28 06:41:55 systemd[1]: ubuntu-fan.service: Deactivated successfully.
Jul 28 06:41:55 systemd[1]: Stopped ubuntu-fan.service - Ubuntu FAN network 
setup.
Jul 28 06:41:55 systemd[1]: Starting ubuntu-fan.service - Ubuntu FAN network 
setup...
Jul 28 06:41:55 systemd[1]: Finished ubuntu-fan.service - Ubuntu FAN network 
setup.
```

`ExecStart` exited `0/SUCCESS`. There is not a single DHCP log line after this 
point, and no
dnsmasq process exists. Every file in `/run/ubuntu-fan/` still carries its 
original boot mtime —
the restart touched none of them.

The last lease renewal on this host was 06:31:38; the containers kept working 
until the lease
elapsed and then went dark, with the juju agents going `lost` and ceph-mon 
losing quorum.

### Why the start does not respawn dnsmasq

`dhcp_reconfigure()` — the function that starts dnsmasq — is called at the very 
end of the
bridge-configuration function in `/usr/sbin/fanctl` (~line 1229), after the 
bridge, tunnel and NAT
setup. That function short-circuits when `/run/ubuntu-fan/brg-state-<bridge>` 
already exists
(~line 1132), which it does, because the bridge was never torn down. So the 
dnsmasq code path is
never reached.

Deleting `/run/ubuntu-fan/dnsmasq-flags-fan-252` — the file 
`dhcp_reconfigure()` compares against
to decide whether to respawn — has no effect either, for the same reason: the 
code that reads it
does not execute.

### Recovery is also blocked

1. `fanctl down` refuses while containers are attached:
   `/usr/sbin/fanctl: fan-252: in use, will not destroy` → `err=1`
2. Because that makes the stop fail, the unit lands in `failed`. `systemctl 
restart` on a failed
   unit does not run `ExecStop`, so `fanctl down` never gets another chance, 
and `ExecStart` keeps
   failing:
   `/usr/sbin/fanctl: fan-252: already configured` → `err=1`
   This loops indefinitely — a plain restart can never recover the service.
3. The only working recovery is to stop every container on the bridge, run
   `/usr/lib/ubuntu-fan/fan-net stop` by hand, `systemctl reset-failed`, 
`systemctl start`, then
   start the containers again.

Note the inconsistent exit codes: the libc-triggered `fan-net start` exited 0 
while leaving the
fan without DHCP (silent failure), whereas later manual starts exit 1 with 
`already configured`
(unrecoverable failure). Neither reflects the actual state of the fan. Compare 
LP: #1719644 on
fanctl exit codes.

### Expected behaviour

Either of these would prevent the outage:

- `ubuntu-fan.service` should not let systemd kill dnsmasq out from under 
`fanctl` — e.g. ship
  `KillMode=process` in the unit, or run dnsmasq from its own unit.
- `fanctl up` should verify that a dnsmasq is actually running for a 
`dhcp`-enabled fan and
  respawn it if not, rather than inferring liveness from `/run` state files, 
and should not exit 0
  when the fan has no DHCP server.

Additionally, `fan-net stop` failing on `in use, will not destroy` should 
probably not leave the
unit in a state from which `systemctl restart` can never recover.

### Tested mitigation

A drop-in makes the outage stop happening:

```ini
# /etc/systemd/system/ubuntu-fan.service.d/10-keep-dnsmasq.conf
[Service]
KillMode=process
```

The unit is `Type=oneshot` with `RemainAfterExit=yes` and its main process has 
already exited, so
nothing is killed on stop and dnsmasq survives. Verified across a `systemctl 
restart` with
containers running — same dnsmasq PID before and after, containers kept their 
leases, and systemd
logs the intent explicitly:

```
ubuntu-fan.service: Unit process 933560 (dnsmasq) remains running after unit 
stopped.
ubuntu-fan.service: Found left-over process 933560 (dnsmasq) in control group 
while starting unit. Ignoring.
```

This does not fix the `already configured` failure — a restart while the fan is 
up still leaves the
unit `failed` — but it does prevent the loss of container networking, which is 
the damaging part.

### Related

- LP: #1903520 — same dnsmasq-killed-by-cgroup mechanism, triggered via 
`jujud-machine`; open since 2020.
- LP: #1719644 — fanctl exit codes not reflecting failed up steps.
- LP: #1744296 — `already configured` after underlay ifdown/ifup; fixed in 
0.12.10, different trigger.

** Affects: ubuntu-fan (Ubuntu)
     Importance: Undecided
         Status: New


** Tags: dnsmasq fan noble systemd

-- 
You received this bug notification because you are a member of Ubuntu
Bugs, which is subscribed to Ubuntu.
https://bugs.launchpad.net/bugs/2161974

Title:
  ubuntu-fan.service restart silently leaves the fan bridge without a
  DHCP server; containers lose addresses an hour later

To manage notifications about this bug go to:
https://bugs.launchpad.net/ubuntu/+source/ubuntu-fan/+bug/2161974/+subscriptions


-- 
ubuntu-bugs mailing list
[email protected]
https://lists.ubuntu.com/mailman/listinfo/ubuntu-bugs

Reply via email to