Skip to content

Add FreeBSD support - #339

Open
ocochard wants to merge 3 commits into
aws:masterfrom
ocochard:freebsd
Open

Add FreeBSD support#339
ocochard wants to merge 3 commits into
aws:masterfrom
ocochard:freebsd

Conversation

@ocochard

Copy link
Copy Markdown

Description of changes:

efs-utils builds and runs on FreeBSD/amd64 and FreeBSD/aarch64. A new mount_efs(8) helper mounts EFS filesystems through the same efs-proxy used on Linux, and the mount-watchdog daemon supervises the proxy just as it does on other platforms.

FreeBSD differs from Linux in a few places that required targeted adjustments:

  • src/efs_utils_common/mount_options.py: FreeBSD's mount_nfs(8) does not recognize nfsvers=4.1; on FreeBSD, pass nfsv4,minorversion=1 instead so mount_nfs negotiates NFSv4.1. Also add oneopenown and retrycnt=1 to match the AWS-recommended FreeBSD EFS mount options.

  • src/efs_utils_common/mount_utils.py: dispatch to /sbin/mount_nfs on FreeBSD

  • src/efs_utils_common/proxy.py: detect FreeBSD init system as "rc" and start the watchdog with service(8) onestart. FreeBSD is also added to the SO_BINDTODEVICE-skip list for stunnel config generation.

  • src/watchdog/init.py: /proc/mounts does not exist on FreeBSD, and neither mount(8) nor nfsstat(8) expose the NFS client's TCP port. Enumerate the watchdog's own state files in STATE_FILE_DIR and cross-check against mount -t nfs to track live mounts. Keying off state files (rather than live proxy sockets) preserves the watchdog's ability to restart a dead efs-proxy.

  • dist/amazon-efs-mount-watchdog.rc: new FreeBSD rc(8) script, companion to the existing systemd unit and launchd plist.

By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.

@mike-adams1

Copy link
Copy Markdown
Contributor

Hi @ocochard thanks for the PR.

We're taking a look at this internally and will let you know when we have updates.

efs-utils builds and runs on FreeBSD/amd64 and FreeBSD/aarch64.
A new mount_efs(8) helper mounts EFS filesystems through the same efs-proxy
used on Linux, and the mount-watchdog daemon supervises the proxy just as it
does on other platforms.

FreeBSD differs from Linux in a few places that required targeted adjustments:

* src/efs_utils_common/mount_options.py: FreeBSD's mount_nfs(8) does not
  recognize `nfsvers=4.1`; on FreeBSD, pass `nfsv4,minorversion=1` instead so
  mount_nfs negotiates NFSv4.1. Also add `oneopenown` and `retrycnt=1` to
  match the AWS-recommended FreeBSD EFS mount options.

* src/efs_utils_common/mount_utils.py: dispatch to /sbin/mount_nfs on FreeBSD

* src/efs_utils_common/proxy.py: detect FreeBSD init system as "rc" and start
  the watchdog with `service(8) onestart`. FreeBSD is also added to the
  SO_BINDTODEVICE-skip list for stunnel config generation.

* src/watchdog/__init__.py: /proc/mounts does not exist on FreeBSD, and neither
  mount(8) nor nfsstat(8) expose the NFS client's TCP port. Enumerate the
  watchdog's own state files in STATE_FILE_DIR and cross-check against
  `mount -t nfs` to track live mounts. Keying off state files (rather than live
  proxy sockets) preserves the watchdog's ability to restart a dead efs-proxy.

* dist/amazon-efs-mount-watchdog.rc: new FreeBSD rc(8) script, companion to the
  existing systemd unit and launchd plist.
@ocochard
ocochard force-pushed the freebsd branch 2 times, most recently from 3378a91 to 82e1f29 Compare July 30, 2026 09:38
A cold-start retry of mount.efs can leave a stale efs-proxy and state file
alongside the live one. On FreeBSD, mount enumeration keys off state files, so a
single mountpoint with two state file/port pairs was reported as two mounts, and
the watchdog maintained two independent restart cycles for one physical mount.

Disambiguate using sockstat(1): the kernel NFS client's loopback connection to
the live proxy shows up as an ESTABLISHED tcp4 socket on 127.0.0.1:<port>,
whereas an orphaned proxy only has its LISTEN socket. Prefer the established
port per mountpoint and leave duplicates out, so the existing stale-mount
cleanup path reaps the orphaned proxy and its state file. If sockstat is
unavailable or ambiguous, keep the first port found rather than dropping a
possibly-live mount.

Pin the get_current_local_nfs_mounts tests to a non-FreeBSD platform so they
exercise the Linux /proc/mounts path identically on any host.
The stunnel health check (check_stunnel_health) runs `df <mountpoint>` every
interval and treats a timeout as "tunnel unhealthy", then SIGKILLs efs-proxy and
restarts it. That design dates back to stunnel 4.56, which could hang silently
while its process stayed alive (aws-efs-csi-driver#616): a process-liveness
check could not see a dead-but-running tunnel, so an end-to-end `df` probe was
used as a black box - a root GETATTR that must traverse
loopback -> efs-proxy -> TLS -> EFS backend and come back.

On FreeBSD that design is actively harmful, for two compounding reasons:

1. `df` does not measure the tunnel. Its latency is dominated by the EFS
   backend, not the local proxy. EFS throttling, burst-credit exhaustion,
   packet loss or a transient network stall routinely push a single GETATTR
   past the timeout while efs-proxy is perfectly healthy. The check has one bit
   of signal - wall-clock elapsed - and cannot distinguish "proxy dead, killing
   is correct" from "backend slow, killing is destructive".

2. FreeBSD's NFSv4.1 client does not reliably survive its transport being torn
   down mid-RPC. When the check SIGKILLs efs-proxy while a GETATTR is still in
   flight, the session can be left unrecoverable (nfsbadse); the next `df` then
   wedges in uninterruptible D-state, which the check reads as another failure
   and kills the proxy again. The check ends up manufacturing the failure it
   reacts to, spinning in an endless SIGKILL/restart loop that only `umount -f`
   or a reboot can clear.

efs-proxy also no longer needs an external end-to-end probe the way stunnel 4.56
did: it self-monitors and, on an unrecoverable internal fault, exits the process
so the watchdog restarts it. A broken proxy therefore surfaces as a dead
process, not a silent hang.

Replace the FreeBSD path with a direct proxy liveness probe. A live efs-proxy
holds a persistent ESTABLISHED tcp4 loopback connection to the kernel NFS client
(NFS-over-TCP keeps the socket up even when the mount is idle), so healthiness is
read from sockstat(1) rather than from an NFS round trip. The proxy is restarted
only when BOTH signals agree it is gone: no ESTABLISHED socket on its port AND
the process no longer running. A slow backend keeps the socket ESTABLISHED, so
it never triggers a kill; a live-but-not-serving proxy (e.g. mid-reconnect) is
left alone rather than SIGKILLed mid-recovery. If sockstat cannot be run there
is no evidence of failure, so nothing is done. No `df` is spawned on FreeBSD, so
the D-state leak is structurally impossible there.

The Linux/macOS `df` path is unchanged.

Tests: pin the existing df-path tests to a non-FreeBSD platform, and add
FreeBSD-branch coverage (established -> no restart; sockstat unavailable -> no
restart; not established but process alive -> no restart; not established and
process dead -> restart).
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

Successfully merging this pull request may close these issues.

2 participants