keepalived-go

module
v0.0.0-...-efbc44a Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 2, 2026 License: GPL-2.0

README

keepalived-go

A reimplementation of keepalived 2.4.3 in Go — VRRP, the LVS health checkers, and BFD.

The goal is a drop-in replacement: same configuration file, same diagnostics, same behaviour on the wire and in the kernel.

Status: not production software. Here be dragons

Why do this at all

Two reasons that hold up, and one honest cost.

Memory safety, in code that parses hostile input. The daemon reads bytes it does not control: VRRP advertisements off the wire, BFD packets, HTTP bodies and SMTP banners from health-checked backends. That is the kind of code where a bounds check in the wrong place costs you the machine, and Go removes the whole class for free. Every wire-format parser is also fuzzed for an hour a target, which found a stack overflow and an out-of-memory in this port's own configuration preprocessor.

A state machine you can actually test. internal/vrrp/fsm has no I/O of any kind — events in, a list of actions out, with the clock as a parameter. That makes the state × event cross-product and the 4×4 sync-group transition matrix exhaustively testable. They are not in the C original, where the state handlers call vrrp_send_adv, netlink and thread_add_timer directly.

The cost: Go is worse at hard deadlines, and the fix is not free. keepalived is one thread that waits for its deadline and does the work when it arrives, so a single sched_setscheduler covers the whole path. Go splits those — the advertisement is written by the runner's locked thread, but the wake-up comes from whichever M the runtime's timer happens to be on. At a 10 ms interval under CPU contention, worst inter-advert gap against a ~36 ms budget:

keepalived this port
no real-time priority 27.0 ms 61.5 ms
vrrp_rt_priority 50 10.7 ms 10.8 ms

Roughly twice as bad untuned. The mitigation is to promote every thread in /proc/self/task rather than one, which this does — and then the two are within a millisecond. If you run sub-100 ms intervals, set vrrp_rt_priority; the daemon warns at startup if you have not.

Compatibility

Concretely, and all of it checkable:

Configuration syntax The same grammar, including $NAME= definitions, ~SEQ/~LST repetition, @id host conditionals and include
Accept / reject 66 recorded configurations, same verdict as keepalived 2.4.3
Diagnostics 81 messages, character for character
-t exit status 0 when accepted, 5 when rejected, as C does
Command-line flags -f -P -C -D -n -t -V -X mean what they mean in keepalived
Wire format VRRPv2 over IPv4, VRRPv3 over IPv4 and IPv6, byte-for-byte against 6 captured advertisements

See Migrating from keepalived for what is not the same.

Where it deliberately differs from the RFCs

The port is written against the keepalived C source, not against the RFCs, and where the two disagree it follows keepalived. An implementation that is more correct than its peers is one that cannot form a virtual router with them.

VRRPv2 here drops an advertisement whose interval differs from its own, for instance; RFC 3768 requires no such check, but keepalived does it. Every such case is listed in docs/keepalived-deltas.md with the C file and line it came from.

Migrating from keepalived

Your configuration file should not need to change. What changes is around it.

Logging. keepalived logs to syslog by default; this writes to stderr with a keepalived: prefix. Under systemd that is already what you want — the journal captures stderr — but a unit file that redirects to a log file needs adjusting, and -t --config-test=FILE has no equivalent here: -t writes its diagnostics to stderr and sets the exit status.

# keepalived.service
ExecStart=/usr/local/bin/keepalived -n -f /etc/keepalived/keepalived.conf
StandardOutput=journal
StandardError=journal

-n (don't fork) is the right mode under systemd with Type=simple, as it is for the C daemon.

Three binaries, not one. The supervisor forks keepalived-vrrp and keepalived-check, and looks for them next to itself. Install all three into the same directory. A container image needs all three in the layer.

Flags. Eight are compatible: -f -P -C -D -n -t -V -X. Check any others against --help; -v in particular is spelled -version here.

Capabilities. Same as keepalived: CAP_NET_RAW for the VRRP socket and CAP_NET_ADMIN for addresses, routes and nftables. A container needs both, or --privileged.

Subsystems that are thinner. If you poll SNMP, drive D-Bus, or rely on ha_suspend, read What is missing before switching — those three are implemented but not to C's full surface.

Quickstart

The supervisor forks two child binaries, so build all three into one directory:

go build -o bin/ ./cmd/...

That gives bin/keepalived, bin/keepalived-vrrp and bin/keepalived-check. The children are looked up next to the supervisor, so keep them together.

A minimal configuration:

vrrp_instance VI_1 {
    state BACKUP
    interface eth0
    virtual_router_id 51
    priority 100
    advert_int 1
    virtual_ipaddress {
        192.168.1.100/24
    }
}

Check it, then run it:

bin/keepalived -t -f keepalived.conf     # exit 0 accepted, 5 rejected
sudo bin/keepalived -n -D -f keepalived.conf

-n keeps it in the foreground and -D turns on detailed logging. With no peer advertising a higher priority it takes the address within a few seconds:

keepalived-vrrp: (VI_1) Entering BACKUP STATE (init)
keepalived-vrrp: (VI_1) Entering MASTER STATE

It needs CAP_NET_RAW and CAP_NET_ADMIN — for a VRRP socket and for assigning the address.

No cgo. One dependency, golang.org/x/sys.

Architecture

Three binaries. keepalived is a supervisor that parses the configuration once to learn which children are needed, starts them, and then only supervises — restart a dead child, forward reload and dump signals, shut down within a bounded time. It holds no protocol state, which is what makes a crash in the VRRP child survivable. keepalived-vrrp and keepalived-check are the two children. keepalived splits the same way.

The VRRP state machine has no I/O. internal/vrrp/fsm takes an event and the current time and returns a list of actions; sockets, netlink, timers and logging all live outside it. internal/vrrp/runner is the single select loop that owns a domain, feeds the machine, and performs what comes back.

The unit of state is a domain — every instance in the process plus its sync groups — rather than a single instance, because sync-group propagation mutates sibling instances. One goroutine owns a sync group, or an instance that has no group. Nothing else touches that state, so there are no locks on the failover path.

Health checkers are one goroutine each, running their own probe-and-wait loop at that checker's delay_loop. Results funnel back through a single mutex before touching quorum arithmetic or the IPVS table, so the ordering of "backend went down" against "quorum lost" against "install the sorry server" is serialised rather than raced.

Reload rebuilds rather than mutates. A new configuration builds a whole new object graph, and instances whose configuration did not change carry their state machine and what they hold in the kernel across to it. Sockets, VMAC interfaces and the kernel parameters the daemon changed outlive a generation, because tearing them down and recreating them on reload would be a failover.

cmd/keepalived           supervisor
cmd/keepalived-vrrp      VRRP daemon
cmd/keepalived-check     health-check daemon

internal/config          the parser, byte-compatible with C's diagnostics
internal/vrrp/fsm        the state machine — pure, no I/O
internal/vrrp/runner     the loop that owns a domain and drives it
internal/vrrp/proto      wire format, checksums, HMAC auth extension
internal/vrrp/socket     raw sockets, IP_HDRINCL, multicast, GTSM
internal/vrrp/kernel     addresses, routes, rules, firewall
internal/vrrp/track      interface, script, file and process trackers
internal/vrrp/vmac       virtual MAC interfaces
internal/check           the checkers and their IPVS application
internal/bfd             BFD sessions
internal/firewall        nftables rules
internal/netlinkx        rtnetlink and generic netlink
internal/sysctl          per-interface kernel parameters

Testing

go test -race ./...          # skips what needs a capability, naming which
test/privileged/run.sh       # the rest, in a user namespace — no root needed
test/fuzz/gate.sh            # 30 fuzz targets, one hour each

The recorded corpus is in testdata/golden, so the compatibility tests run on a bare clone. KEEPALIVED_SRC points them at a keepalived checkout for re-recording.

Two tools measure how much of the configuration actually does something:

go run ./tools/parity        # settings that are read outside the parser
go run ./tools/inert         # settings no test notices being broken

What works

VRRP v2 and v3 over IPv4 and IPv6, unicast and multicast, with virtual MAC interfaces, virtual routes and rules, and accept_mode enforced through nftables.

  • Sync groups, so several instances fail over together.
  • Trackingtrack_interface, track_script with rise/fall hysteresis, track_file, vrrp_track_process, track_bfd.
  • Notifications — per-state and generic scripts, notify FIFOs, and SMTP alerts for instances, sync groups and real servers.
  • LVS — ten checker types, IPVS application, quorum and sorry servers, session persistence.
  • BFD sessions per RFC 5880/5881.

Each was compared against the running C daemon, not only against its source: on live interfaces for VRRP and tracking, through a recording SMTP server for the alerts, and against the kernel's IPVS table for persistence.

What is missing

Three subsystems are thinner than keepalived's:

  • SNMP carries the columns a monitoring system polls, not all of C's tables.
  • D-Bus PrintStats answers and returns nothing, because there are no per-instance counters yet.
  • ha_suspend follows the virtual address rather than a VRRP instance's state.

Smaller gaps: the preferred_lft, track_group and use_vmac qualifiers on a virtual address are recognised and reported rather than applied; virtual_routes takes add, prepend and append but not replace; and the nftables rule for a VMAC's multicast membership reports is implemented in its drop-only form.

The daemon reports its own limits at startup, so a configuration relying on one of these says so when you run it.

Known limitations

Sub-100 ms advertisement intervals need vrrp_rt_priority. At a 10 ms interval a backup declares its master down after about 36 ms, and a contended general-purpose scheduler does not meet a 36 ms deadline — in either implementation. Untuned, this port's worst-case gap is roughly twice C's; with a real-time policy the two are within a millisecond. See Why do this at all for the numbers and the reason. The daemon warns at startup if you run short intervals without it.

No IPv6 for VRRPv2, which is not a gap: RFC 3768 has no IPv6.

Untriaged test-coverage gaps. tools/inert reports sites where breaking the code on purpose fails no test — roughly a third of the candidate sites in internal/check. Each is either dead code or code no test covers, and the tool cannot tell which. They have not been worked through.

What "production" would take

The warning at the top is not a formality, and it is not because the project is abandoned. Concretely, before it comes off:

  • External review. Nobody but the author has read this code. For a daemon that parses hostile input and decides which machine owns an address, that is the largest single risk.
  • A real deployment. It has never run outside a test namespace. Nothing here has met a switch that does something unexpected, a driver that reorders packets, or six months of uptime.
  • Sustained load and soak testing. The measurements are minutes long. Slow leaks, fragmentation and clock drift do not show up in minutes.
  • The tools/inert survivors triaged, so "tested" means the same thing everywhere in the tree.
  • The three thin subsystems finished, or documented as permanently out of scope.

There is no schedule. It is a personal project.

Contributing

See CONTRIBUTING.md. The rule that is not obvious: parity with keepalived beats correctness, so a patch fixing a bug this port copies on purpose will usually be declined.

Security reports: SECURITY.md.

License

GPL-2.0-or-later — see LICENSE. Every source file carries an SPDX-License-Identifier.

This is a derivative work of keepalived, Copyright (C) 2001-2017 Alexandre Cassen acassen@gmail.com and contributors, licensed GPL-2.0-or-later. It is a reimplementation written against the keepalived source, with that source cited by file and line throughout, and is covered by the same license.

keepalived is at https://github.com/acassen/keepalived. This project is not affiliated with or endorsed by its maintainers.

Directories

Path Synopsis
cmd
keepalived command
Command keepalived is the supervisor.
Command keepalived is the supervisor.
keepalived-check command
Command keepalived-check is the health-check child.
Command keepalived-check is the health-check child.
keepalived-vrrp command
Command keepalived-vrrp is the VRRP child.
Command keepalived-vrrp is the VRRP child.
internal
agentx
Package agentx is an AgentX subagent (RFC 2741).
Package agentx is an AgentX subagent (RFC 2741).
bfd
Package bfd implements Bidirectional Forwarding Detection (RFC 5880) for single-hop IPv4 and IPv6 sessions (RFC 5881), as keepalived uses it: a tracking input that raises a VRRP fault when a session goes down.
Package bfd implements Bidirectional Forwarding Detection (RFC 5880) for single-hop IPv4 and IPv6 sessions (RFC 5881), as keepalived uses it: a tracking input that raises a VRRP fault when a session goes down.
check/apply
Package apply turns health-check verdicts into IPVS table changes.
Package apply turns health-check verdicts into IPVS table changes.
check/dns
Package dns builds and parses the queries the DNS_CHECK health check uses.
Package dns builds and parses the queries the DNS_CHECK health check uses.
check/file
Package file is the FILE_CHECK health check: a real server's health follows the number in a tracked file.
Package file is the FILE_CHECK health check: a real server's health follows the number in a tracked file.
check/framework
Package framework is the health-check framework: per-checker retry state and per-virtual-server quorum with hysteresis.
Package framework is the health-check framework: per-checker retry state and per-virtual-server quorum with hysteresis.
check/http
Package http parses HTTP responses for the HTTP_GET and SSL_GET health checks.
Package http parses HTTP responses for the HTTP_GET and SSL_GET health checks.
check/layer4
Package layer4 performs the TCP connection attempt every connect-based health check is built on, and classifies the outcome.
Package layer4 performs the TCP connection attempt every connect-based health check is built on, and classifies the outcome.
check/misc
Package misc is the MISC_CHECK health check: run a script and read the verdict from its exit status.
Package misc is the MISC_CHECK health check: run a script and read the verdict from its exit status.
check/ping
Package ping is the PING_CHECK health check: send an ICMP echo request and treat a matching echo reply as healthy.
Package ping is the PING_CHECK health check: send an ICMP echo request and treat a matching echo reply as healthy.
check/reload
Package reload computes what changes when a health-check configuration is re-read, and what must be carried across.
Package reload computes what changes when a health-check configuration is re-read, and what must be carried across.
check/smtp
Package smtp implements the line reading and status parsing for the SMTP_CHECK health check.
Package smtp implements the line reading and status parsing for the SMTP_CHECK health check.
check/ssl
Package ssl provides the TLS layer for the SSL_GET health check.
Package ssl provides the TLS layer for the SSL_GET health check.
check/tcp
Package tcp is the TCP_CHECK health check: connect to the backend, and treat a completed connection as healthy.
Package tcp is the TCP_CHECK health check: connect to the backend, and treat a completed connection as healthy.
check/udp
Package udp is the UDP_CHECK health check: send an optional payload to a UDP port and, optionally, require a reply that matches a pattern.
Package udp is the UDP_CHECK health check: send an optional payload to a UDP port and, optionally, require a reply that matches a pattern.
config
Package config reads keepalived configuration files.
Package config reads keepalived configuration files.
config/iproute
Package iproute parses keepalived's virtual-route and virtual-rule sub-language.
Package iproute parses keepalived's virtual-route and virtual-rule sub-language.
dbus
Package dbus is enough of the D-Bus protocol to export keepalived's VRRP interface (keepalived/vrrp/vrrp_dbus.c).
Package dbus is enough of the D-Bus protocol to export keepalived's VRRP interface (keepalived/vrrp/vrrp_dbus.c).
dumpfile
Package dumpfile writes the files keepalived dumps its state into.
Package dumpfile writes the files keepalived dumps its state into.
firewall
Package firewall builds the nftables ruleset keepalived installs to enforce accept_mode.
Package firewall builds the nftables ruleset keepalived installs to enforce accept_mode.
ipvs
Package ipvs is a generic-netlink client for the IPVS load balancer.
Package ipvs is a generic-netlink client for the IPVS load balancer.
netlinkx
Package netlinkx is a typed rtnetlink client: addresses, routes, rules and links, plus the monitor that reports changes.
Package netlinkx is a typed rtnetlink client: addresses, routes, rules and links, plus the monitor that reports changes.
notify
Package notify runs the scripts and writes the FIFO lines that tell the rest of the system a VRRP instance or sync group changed state.
Package notify runs the scripts and writes the FIFO lines that tell the rest of the system a VRRP instance or sync group changed state.
observ
Package observ exposes the daemon's own runtime for inspection.
Package observ exposes the daemon's own runtime for inspection.
procconn
Package procconn subscribes to the kernel's process connector.
Package procconn subscribes to the kernel's process connector.
proctune
Package proctune applies the process-level settings global_defs asks for: the nice value, memory locking and the real-time CPU limit.
Package proctune applies the process-level settings global_defs asks for: the nice value, memory locking and the real-time CPU limit.
rtsched
Package rtsched puts the calling thread on the real-time scheduler.
Package rtsched puts the calling thread on the real-time scheduler.
snmp
Package snmp exports keepalived's MIBs through an AgentX subagent.
Package snmp exports keepalived's MIBs through an AgentX subagent.
supervisor
Package supervisor runs the child processes and keeps them running.
Package supervisor runs the child processes and keeps them running.
sysctl
Package sysctl reads and writes the per-interface kernel knobs keepalived needs, remembering the previous values so they can be put back.
Package sysctl reads and writes the per-interface kernel knobs keepalived needs, remembering the previous values so they can be put back.
trackfile
Package trackfile reads and monitors the files that `track_file` names, and turns their contents into the status a tracking object sees.
Package trackfile reads and monitors the files that `track_file` names, and turns their contents into the status a tracking object sees.
vrrp/fsm
Package fsm is the VRRP state machine, with no I/O of any kind.
Package fsm is the VRRP state machine, with no I/O of any kind.
vrrp/kernel
Package kernel implements the runner's outward-facing interfaces against the real kernel.
Package kernel implements the runner's outward-facing interfaces against the real kernel.
vrrp/neigh
Package neigh builds and sends the link-layer announcements a new master uses to redirect traffic to itself.
Package neigh builds and sends the link-layer announcements a new master uses to redirect traffic to itself.
vrrp/pktcheck
Package pktcheck is the receive-side policy check for VRRP advertisements (vrrp_check_packet, keepalived/vrrp/vrrp.c:927-1368).
Package pktcheck is the receive-side policy check for VRRP advertisements (vrrp_check_packet, keepalived/vrrp/vrrp.c:927-1368).
vrrp/proto
Package proto encodes and decodes VRRP advertisements.
Package proto encodes and decodes VRRP advertisements.
vrrp/reload
Package reload decides what a VRRP configuration change may do to a running instance.
Package reload decides what a VRRP configuration change may do to a running instance.
vrrp/runner
Package runner drives the VRRP state machine against real time and a real network.
Package runner drives the VRRP state machine against real time and a real network.
vrrp/socket
Package socket carries VRRP advertisements between the state machine and the wire.
Package socket carries VRRP advertisements between the state machine and the wire.
vrrp/track
Package track turns things that go wrong outside VRRP into events the state machine understands.
Package track turns things that go wrong outside VRRP into events the state machine understands.
vrrp/vectors
Package vectors holds VRRP advertisements captured from keepalived 2.4.3 (C) running in a network namespace, recorded with `tcpdump -x` and stripped of their IP headers.
Package vectors holds VRRP advertisements captured from keepalived 2.4.3 (C) running in a network namespace, recorded with `tcpdump -x` and stripped of their IP headers.
vrrp/vmac
Package vmac derives the virtual MAC address a VRRP instance uses, and the link-local address that follows from it.
Package vmac derives the virtual MAC address a VRRP instance uses, and the link-local address that follows from it.
tools
inert command
Command inert finds configuration that parses, is read, and does nothing.
Command inert finds configuration that parses, is read, and does nothing.
parity command
Command parity measures how much of the parsed configuration reaches a daemon.
Command parity measures how much of the parsed configuration reaches a daemon.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL