How to measure a roam gap
A roam gap is measured from the wired side and over the air at the same time, timed on one clock, repeated across enough roams to mean something, and judged by the longest gap you find.
A roam gap is measured from the wired side and over the air at once, and only the longest gap counts.
The wired probe tells you what the application saw. The capture tells you why. A probe alone is a number with no cause; a capture alone is a list of frames with no consequence.
The page reports no measurements of its own. Every figure below is cited, or is arithmetic on a stated example.
What counts as the gap
The site’s roaming clauses define the gap as the time from the last data frame on the old access point to the first data frame on the new one. Both ends are wider than they look.
Before the roam, the client scans. A client that roams on falling signal stays associated while it scans and breaks off to pass data, so packets arrive late rather than not at all. An industrial lab study of a client riding an AMR between enterprise access points found the scan could last up to about 1 second1, with latency raised throughout. A packet that misses the timeout is as bad as a lost one. A client that roams because it lost the old access point scans fully disconnected, so the whole scan sits inside the gap (roam scan gaps).
After the reassociation, data still waits. The same study found that once the client starts joining the new access point, no data moves until the key handshake completes. On 802.1X without fast roaming, the whole EAP exchange comes first (full reauthentication on every roam). Traffic from the wired side also has to find the client at its new access point (wired MAC table convergence).
Time all of it. The connection’s timer counts from the last valid packet, whatever made the next one late, and it faults on one gap, not on the average (the timing budget).
Probe from the wired side
Put a host on the wired network, as close to the controller as you can, and have it send to the client at a fixed interval while the client roams. It sees what the controller sees: every hop, wired and wireless, in one number.
Pick the interval from the pass line. A gap is only known to the nearest probe: with a probe every 10 ms, ten missing replies in a row mean a gap somewhere between 90 and 110 ms. The acceptance test plan warns when the probe is too coarse and asks for one at least four times faster than the pass line. Against a hypothetical 80 ms pass line, that is a probe every 20 ms or less.
Ping is the quick version. These commands log one line per probe to 192.0.2.10, a documentation address; use an address on your client.
# Linux (iputils)
ping -D -O -i 0.01 192.0.2.10 > roam-test.log
# macOS
ping -i 0.01 --apple-time 192.0.2.10 > roam-test.log
# Windows
ping /t /w 1000 192.0.2.10 > roam-test.log
On Linux, iputils’ -i takes seconds and defaults to 1 second2, so set it. -D stamps each line with the Unix time; -O reports each probe still unanswered when the next goes out. Apple’s ping takes -i the same way and adds --apple-time, which prints the time each reply arrived. If a system refuses a short interval, run it with administrator rights.
Windows ping cannot do this job. Its documented parameters include no interval and no time of day; /w only sets how long to wait for each reply, 4000 ms3 by default. A Windows log shows that probes went missing, not when or for how long.
Ping is not your traffic, either. One test tool’s documentation warns that some devices prioritize ICMP, so ping may not show what application traffic sees.
The better probe is cyclic UDP at the application’s own interval and marking, with a sequence number in every packet. A gap then becomes a count of missed cycles, the unit the controller’s timeout counts in. RFC 33574 calls a run of consecutive lost packets a loss period and counts it by sequence number. Compare your longest run with the connection’s timeout multiplier or accepted missed cycles.
One open-source tool built this way is IRTT. It sends UDP packets on a fixed period and writes per-packet results to JSON, each with its sequence number and whether it was lost.
# on the client, or on a host wired behind the client radio
irtt server -i 0
# on the wired host near the controller
irtt client -i 10ms -d 2h -o roam-run-01.json 192.0.2.10
The server refuses intervals under 10ms by default5, which -i 0 lifts. Set --dscp to your control traffic’s marking; the flag writes the whole ToS byte, so shift the DSCP value left two bits. Then find the longest run of lost sequence numbers, and read the round-trip times too: a reply that returns after the timeout belongs to the gap.
Capture over the air
You need a capture radio in monitor mode, which passes up every frame on its channel from every network, management frames included. Without it, a capture may show only data frames behind synthesized Ethernet headers.
A radio in monitor mode hears one channel at a time, and Wireshark’s capture notes say channel hopping will lose traffic for that reason. A roam crosses two channels, the old access point’s and the new one’s. So you need two capture radios, each fixed on one of those channels, at every handoff you want to explain.
Find the handoff first from the probe log, then place the radios where they hear both access points and the client. A capture host riding on the vehicle near the client’s antenna comes closest to hearing what the client hears.
Keep the capture host from undoing your work. The same notes warn that another interface retuning the radio or starting a scan makes you silently miss packets, and name the network manager as a common culprit.
A capture holds the frames the capture radio heard. A frame missing from it may be one the radio missed.
Find the roam in the capture
Field names come from Wireshark’s display filter reference. Management frames are type 0 and data frames type 26, and wlan.fc.type_subtype combines them as type times 16 plus subtype7: authentication (management subtype 11) is 0x000b, QoS data (data subtype 8) is 0x0028. The client’s MAC, 00:00:5e:00:53:01, is from the documentation range.
| What you want | Display filter |
|---|---|
| Everything to or from the client | wlan.addr == 00:00:5e:00:53:01 |
| Authentication frames | wlan.fc.type_subtype == 0x000b |
| FT authentication (over the air) | wlan.fixed.auth.alg == 2 |
| SAE authentication | wlan.fixed.auth.alg == 3 |
| Association request and response | wlan.fc.type_subtype == 0x0000 or wlan.fc.type_subtype == 0x0001 |
| Reassociation request and response | wlan.fc.type_subtype == 0x0002 or wlan.fc.type_subtype == 0x0003 |
| A PMKID in an RSN element | wlan.rsn.ie.pmkid |
| FT action frames (over the DS) | wlan.fixed.category_code == 6 |
| EAP exchange | eap |
| Key handshake | eapol |
| Deauthentication and disassociation | wlan.fc.type_subtype == 0x000c or wlan.fc.type_subtype == 0x000a |
Join any of them to the client filter with and.
Deauthentication and disassociation frames carry a reason code, wlan.fixed.reason_code, and the transmitter address, wlan.ta, tells you which side ended the association. Common codes: 1 unspecified, 3 sender leaving, 4 inactivity, 8 sender has left the BSS, 15 four-way handshake timeout, 23 802.1X authentication failed, and 34 too many unacknowledged frames. A run of frames with wlan.fc.retry set just before the gap is a sign the old link was failing.
Time the gap
Filter to the client’s data frames in both directions, across both capture interfaces:
wlan.addr == 00:00:5e:00:53:01 and (wlan.fc.type_subtype == 0x0020 or wlan.fc.type_subtype == 0x0028)
Add wlan.bssid as a column and scroll to where it changes. The last frame on the old BSSID and the first on the new one bound the gap. The filter leaves out Null and QoS Null frames on purpose: they are null function frames with no data.
From the View menu, set the time display to Seconds Since Previous Displayed Packet, one of Wireshark’s time display formats. With the data filter applied, the first frame on the new BSSID shows the gap directly. The same value is the field frame.time_delta_displayed, the time since the previous displayed frame; as a column, it makes the long silences stand out.
The frames between tell you which kind of roam it was.
- Open authentication, reassociation, then EAP and the four-way handshake. Full 802.1X at every roam. One vendor’s roaming note states that without fast secure roaming, EAP and key management must complete before data frames pass. See full reauthentication on every roam.
- A PMKID in the reassociation request, then a four-way handshake with no EAP. The access point accepted a cached key; the same note shows a matching PMKID letting the access point skip EAP.
- FT authentication, or FT action frames through the old access point, then reassociation. Fast transition worked. FT runs over the air in authentication frames or over the DS in action frames, and its four-way handshake happens only at the first association in the mobility domain, so fewer frames come before data.
- FT advertised, but the client joins with open authentication and EAP. See fast roam mismatch.
- A deauthentication or disassociation before the gap. The roam was forced, not chosen. Read the reason code and the sender.
Then split the gap: the scan, from the last data frame to the first authentication frame on the new channel; the join, from there to the reassociation response; the keys, from there to the first data frame. Each part points at a different fix.
Keep the clocks honest
A gap measured between two clocks inherits their disagreement.
Wireshark takes its time stamps from the operating system kernel and warns that USB-connected adapters often give very bad time stamp accuracy, because frames cross the cable before the kernel stamps them.
Synchronization helps, within limits. NTP’s specification, RFC 59058, puts typical clients on fast LANs within a few hundred microseconds. That is a typical figure, not a promise for your laptop: Microsoft supports 1 s, 50 ms, or 1 ms accuracy9 on Windows 10 and later only under stated conditions, and 1 ms needs, among other things, under 0.1 ms of latency and no more than four hops to the time source. PTP, defined in IEEE 158810, targets the sub-microsecond range.
The practical answer is to need none of it for the number. Measure each gap on one clock, and use the others only to find it.
- The wired probe times the whole gap on one host. One clock stamps the last reply before the gap and the first after it. One-way delay needs synchronized clocks, as IRTT’s documentation notes; a run of losses does not.
- Put both capture radios in one capture on one host. Wireshark can capture more than one interface into one pcapng file, stamped by one kernel clock, with the USB caveat above.
- Time the join and the keys on one radio. Everything from the first authentication frame to the first data frame happens on the new channel.
- Merge separate captures with care. mergecap orders frames by each frame’s timestamp, trusting whatever clocks you hand it.
Synchronize every host with NTP anyway, and note which clock stamped each time. Synchronized clocks find the same roam in three logs. One clock measures it.
Read the controller’s side
The controller keeps the record the plant cares about, but it records only what crossed its threshold.
On a Logix controller, an EtherNet/IP I/O connection that stops getting answers faults with 16#0203, connection timed out11, and the controller usually keeps removing and remaking the connection. An attempt to make it that gets no response shows as 16#0204, connection request timed out. The code appears on the Connection tab of the module’s properties.
A CIP Safety connection that gets no valid packet within its connection reaction time limit times out, and its data goes to the safe state. The module’s Safety view shows the maximum observed network delay, resettable online. Reset it before a run and read it after: it is the controller’s own worst case.
On PROFINET, a device that gets no IO data within its watchdog time outputs substitute values, and the controller reports a station failure. An S7-1500 calls OB 8612 when an IO device fails and again when it recovers, and its diagnostics buffer lists events in the order they occurred. TIA Portal names the block Rack or station failure, and Siemens’ own diagnostics example records a time stamp for each fault.
Then line the fault up with the capture. The controller declares a fault one timeout after the last packet it accepted, so subtract the configured timeout from the fault’s time stamp to find where the gap began, to the accuracy of the two clocks. The timing budget shows where each protocol’s timeout comes from. Do not read the return event as the end of the radio gap: after a timeout, the connection has to be remade first.
A clean log means no gap reached the timeout. It does not say how close any came; the probe and the capture do.
How many roams to measure
One clean roam proves nothing. Neither do ten.
A test can answer one question: with what confidence can you rule out a failure rate this high? In the binomial model, the chance that n independent roams all pass when each fails with probability p is (1 - p) to the power n13. Hold that to 1 - C or less and you have the zero-failure test, 1 - CL = R to the power n14 with R = 1 - p, solved for n:
n = ceil( ln(1 - C) / ln(1 - p) )
For one roam in 100 at 95% confidence, ln(0.05) / ln(0.99) is 298.07, which rounds up to 299 roams in a row with none over the pass line. One in 1,000 takes 2,995. The acceptance test plan runs the same arithmetic.
The strongest objection: roams are not independent. A vehicle roaming at the same spot, between the same two access points, repeats the same conditions, and the model assumes trials repeated independently. So 299 roams at one handoff count for less than 299. Spread the sample over every handoff on every route, every client model, both directions, shifts, and plant load. Capture every roam, and if one gap crosses the line, fix the cause and restart the count.
On a hypothetical route with six roams a lap, 299 roams is 50 laps. The worked example walks a whole plan through.
What to record
A gap without its conditions cannot be compared, repeated, or defended. Per roam, record:
- The time, and which clock stamped it.
- The client model, radio, driver, and firmware.
- The SSID and the key management suite in use (
wlan.rsn.akms.typeshows the one offered). - The old BSSID and channel, and the new BSSID and channel.
- The signal before and after, and whose view it is: the client’s log, or the capture radio’s
wlan_radio.signal_dbm. - The gap, how it was measured (ping, UDP probe, or capture), and the probe interval.
- What the frames between showed: full EAP, a PMKID, or FT.
- Any controller fault in the window.
Per test, add the route, the vehicles, the number of roams, the pass line, and the longest gap with where it happened. The spec clauses ask for the same evidence: they accept only roam measurements that log the radio, the firmware, and the key management suite.
Measure every roam, explain the longest, and let that one decide.
About this page
Built from 37 sources: 5 standards bodies and labs, 2 research papers and theses, 28 vendor documents and 2 other sources. Researched and drafted with AI assistance. How pages are made
- First published
- Last updated
Change history (1)
- First published
Cite this page
Plain
Ben Rutter. "How to measure a roam gap." OT Wireless, published October 8, 2026. https://otwireless.com/guides/how-to-measure-a-roam-gap/
APA 7
Rutter, B. (2026, October 8). How to measure a roam gap. OT Wireless. https://otwireless.com/guides/how-to-measure-a-roam-gap/
BibTeX
@misc{rutter2026howtomeasurearoamgap,
author = {Rutter, Ben},
title = {{How to measure a roam gap}},
year = {2026},
howpublished = {\url{https://otwireless.com/guides/how-to-measure-a-roam-gap/}},
organization = {OT Wireless},
}