Skip to main content
RunBook Academy

OPNsenseXXVII · CARPCARP fundamentals

CARP advertisements and skew — the maths of who is master

Intermediate⏱ ~14 mintcpdumpifconfigsysctl

What you'll learn

  • Read a captured CARP advertisement and identify its fields
  • Explain the difference between the static base skew and the runtime demotion counter
  • Configure preemption and explain when it helps and when it hurts
  • Calculate the effective advertisement interval from advbase and advskew
  • Predict the master election outcome from two nodes' advbase and advskew

Prerequisites

Verified against OPNsense 25.x · FreeBSD 14.x · PF (FreeBSD packet filter) FreeBSD 14.x · Unbound 1.20+ · Kea DHCP OPNsense 25.x plugin · WireGuard in-kernel + OPNsense plugin · strongSwan (IPsec plugin) OPNsense 25.x plugin · OpenVPN 2.6.x · Suricata 7.x · 2026-08-14

Not yet marked complete on this device.

The first lesson covered what CARP is. This lesson covers how the protocol actually decides who is the master when two nodes are alive, both advertising, and both willing to take over. The election is governed by three numbers: the base advertisement interval, the base advertisement skew, and the runtime demotion counter — plus a setting called preemption that decides whether a lower-skew node can take over from a higher-skew one.

The protocol is small enough to read on the wire. A tcpdump capture shows the full advertisement, and an operator who can read that capture has a much faster time diagnosing a flapping election than an operator who only knows the GUI.

The CARP advertisement packet

A CARP advertisement is a single packet sent to multicast group 224.0.0.18 (IPv4) or ff02::12 (IPv6), encapsulated in a frame addressed to the CARP multicast MAC 01:00:5e:00:00:12. The packet is small — fixed-length, no options — and contains:

byte:   0        1        2        3        4        5        6        7
      +--------+--------+--------+--------+--------+--------+-----------------+
      |Ver/Type|  VHID  | AdvSkew| AuthLen|  pad   | AdvBase|    Checksum     |
      +--------+--------+--------+--------+--------+--------+-----------------+
      |                        Counter (8 bytes)                              |
      +-----------------------------------------------------------------------+
      |                    HMAC-SHA1 digest (20 bytes)                        |
      +-----------------------------------------------------------------------+

      36 bytes in total, carried directly over IP as protocol 112.

The fields an operator reads off the wire:

  • Version/Type — one byte carrying a four-bit version (2) and a four-bit type. CARP defines a single type, 0x01, the advertisement.
  • VHID — the Virtual Host ID, 1 to 255.
  • AdvSkew — the skew actually being advertised: the configured advskew plus the node’s demotion counter, clamped at 240.
  • AuthLen — the size of the counter plus the digest, in 32-bit words. It is 7.
  • AdvBase — the base advertisement interval, in seconds.
  • Counter — a 64-bit monotonic counter, incremented every advertisement. A counter that jumps backwards indicates the sender has restarted.
  • HMAC-SHA1 digest — 20 bytes computed over the CARP version, type and VHID together with the node’s addresses for that VHID, keyed with the CARP password. This is the authentication — the receiver computes the same digest and discards the packet if it does not match.

The receiving node reads the VHID, the skew, and the counter, and validates the HMAC. If the HMAC is valid and the VHID is one this node is configured for, the receiver updates its view of who is the master for that VHID.

Read-only / Safetcpdump CARP
$ tcpdump -n -i igb1 proto carp -c 3
12:34:56.789012 IP 198.51.100.10 > 224.0.0.18: CARPv2-advertise 36: vhid=1 advbase=1 advskew=0 authlen=7 counter=4218765
12:34:57.790145 IP 198.51.100.10 > 224.0.0.18: CARPv2-advertise 36: vhid=1 advbase=1 advskew=0 authlen=7 counter=4218766
12:34:58.791233 IP 198.51.100.10 > 224.0.0.18: CARPv2-advertise 36: vhid=1 advbase=1 advskew=0 authlen=7 counter=4218767

Illustrative output

The static base skew and the runtime demotion counter

The skew a node advertises is built from two numbers. The static one (advskew) is configured by the operator and persists across reboots. The runtime one is the demotion counter, net.inet.carp.demotion, which the kernel raises when the node is in trouble and which the operator can adjust by hand.

The static skew is the operator’s preference. The primary gets advskew=0 (or advskew=10 for a small preference); the secondary gets advskew=100 or higher. The demotion counter is raised by the kernel when a CARP interface goes down or an advertisement fails to send, and by the operator for a planned failover.

The arithmetic in the kernel is:

advertised_skew = min(advskew + demotion, 240)
interval        = advbase + (advertised_skew / 256) seconds

The demotion counter is added to the configured advskew, not multiplied by anything, and the sum is clamped at 240 — the protocol maximum, CARP_MAXSKEW. The counter is node-wide: it applies to every VHID on the node at once. Its default is 0.

A demotion of 240 therefore takes a primary configured with advskew=0 straight to the maximum advertised skew, which is enough to lose to a backup running advskew=100. A demotion of 1 raises the advertised skew from 0 to 1 — a change of about four milliseconds in the advertisement interval, which is not enough to shift the election against a backup at 100.

The election is run on the advertised skew, not the configured one. That is what lets a manual demotion — “I want the primary to give up master” — work without touching the stored configuration. The operator raises the demotion counter; the advertised skew climbs; the primary’s advertisement interval becomes the longer of the two; the backup becomes master.

Preemption

Preemption is the net.inet.carp.preempt sysctl. With preemption enabled, a node that joins the segment with a lower effective skew than the current master will take over, even if the current master is healthy and advertising. With preemption disabled, the new node joins as backup and stays as backup until the master stops.

Preemption is helpful for clean recovery: the primary boots, finds the secondary in master (because the primary was failing), and takes over automatically. The operator does not have to fail back manually.

Preemption is hurtful in some scenarios: if the operator demotes the primary for a planned maintenance, and the primary is the higher-skew of the two, preemption is what makes the change-of-mind stick. But preemption is also what causes a flapping node (one that is continuously failing and recovering) to keep grabbing the master role from the healthy peer. The flapping node’s advertised skew keeps oscillating between healthy and demoted, and the election keeps re-resolving.

The discipline: enable preemption for deterministic recovery, but make sure the demotion mechanism is stable. A node that demotes on a transient daemon hiccup and then undemotes when the daemon recovers is the source of flap.

Calculating the election

The maths of the election is the same maths that decides every failover. The node with the lower effective skew wins. The defaults — primary advskew=0, secondary advskew=100, both advbase=1 — give:

Nodeadvbaseadvskewdemotionadvertised skewintervalstate
Primary10001.000 smaster
Secondary110001001.391 sbackup

The primary is master by virtue of the shorter interval. The secondary is backup. The backup does not answer ARP for the VIP; the primary does. If the primary dies, the secondary waits for three missed advertisements (about 3 seconds), then takes over.

The election becomes interesting when the primary is demoted — for a planned failover, or because a monitoring script raised the demotion counter:

Nodeadvbaseadvskewdemotionadvertised skewintervalstate
Primary (demoted)102402401.938 sbackup
Secondary110001001.391 smaster

The demotion of 240 is added to the primary’s advskew of 0 and lands on the 240 ceiling. The primary now advertises every 1.938 s against the secondary’s 1.391 s — the secondary has the shorter interval and wins. The primary becomes backup; the secondary becomes master. With preemption enabled, the kernel flips the roles within seconds.

Read-only / Safesysctl demotion
$ sysctl net.inet.carp.demotion
net.inet.carp.demotion: 0

Illustrative output

Summary

  • The CARP advertisement is a small fixed-format packet on multicast 224.0.0.18 with the VHID, the base skew, the base interval, and an HMAC of the IP header using the CARP password.
  • The election compares advertisement intervals: advbase + (min(advskew + demotion, 240) / 256) seconds. The node with the shorter interval wins.
  • The static advskew is the operator’s preference; net.inet.carp.demotion is a runtime knob that is added to it and clamped at 240. Demote, do not reconfigure, for planned failover.
  • Preemption lets a shorter-interval node take over from a healthy longer-interval node. Preemption plus an unstable demotion produces flap.
  • The election maths: advbase=1, advskew=0 vs advskew=100 gives intervals of 1.000 s vs 1.391 s — the primary wins.

Knowledge check · 4 questions

  1. Q1. A primary has advbase=1, advskew=0, and a demotion of 0. A secondary has advbase=1, advskew=100, and a demotion of 0. The operator writes `sysctl net.inet.carp.demotion=240` on the primary. What is the primary's new advertised skew, and which node is master?

  2. Q2. A CARP node with a statically configured advskew of 100 can never be the master if the other node has advskew of 50 and a demotion of 0.

  3. Q3. Which of the following are correct ways to fail over from the primary to the secondary in a CARP deployment? Select all that apply.

  4. Q4. A captured CARP advertisement shows vhid=2, advbase=1, advskew=120, counter=8192. The local node is configured as vhid=2 with advskew=10 and a demotion of 0. What is the local node's advertisement interval, and is it master or backup?

Passing score: 75%. Answers are checked in this browser.