Main Content

OFDM Beacon Receiver Using Software-Defined Radio

This example shows how to retrieve information about WiFi networks on the 5 GHz band using a software-defined radio (SDR). The example scans over the 5 GHz beacon channels and captures waveforms for analysis within MATLAB®. 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 WiFi channels in the 5 GHz band to detect AP beacons that are transmitted on 20 MHz subchannels.

The scanning procedure comprises these steps.

  • Set the frequency band and channels for the SDR to capture.

  • Capture a waveform for a set duration for each specified channel.

  • 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.

This example supports these SDRs for capturing waveforms in the 5 GHz band.

  • ADALM-Pluto from the Communications Toolbox Support Package for Analog Devices® ADALM-Pluto Radio

  • USRP™ E310/E312 from the Communications Toolbox Support Package for USRP™ Embedded Series Radio

  • AD936x/FMCOMMS5 from the Communications Toolbox Support Package for Xilinx® Zynq®-Based Radio

  • USRP™ N200/N210/USRP2/N320/N321/B200/B210/X300/X310 from the Communications Toolbox Support Package for USRP™ Radio

Alternatively, if an SDR is not available for waveform capture, the example supports importing a file with a precaptured waveform.

Example Setup

Before running the example, ensure that you have installed the appropriate support package for the SDR that you intend to use and that you have set up the hardware.

The ReceiveOnSDR field of the rxsim structure determines whether the example receives a waveform off the air or imports a waveform from a MAT file.

rxsim.ReceiveOnSDR = false;

Specify the file name of a precaptured waveform in the fileName variable. Confirm that the MAT file contains these variables: capturedWaveforms, channels, radioSampleRate, and band.

fileName = "capturedBeacons.mat";

By default, the example processes waveforms that are stored in a MAT file. If you are using an SDR to perform a live scan of the 5 GHz band, specify the SDR name, radio sample rate, channel numbers, and other parameters. The valid channel numbers for the 5 GHz band are between 1 and 200, inclusive. However, the valid 20 MHz control channels for an AP 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, and 177.

if rxsim.ReceiveOnSDR
    rxsim.SDRDeviceName = "AD936x";        % SDR that is used for waveform reception
    rxsim.RadioSampleRate = 20000000;      % Configured for 20e6 Hz as this is beacon transmission BW
    rxsim.RadioGain = 50;
    rxsim.FrequencyBand = 5;
    rxsim.ChannelNumbers = [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];       % Default scans all 5 GHz beacon channels
    rxsim.ReceiveAntenna = 1;       % Configure to work with only a single antenna
    rxsim.CaptureTime = milliseconds(102.4);          % Value expected to be of type duration

    % Derived Parameters
    rxsim.CenterFrequencies = wlanChannelFrequency(rxsim.ChannelNumbers,rxsim.FrequencyBand);
        
else
    rx = load(fileName);

    rxsim.ChannelNumbers = rx.channels;
    rxsim.RadioSampleRate = rx.radioSampleRate;
    rxsim.FrequencyBand = rx.band;

    % Derived Parameters
    rxsim.CaptureTime = milliseconds(size(rx.capturedWaveforms,1)/rxsim.RadioSampleRate);
end
osf = rxsim.RadioSampleRate/20e6;

Set Optional Information to Display

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;

To display additional packet information such as payload size, code rate, and modulation for all successfully decoded non-HT packets, select the displayAdditionalInfo box.

displayAdditionalInfo = false;

To display a spectrum and spectrogram for the captured waveform, select the displayScope box.

displayScope = false;

Scan 5 GHz Channels

Initialize SDR object

This example communicates with the radio hardware using the object pertaining to the selected radio.

Create an SDR receiver object by calling hSDRReceiver with your SDR device name. Then, apply the parameters set in the rxsim structure above to the properties of that object.

if rxsim.ReceiveOnSDR
    sdrReceiver = hSDRReceiver(rxsim.SDRDeviceName); 
    sdrReceiver.SampleRate = rxsim.RadioSampleRate;
    sdrReceiver.Gain = rxsim.RadioGain;
    sdrReceiver.ChannelMapping = rxsim.ReceiveAntenna;
    sdrReceiver.OutputDataType = "single";
end

Receiver Design

This diagram shows an overview of the receiver for scanning the selected channels and frequency band and recovering beacon information.

These steps provide further information on the diagram.

  1. Set the center frequency of the SDR, then initialize the capture of a waveform for a set duration.

  2. Determine and apply frequency and timing corrections on the waveform, then attempt to recover the legacy signal (L-SIG) field bits.

  3. Check that the packet format is non-HT.

  4. 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).

  5. Using the recovered MAC frame configuration, check if the non-HT packet is a beacon.

  6. 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.

  7. Check if the waveform contains another packet that you can decode.

Begin Packet Capture and Processing

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 index

  • 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",[]);

Initialize a non-HT config object to use for processing incoming waveforms.

cbw = "CBW20";
cfg = wlanNonHTConfig(ChannelBandwidth=cbw);
ind = wlanFieldIndices(cfg);
indexAP = 1;

Begin scanning and decoding for specified channels.

for i = 1:length(rxsim.ChannelNumbers)    
    
    fprintf("<strong>Scanning channel %d on band %.1f.</strong>\n",rxsim.ChannelNumbers(i),rxsim.FrequencyBand);
    if rxsim.ReceiveOnSDR
        sdrReceiver.CenterFrequency = rxsim.CenterFrequencies(i);
        capturedData = capture(sdrReceiver,rxsim.CaptureTime);
    else
        capturedData = rx.capturedWaveforms(:,i);
    end
    
    % Display spectrum and spectrogram
    if displayScope %#ok<*UNRCH>
        scope = spectrumAnalyzer(ViewType="spectrum-and-spectrogram",SampleRate=rxsim.RadioSampleRate,...
            TimeSpanSource="property",TimeSpan=seconds(rxsim.CaptureTime));
        scope.Title = "Band: " + rxsim.FrequencyBand + " Channel: " + rxsim.ChannelNumbers(i);
        scope(capturedData);
    end    
   
    % Resample the captured data to 20 MHz for beacon processing. 
    if osf ~= 1
        capturedData = resample(capturedData,20e6,rxsim.RadioSampleRate);
    end

    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,rxsim.RadioSampleRate/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*rxsim.RadioSampleRate/osf*3));        
        
        [LSIGBits, failcheck] = wlanLSIGRecover(fmtDetect(1:4e-6*rxsim.RadioSampleRate/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);
                
                % Print additional information on all successfully packets
                if ~decodeStatus && displayAdditionalInfo
                   payloadSize = floor(length(bitsData)/8);
                   [modulation,coderate] = getRateInfo(cfg.MCS);

                   fprintf("Payload Size: %d | Modulation: %s | Code Rate: %s \n",payloadSize,modulation,coderate);
                   fprintf("Type: %s | Sub-Type: %s",cfgMAC.getType,cfgMAC.getSubtype);
                
                end

                % Extract information about channel from the beacon.
                if ~decodeStatus && matches(cfgMAC.FrameType,"Beacon")
                    % Populate the table with information about the beacon.
                    if isempty(cfgMAC.ManagementConfig.SSID)
                        APs(indexAP).SSID = "Hidden";
                    else
                        APs(indexAP).SSID = string(cfgMAC.ManagementConfig.SSID);
                    end

                    fprintf("<strong>%s beacon detected on channel %d in band %.1f.</strong>\n",APs(indexAP).SSID,rxsim.ChannelNumbers(i),rxsim.FrequencyBand);
                    
                    APs(indexAP).BSSID = string(cfgMAC.Address3);
                    if retrieveVendorInfo 
                        APs(indexAP).Vendor = determineVendor(cfgMAC.Address3);
                    else
                        APs(indexAP).Vendor = "Skipped"; 
                    end
                    [APs(indexAP).Mode, APs(indexAP).Channel_Width_MHz, operatingChannel] = ...
                        determineMode(cfgMAC.ManagementConfig.InformationElements);
                    
                    if isempty(operatingChannel)
                        % Default to scanning channel if operating channel
                        % cannot be determined.  
                        operatingChannel = rxsim.ChannelNumbers(i);
                    end
                    
                    APs(indexAP).Beacon_Channel = rxsim.ChannelNumbers(i);
                    APs(indexAP).Operating_Channel = operatingChannel;
                    APs(indexAP).SNR_dB = res.LLTFSNR;
                    APs(indexAP).MAC_Config = cfgMAC;
                    APs(indexAP).Offset = res.PacketOffset;
                    APs(indexAP).Waveform = capturedData;
                    indexAP = indexAP + 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*rxsim.RadioSampleRate/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*rxsim.RadioSampleRate/osf*10;
        end
    end
end
Scanning channel 52 on band 5.0.
WLAN_5G beacon detected on channel 52 in band 5.0.
Scanning channel 56 on band 5.0.
w-inside beacon detected on channel 56 in band 5.0.
w-mobile beacon detected on channel 56 in band 5.0.
w-guest beacon detected on channel 56 in band 5.0.
Scanning channel 157 on band 5.0.
w-inside beacon detected on channel 157 in band 5.0.
w-mobile beacon detected on channel 157 in band 5.0.
w-guest beacon detected on channel 157 in band 5.0.

Convert the APs structure to a table and display the information specified in step 6 by using the local function generateBeaconTable.

detectedBeaconsInfo = generateBeaconTable(APs,rxsim.FrequencyBand,retrieveVendorInfo)
detectedBeaconsInfo=7×9 table
       SSID           BSSID                    Vendor               SNR (dB)    Primary 20 MHz Channel    Current Channel Center Frequency Index    Channel Width (MHz)    Band       Mode   
    __________    ______________    ____________________________    ________    ______________________    ______________________________________    ___________________    ____    __________

    "WLAN_5G"     "04D4C451C584"    "ASUSTek COMPUTER INC."           34.57               52                                50                             "160"            5      "802.11ax"
    "w-inside"    "B0B867F6B2D0"    "Hewlett Packard Enterprise"      26.24               56                                58                             "80"             5      "802.11ac"
    "w-mobile"    "B0B867F6B2D1"    "Hewlett Packard Enterprise"     26.251               56                                58                             "80"             5      "802.11ac"
    "w-guest"     "B0B867F6B2D2"    "Hewlett Packard Enterprise"     25.843               56                                58                             "80"             5      "802.11ac"
    "w-inside"    "B0B867F3D9B0"    "Hewlett Packard Enterprise"     31.592              157                               155                             "80"             5      "802.11ac"
    "w-mobile"    "B0B867F3D9B1"    "Hewlett Packard Enterprise"     31.971              157                               155                             "80"             5      "802.11ac"
    "w-guest"     "B0B867F3D9B2"    "Hewlett Packard Enterprise"       33.3              157                               155                             "80"             5      "802.11ac"

if rxsim.ReceiveOnSDR
    release(sdrReceiver);
end

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 the APs 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 [modulation,coderate] = getRateInfo(mcs)
% GETRATEINFO returns the modulation scheme as a character array and the
% code rate of a packet given a scalar integer representing the modulation
% coding scheme
switch mcs
    case 0 % BPSK
        modulation = 'BPSK';
        coderate = '1/2';
    case 1 % BPSK
        modulation = 'BPSK';
        coderate = '3/4';
    case 2 % QPSK
        modulation = 'QPSK';
        coderate = '1/2';
    case 3 % QPSK
        modulation = 'QPSK';
        coderate = '3/4';
    case 4 % 16QAM
        modulation = '16QAM';
        coderate = '1/2';
    case 5 % 16QAM
        modulation = '16QAM';
        coderate = '3/4';
    case 6 % 64QAM
        modulation = '64QAM';
        coderate = '2/3';
    otherwise % 64QAM
        modulation = '64QAM';
        coderate = '3/4';
end
end

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...")
            options = weboptions("Timeout",10);
            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 operation information elements as defined in IEEE Std 802.11-2020
% Table 11-23.

msbFirst = false;

% Convert to bits to get STA channel width value in 3rd bit.
htOperationInfoBits = int2bit(htElement(2),5*8,msbFirst);
operatingChannel = 0;

if nargin == 2
    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);

    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,retrieveVendorInfo)
% 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)"]);
if retrieveVendorInfo
    tbl = tbl(:,1:9);
else
    tbl = tbl(:,[1:2,4:9]);
end

end