OFDM Wi-Fi Scanner Using SDR Preamble Detection
This example shows how to retrieve information about Wi-Fi® networks using a software-defined radio (SDR) and preamble detection. The example scans over the 5 GHz channels and uses an SDR preamble detector to detect and capture orthogonal frequency-division multiplexing (OFDM) packets from the air. The example then decodes the OFDM packets to determine which packets are access point (AP) beacons. The AP beacon information includes the service set identifier (SSID), media access control (MAC) address (also known as the basic SSID, or BSSID), AP channel bandwidth, and 802.11 standard used by the AP.
Introduction
This example scans through a set of Wi-Fi channels to detect AP beacons that are transmitted on 20 MHz subchannels. The scanning procedure uses a preamble detector on an NI™ USRP™ radio.
The scanning procedure comprises of these steps.
Configure the
preambleDetector
object with a preamble that is generated from the legacy long training field (L-LTF).Set the frequency band and channels for the preamble detector to scan.
Scan each specified channel and with each successful detection of an OFDM packet, capture a waveform for a set duration.
Process the waveform in MATLAB® by searching for beacon frames in the captured waveform and extracting relevant information from each successfully decoded beacon frame.
Display key information about the detected APs.
Set Up Radio
Call the radioConfigurations
function. The function returns all available radio setup configurations that you saved using the Radio Setup wizard.
savedRadioConfigurations = radioConfigurations;
To update the dropdown menu with your saved radio setup configuration names, click Update. Then select the radio to use with this example.
savedRadioConfigurationNames = [string({savedRadioConfigurations.Name})]; radio = savedRadioConfigurationNames(1) ;
Configure Preamble Detector
Create a preamble detector object with the specified radio. Because the object requires exclusive access to radio hardware resources, before running this example for the first time, clear any other object associated with the specified radio. In subsequent runs, to speed up the execution time of the example, reuse your new workspace object.
if ~exist("pd","var") pd = preambleDetector(radio); end
To update the dropdown menu with the antennas available for capture on your radio, call the hCaptureAntennas
helper function. Then select the antenna to use with this example.
captureAntennaSelection = hCaptureAntennas(radio);
pd.Antennas = captureAntennaSelection(1);
To increase the capture sample rate to 40 MHz, specify an oversampling factor of 2
.
osf = 2; pd.SampleRate = 20e6*osf; pd.CaptureDataType = "double"; pd.ThresholdMethod = "adaptive";
Configure Preamble For Radio
The 802.11 standard requires that all Wi-Fi APs must transmit OFDM beacons using non-high throughput (non-HT) packets over a 20 MHz bandwidth. Therefore, generate a 20 MHz L-LTF waveform and use one long training symbol from the generated waveform as the preamble to detect WLAN OFDM packets.
cbw = "CBW20";
cfg = wlanNonHTConfig(ChannelBandwidth=cbw);
lltf = wlanLLTF(cfg,OversamplingFactor=osf);
Extract the first long training symbol from the L-LTF waveform.
cyclicPrefixLength = 1.6e-6*pd.SampleRate; trainingSymbolLength = 3.2e-6*pd.SampleRate; preamble = lltf(cyclicPrefixLength+1:cyclicPrefixLength+trainingSymbolLength);
Because the preamble detector requires the preamble to be between –1 and 1, normalize and set the preamble.
preamble = preamble/sqrt(sum(abs(preamble).^2)); pd.Preamble = preamble;
To capture the entire first non-HT packet, you must set the trigger offset to a negative value. Since you created a matched filter based on the long training symbol in the L-LTF waveform, the offset is at least one legacy short training field (L-STF), one L-LTF cyclic prefix, and one long training symbol.
lstfLength = 8e-6*pd.SampleRate; pd.TriggerOffset = -(lstfLength + cyclicPrefixLength + trainingSymbolLength + 5);
Tune Preamble Detector
Configure the adaptive threshold gain, the adaptive threshold offset, and the radio gain values of the preamble detector for the local environment. Configuring these values requires manual tuning by exploring the trigger points provided by the plotThreshold
function. For more information on tuning these values, see Triggered Capture Using Preamble Detection.
For tuning the preamble detector, specify a channel in the 5 GHz band with a known OFDM packet.
In the 5 GHz band, the valid channel numbers are 1–200. However, the valid 20 MHz control channels for APs that use the 5 GHz band are 32, 36, 40, 44, 48, 52, 56, 60, 64, 100, 104, 108, 112, 116, 120, 124, 128, 132, 136, 140, 144, 149, 153, 157, 161, 165, 169, 173, 177.
band = 5;
channel = 100;
pd.CenterFrequency = wlanChannelFrequency(channel,band);
Adjust these values for tuning the preamble detector.
pd.AdaptiveThresholdGain = 0.3; pd.AdaptiveThresholdOffset = 0.00003; pd.RadioGain = 30;
Plot the filter output power, adaptive threshold, and trigger points of the reconfigured preamble detector. The generated figure contains two trigger points for each OFDM packet. Each trigger point corresponds to a long training symbol.
When you generate a plotThreshold
figure, if you do not have at least two trigger points for each OFDM packet, readjust the adaptive threshold gain, the adaptive threshold offset, and the radio gain until there are at least two trigger points per OFDM packet.
captureDuration = milliseconds(120); plotThreshold(pd,captureDuration);
Inspect the trigger points by zooming in along the x-axis of the plot. For example, this figure shows a zoomed-in view of an OFDM packet with the trigger points on the correlation peaks.
Scan Wi-Fi Channels
Specify Scanning Region
Specify the channels in the 5 GHz band for the SDR to scan.
band = 5;
channels = [32 36 40 44 48 52 56 60 64 100 104 108 112 116 120 124 128 132 136 140 144 149 153 157 161 165 169 173 177];
Generate the center frequencies associated with the selected channels and band values.
centerFrequencies = wlanChannelFrequency(channels,band);
Receiver Design
This diagram shows an overview of the receiver for scanning the selected channels and frequency band.
These steps provide further information on the diagram.
Set the center frequency of the preamble detector, then initialize the detection and capture of a waveform for a set duration.
Check if the preamble detector detects an OFDM packet.
Determine and apply frequency and timing corrections on the waveform, then attempt to recover the legacy signal (L-SIG) field bits.
Check that the packet format is non-HT.
From the recovered L-SIG, extract the modulation and coding scheme (MCS) and the length of the PLCP service data unit (PSDU). Then recover the non-HT data and subsequently decode the MAC protocol data unit (MPDU).
Using the recovered MAC frame configuration, check if the non-HT packet is a beacon.
Recover the SSID, BSSID, vendor of the AP, SNR, primary 20 MHz channel, current channel center frequency index, supported channel width, frequency band, and wireless standard used by the AP.
Check if the waveform contains another packet that you can decode.
Initialize Variables
When you call the capture
function to detect and capture a signal, you must specify the length of the capture and the signal detection timeout. Since beacons transmit every 100 time units (TU), set the capture length and the signal detection timeout to 102.4 ms.
captureLength = milliseconds(102.4); timeout = milliseconds(102.4);
Create a structure (APs
) for storing this information for each successfully decoded beacon.
SSID
BSSID
Vendor of AP
Signal-to-noise ratio (SNR)
Primary 20 MHz channel
Current channel center frequency
Channel width
Frequency band
Operating mode supported by the AP
MAC frame configuration
Waveform in which the beacon exists
Index value at which the non-HT beacon packet begins in the captured waveform
APs = struct(... "SSID",[],"BSSID",[],"Vendor",[],"SNR_dB",[],"Beacon_Channel",[], ... "Operating_Channel",[],"Channel_Width_MHz",[],"Band",[],"Mode",[], ... "MAC_Config",wlanMACFrameConfig,"Waveform",[],"Offset",[]);
To determine the hardware manufacturer of the AP, select the retrieveVendorInfo
box. Selecting the retrieveVendorInfo
box downloads the organizationally unique identifier (OUI) CSV file from the IEEE® Registration Authority website for vendor AP identification.
retrieveVendorInfo = true; counter = 1; ind = wlanFieldIndices(cfg); % Begin scanning and decoding for specified channels. for i = 1:length(centerFrequencies) pd.CenterFrequency = centerFrequencies(i); fprintf("Scanning channel %d on band %.1f.\n",channels(i),band); [capturedData, ~, ~, status] = capture(pd, captureLength, timeout); if ~status % If no non-HT packet is decoded, go to next channel. fprintf("No non-HT packet detected on channel %d in band %.1f.\n",channels(i),band); continue; else fprintf("<strong>Non-HT packet detected on channel %d in band %.1f.</strong>\n",channels(i),band) end % Resample the captured data to 20 MHz for beacon processing. capturedData = resample(capturedData,1,osf); searchOffset = 0; while searchOffset<length(capturedData) % recoverPreamble detects a packet and performs analysis of the non-HT preamble. [preambleStatus,res] = recoverPreamble(capturedData,cbw,searchOffset); if matches(preambleStatus,"No packet detected") break; end % Retrieve synchronized data and scale it with LSTF power as done % in the recoverPreamble function. syncData = capturedData(res.PacketOffset+1:end)./sqrt(res.LSTFPower); syncData = frequencyOffset(syncData,pd.SampleRate/osf,-res.CFOEstimate); % Need only 4 OFDM symbols (LSIG + 3 more symbols) following LLTF % for format detection fmtDetect = syncData(ind.LSIG(1):(ind.LSIG(2)+4e-6*pd.SampleRate/osf*3)); [LSIGBits, failcheck] = wlanLSIGRecover(fmtDetect(1:4e-6*pd.SampleRate/osf*1), ... res.ChanEstNonHT,res.NoiseEstNonHT,cbw); if ~failcheck format = wlanFormatDetect(fmtDetect,res.ChanEstNonHT,res.NoiseEstNonHT,cbw); if matches(format,"Non-HT") % Extract MCS from first 3 bits of L-SIG. rate = double(bit2int(LSIGBits(1:3),3)); if rate <= 1 cfg.MCS = rate + 6; else cfg.MCS = mod(rate,6); end % Determine PSDU length from L-SIG. cfg.PSDULength = double(bit2int(LSIGBits(6:17),12,0)); ind.NonHTData = wlanFieldIndices(cfg,"NonHT-Data"); if double(ind.NonHTData(2)-ind.NonHTData(1))> ... length(syncData(ind.NonHTData(1):end)) % Exit while loop as full packet not captured. break; end nonHTData = syncData(ind.NonHTData(1):ind.NonHTData(2)); bitsData = wlanNonHTDataRecover(nonHTData,res.ChanEstNonHT, ... res.NoiseEstNonHT,cfg); [cfgMAC, ~, decodeStatus] = wlanMPDUDecode(bitsData,cfg, ... SuppressWarnings=true); % Extract information about channel from the beacon. if ~decodeStatus && matches(cfgMAC.FrameType,"Beacon") fprintf("Beacon detected on channel %d in band %.1f.\n",channels(i),band); % Populate the table with information about the beacon. if isempty(cfgMAC.ManagementConfig.SSID) APs(counter).SSID = "Hidden"; else APs(counter).SSID = string(cfgMAC.ManagementConfig.SSID); end APs(counter).BSSID = string(cfgMAC.Address3); if retrieveVendorInfo APs(counter).Vendor = determineVendor(cfgMAC.Address3); else APs(counter).Vendor = "Skipped"; %#ok<UNRCH> end [APs(counter).Mode, APs(counter).Channel_Width_MHz, operatingChannel] = ... determineMode(cfgMAC.ManagementConfig.InformationElements); if isempty(operatingChannel) % Default to scanning channel if operating channel % cannot be determined. operatingChannel = channels(i); end APs(counter).Beacon_Channel = channels(i); APs(counter).Operating_Channel = operatingChannel; APs(counter).SNR_dB = res.LLTFSNR; APs(counter).MAC_Config = cfgMAC; APs(counter).Offset = res.PacketOffset; APs(counter).Waveform = capturedData; counter = counter + 1; end % Shift packet search offset for next iteration of while loop. searchOffset = res.PacketOffset + double(ind.NonHTData(2)); else % Packet is NOT non-HT; shift packet search offset by 10 OFDM symbols (minimum % packet length of non-HT) for next iteration of while loop. searchOffset = res.PacketOffset + 4e-6*pd.SampleRate/osf*10; end else % L-SIG recovery failed; shift packet search offset by 10 OFDM symbols (minimum % packet length of non-HT) for next iteration of while loop. searchOffset = res.PacketOffset + 4e-6*pd.SampleRate/osf*10; end end end
Scanning channel 32 on band 5.0.
No non-HT packet detected on channel 32 in band 5.0.
Scanning channel 36 on band 5.0.
No non-HT packet detected on channel 36 in band 5.0.
Scanning channel 40 on band 5.0.
No non-HT packet detected on channel 40 in band 5.0.
Scanning channel 44 on band 5.0.
No non-HT packet detected on channel 44 in band 5.0.
Scanning channel 48 on band 5.0.
No non-HT packet detected on channel 48 in band 5.0.
Scanning channel 52 on band 5.0.
No non-HT packet detected on channel 52 in band 5.0.
Scanning channel 56 on band 5.0.
Non-HT packet detected on channel 56 in band 5.0.
Scanning channel 60 on band 5.0.
No non-HT packet detected on channel 60 in band 5.0.
Scanning channel 64 on band 5.0.
No non-HT packet detected on channel 64 in band 5.0.
Scanning channel 100 on band 5.0.
Non-HT packet detected on channel 100 in band 5.0.
Beacon detected on channel 100 in band 5.0. Beacon detected on channel 100 in band 5.0. Beacon detected on channel 100 in band 5.0. Beacon detected on channel 100 in band 5.0. Beacon detected on channel 100 in band 5.0.
Scanning channel 104 on band 5.0.
No non-HT packet detected on channel 104 in band 5.0.
Scanning channel 108 on band 5.0.
No non-HT packet detected on channel 108 in band 5.0.
Scanning channel 112 on band 5.0.
No non-HT packet detected on channel 112 in band 5.0.
Scanning channel 116 on band 5.0.
No non-HT packet detected on channel 116 in band 5.0.
Scanning channel 120 on band 5.0.
No non-HT packet detected on channel 120 in band 5.0.
Scanning channel 124 on band 5.0.
Non-HT packet detected on channel 124 in band 5.0.
Beacon detected on channel 124 in band 5.0. Beacon detected on channel 124 in band 5.0. Beacon detected on channel 124 in band 5.0. Beacon detected on channel 124 in band 5.0. Beacon detected on channel 124 in band 5.0.
Scanning channel 128 on band 5.0.
No non-HT packet detected on channel 128 in band 5.0.
Scanning channel 132 on band 5.0.
No non-HT packet detected on channel 132 in band 5.0.
Scanning channel 136 on band 5.0.
No non-HT packet detected on channel 136 in band 5.0.
Scanning channel 140 on band 5.0.
No non-HT packet detected on channel 140 in band 5.0.
Scanning channel 144 on band 5.0.
No non-HT packet detected on channel 144 in band 5.0.
Scanning channel 149 on band 5.0.
No non-HT packet detected on channel 149 in band 5.0.
Scanning channel 153 on band 5.0.
No non-HT packet detected on channel 153 in band 5.0.
Scanning channel 157 on band 5.0.
No non-HT packet detected on channel 157 in band 5.0.
Scanning channel 161 on band 5.0.
No non-HT packet detected on channel 161 in band 5.0.
Scanning channel 165 on band 5.0.
No non-HT packet detected on channel 165 in band 5.0.
Scanning channel 169 on band 5.0.
No non-HT packet detected on channel 169 in band 5.0.
Scanning channel 173 on band 5.0.
No non-HT packet detected on channel 173 in band 5.0.
Scanning channel 177 on band 5.0.
No non-HT packet detected on channel 177 in band 5.0.
Convert the APs
structure to a table and display the information specified in step 7 by using the local function generateBeaconTable
.
detectedBeaconsInfo = generateBeaconTable(APs,band)
detectedBeaconsInfo=10×9 table
SSID BSSID Vendor SNR (dB) Primary 20 MHz Channel Current Channel Center Frequency Index Channel Width (MHz) Band Mode
____________ ______________ ____________________________ ________ ______________________ ______________________________________ ___________________ ____ __________
"wlan1234_5" "04D4C451C584" "ASUSTek COMPUTER INC." 15.298 100 106 "80" 5 "802.11ax"
"wlan1234_5" "04D4C451C584" "ASUSTek COMPUTER INC." 15.235 100 106 "80" 5 "802.11ax"
"wlan1234_5" "04D4C451C584" "ASUSTek COMPUTER INC." 15.114 100 106 "80" 5 "802.11ax"
"wlan1234_5" "04D4C451C584" "ASUSTek COMPUTER INC." 14.931 100 106 "80" 5 "802.11ax"
"wlan1234_5" "04D4C451C584" "ASUSTek COMPUTER INC." 15.809 100 106 "80" 5 "802.11ax"
"w-inside" "B0B867F3D9B0" "Hewlett Packard Enterprise" 24.668 124 122 "80" 5 "802.11ac"
"w-mobile" "B0B867F3D9B1" "Hewlett Packard Enterprise" 24.217 124 122 "80" 5 "802.11ac"
"w-guest" "B0B867F3D9B2" "Hewlett Packard Enterprise" 23.676 124 122 "80" 5 "802.11ac"
"w-iot" "B0B867F3D9B3" "Hewlett Packard Enterprise" 24.636 124 122 "80" 5 "802.11ac"
"w-training" "B0B867F3D9B4" "Hewlett Packard Enterprise" 23.559 124 122 "80" 5 "802.11ac"
Further Exploration
The
detectedBeaconsInfo
table shows only key information about the APs. To get further information about the beacons, such as data rates supported by the AP, explore the MAC frame configuration in theAPs
structure.If you have access to a configurable AP, change the channel width of your AP and rerun the example to confirm the channel width.
Local Functions
These functions assist in processing the incoming beacons.
function vendor = determineVendor(mac) % DETERMINEVENDOR returns the vendor name of the AP by extracting the % organizationally unique identifier (OUI) from the specified MAC address. persistent ouis vendor = strings(0); try if isempty(ouis) if ~exist("oui.csv","file") disp("Downloading oui.csv from IEEE Registration Authority...") % Increase websave timeout if necessary options = weboptions("Timeout",5); websave("oui.csv","http://standards-oui.ieee.org/oui/oui.csv",options); end ouis = readtable("oui.csv",VariableNamingRule="preserve"); end % Extract OUI from MAC Address. oui = mac(1:6); % Extract vendors name based on OUI. vendor = string(cell2mat(ouis.("Organization Name")(matches(ouis.Assignment,oui)))); catch ME % Rethrow caught error as warning. warning(ME.message+"\nTo skip the determineVendor function call, set retrieveVendorInfo to false.",[]); end if isempty(vendor) vendor = "Unknown"; end end function [mode,bw,operatingChannel] = determineMode(informationElements) % DETERMINEMODE determines the 802.11 standard that the AP uses. % The function checks for the presence of HT, VHT, and HE capability % elements and determines the 802.11 standard that the AP uses. The element % IDs are defined in IEEE Std 802.11-2020 and IEEE Std 802.11ax-2021. elementIDs = cell2mat(informationElements(:,1)); IDs = elementIDs(:,1); if any(IDs==255) if any(elementIDs(IDs==255,2)==35) % HE Packet Format mode = "802.11ax"; else mode = "Unknown"; end vhtElement = informationElements{IDs==192,2}; htElement = informationElements{IDs==61,2}; [bw,operatingChannel] = determineChannelWidth(htElement,vhtElement); elseif any(IDs==191) % VHT Packet Format mode = "802.11ac"; vhtElement = informationElements{IDs==192,2}; htElement = informationElements{IDs==61,2}; [bw,operatingChannel] = determineChannelWidth(htElement,vhtElement); elseif any(IDs==45) % HT Packet Format mode = "802.11n"; htElement = informationElements{IDs==61,2}; [bw,operatingChannel] = determineChannelWidth(htElement); else % Non-HT Packet Format % Exclude b as only DSSS is supported mode ="802.11a/g/j/p"; bw = "Unknown"; operatingChannel = []; end end function [bw,operatingChannel] = determineChannelWidth(htElement,varargin) % DETERMINECHANNELWIDTH returns the bandwidth of the channel from the % beacons HT/VHT operation information elements as defined in IEEE Std 802.11-2020 % Section 9.4.2.56 and Section 9.4.2.158. msbFirst = false; % IEEE Std 802.11-2020 Figure 9-382 and Table 9-190 define each bit in % htOperationInfoBits % Convert to bits to get STA channel width value in 3rd bit. htOperationInfoBits = int2bit(htElement(2),5*8,msbFirst); operatingChannel = 0; if nargin == 2 % IEEE Std 802.11-2020 Figure 9-163 and Table 9-274 define each octet % in vhtElement vhtElement = varargin{1}; % VHT Operation Channel Width Field CW = vhtElement(1); % Channel Center Frequency Segment 0 CCFS0 = vhtElement(2); % Channel Center Frequency Segment 1 CCFS1 = vhtElement(3); % IEEE Std 802.11-2020 Table 11-23 defines the logic below if htOperationInfoBits(3) == 0 bw = "20"; operatingChannel = CCFS0; elseif CW == 0 % HT Operation Channel Width Field is 1 bw = "40"; operatingChannel = CCFS0; elseif CCFS1 == 0 % HT Operation Channel Width Field is 1 and % VHT Operation Channel Width Field is 1 bw = "80"; operatingChannel = CCFS0; elseif abs(CCFS1 - CCFS0) == 8 % HT Operation Channel Width Field is 1 and % VHT Operation Channel Width Field is 1 and % CCFS1 is greater than 0 bw = "160"; operatingChannel = CCFS1; else % HT Operation Channel Width Field is 1 and % VHT Operation Channel Width Field is 1 and % CCFS1 is greater than 0 and % |CCFS1 - CCFS0| is greater than 16 bw = "80+80"; end end if operatingChannel == 0 if htOperationInfoBits(3) == 1 bw = "40"; secondaryChannelOffset = bit2int(htOperationInfoBits(1:2),2,false); if secondaryChannelOffset == 1 % Secondary Channel is above the primary channel. operatingChannel = htElement(1) + 2; elseif secondaryChannelOffset == 3 % Secondary Channel is below the primary channel. operatingChannel = htElement(1) - 2; else warning("Could not determine operating channel.") end else bw = "20"; operatingChannel = htElement(1); end end end function tbl = generateBeaconTable(APs,band) % GENERATEBEACONTABLE converts the access point structure to a table and % cleans up the variable names. tbl = struct2table(APs,"AsArray",true); tbl.Band = repmat(band,length(tbl.SSID),1); tbl = renamevars(tbl,["SNR_dB","Beacon_Channel","Operating_Channel","Channel_Width_MHz"], ... ["SNR (dB)","Primary 20 MHz Channel","Current Channel Center Frequency Index", ... "Channel Width (MHz)"]); tbl = tbl(:,1:9); end