Chapter 6 of 14Layer 2–7 Protocol Reference (81 Protocols)10 min read

Layer 2 Switching, VLAN Trunking & Data-Link Protocols Reference (ARP, VLAN, QINQ, LLDP, STP, LACP, EAPOL, MPLS)

Testing Layer 2 edge cases—double-tagged 802.1ad QinQ trunks, Gratuitous ARP (GARP) VIP failovers, STP BPDU Guard triggers, LACP port-channel negotiations, 802.1X EAPOL NAC onboarding, or MPLS label stacks—is normally a headache because OS network stacks strip VLAN tags and physical lab switches take time to cable and configure.

With FrameAssembly, you write the Layer 2 or Layer 2.5 frame in a few lines of .fasm code, compile it to .pcap (frameassembly l2_test.fasm), and replay it onto a trunk port with tcpreplay—or convert an existing .pcap / .pcapng switch trace into .fasm code with frameassembly reverse trace.pcap. When optional fields are omitted, FrameAssembly automatically builds Ethernet II or IEEE 802.3 LLC headers, derives unicast, broadcast, and multicast MAC addresses, and populates mandatory protocol TLVs.

MAC-Only Layer 2 Hosts & Inline compile Statements

All 8 Layer 2 protocols (ARP, VLAN, QINQ, LLDP, STP, LACP, EAPOL, MPLS) support MAC-only host blocks (host sw1 { mac 00:1c:73:00:00:01 }) without requiring an ip address, and you can write packet statements directly inside compile { ... } without a separate flow block:

l2_protocols_example_1.fasm
host sw1 { mac 00:1c:73:00:00:01 }
host sw2 { mac 00:1c:73:00:00:02 }
compile {
sw1 -> sw2 lldp chassis_id "00:1c:73:00:00:01" port_id "Ethernet1/1" ttl 120 sysname "spine-01" wait 2ms
sw1 <- sw2 stp rstp root_id "4096/00:1c:73:00:00:02" bridge_id "4096/00:1c:73:00:00:02" port_id 0x8001 cost 0 flags 0x3c wait 2ms
sw1 -> sw2 lacp actor_sys 00:1c:73:00:00:01 actor_key 10 actor_port 1 actor_state 0x3f partner_sys 00:1c:73:00:00:02 partner_key 10 partner_port 1 partner_state 0x3f
}
ProtocolKeywordStandardFraming / EtherTypeDefault Destination MACPrimary Operational Use Cases
ARPARPRFC 826 / RFC 5227Ethernet II (0x0806)ff:ff:ff:ff:ff:ff (Request) / Unicast (Reply)Next-hop MAC resolution, Gratuitous ARP (GARP) VIP failover, RFC 5227 duplicate IP probes
VLANVLANIEEE 802.1Q / 802.1p802.1Q Tag (0x8100)Host / Protocol MAC12-bit VLAN IDs (1..4094), 3-bit CoS priority (PCP 0..7), 1-bit drop eligibility (DEI 0..1)
QinQQINQIEEE 802.1adS-Tag (0x88A8) + C-Tag (0x8100)Host / Protocol MACCarrier Metro Ethernet double-tagging (qinq <s_vid> inner_vlan <c_vid>) and L2VPN handoffs
LLDPLLDPIEEE 802.1ABEthernet II (0x88CC)01:80:c2:00:00:0eSwitch/server topology discovery (CHASSIS_ID, PORT_ID, TTL, SYSNAME, SYSDESC)
STP / RSTPSTPIEEE 802.1D / 802.1w802.3 Length + 802.2 LLC (0x424203)01:80:c2:00:00:00Root Bridge elections, RSTP Proposal/Agreement handshakes, BPDU Guard and Root Guard testing
LACPLACPIEEE 802.3ad / 802.1AXSlow Protocols (0x8809)01:80:c2:00:00:02Dynamic LAG / Port-Channel Actor and Partner state synchronization
EAPOLEAPOLIEEE 802.1X / RFC 3748Ethernet II (0x888E)01:80:c2:00:00:03Port-based NAC (START, EAP_PACKET Identity/MD5/TLS/PEAP, WPA KEY, LOGOFF)
MPLSMPLSRFC 3031 / RFC 3032Ethernet II (0x8847)Host / Next-Hop MAC20-bit label switching (MPLS / LABEL), Traffic Class (EXP / TC), BOS, and MPLS_TTL

Universal Ethernet II Header Overrides & Automatic MAC Derivation

Every statement in FrameAssembly—whether it is a raw Layer 2 frame (ARP, LLDP, STP) or a Layer 7 application packet (HTTP, DNS, SNMP3, MODBUS)—is wrapped in an outer Ethernet frame. You can override the outer Ethernet header on any statement using three universal modifiers:

ModifierSyntaxValue FormatDescription
Source MACSRCMAC <mac>aa:bb:cc:dd:ee:ffOverrides the 48-bit Ethernet source MAC address for this single frame
Destination MACDSTMAC <mac>aa:bb:cc:dd:ee:ffOverrides the 48-bit Ethernet destination MAC address for this single frame
EtherTypeETHERTYPE <0..65535>Hex (0x88cc) or 0..65535Overrides the 16-bit Ethernet II EtherType preceding the inner payload

How FrameAssembly Automatically Derives MAC Addresses

When you omit SRCMAC, DSTMAC, or the optional MAC field in a HOST declaration, FrameAssembly selects the wire MAC address using the following priority order:

PriorityConditionSource MAC SelectedDestination MAC Selected
1. Statement Overridesrcmac <mac> or dstmac <mac> on statementThe statement's srcmacThe statement's dstmac
2. Protocol Control GroupLLDP, STP, LACP, EAPOL, ARP REQUESTHost mac (or default unicast)IEEE 802.1D multicast (01:80:c2:00:00:xx) or broadcast (ff:ff:ff:ff:ff:ff)
3. Multicast Target IPDestination IP is IPv4 224.0.0.0/4 or IPv6 ff00::/8Host mac (or default unicast)RFC 1112 01:00:5e:xx:xx:xx (IPv4) or RFC 2464 33:33:xx:xx:xx:xx (IPv6)
4. Host Declarationhost <id> { ip <ip> mac <mac> }Sending host's macReceiving host's mac
5. Unicast FallbackHost mac omitted on unicast IP00:00:00:00:00:01 (->) / 00:00:00:00:00:02 (<-)00:00:00:00:00:02 (->) / 00:00:00:00:00:01 (<-)

Why Direction-Aware Unicast Fallback Matters: When replaying a .pcap through a physical or virtual switch with tcpreplay, forward (->) and reverse (<-) packets automatically swap 00:00:00:00:00:01 and 00:00:00:00:00:02 even if you never typed a mac address in your host blocks. This prevents switch MAC-table flapping alerts.

custom_ethernet_framing.fasm
host sensor_a { ip 10.10.1.10 mac 00:1b:21:aa:bb:01 }
host sensor_b { ip 10.10.1.20 mac 00:1b:21:aa:bb:02 }
flow custom_ethernet_framing(a, b) {
// Standard unicast UDP frame using the MACs declared in the host blocks
a -> b udp srcport 40000 dstport 40000 payload "NORMAL_UNICAST" wait 1ms
// Spoofed source MAC, broadcast destination MAC, and custom EtherType (0x88B5 IEEE Local Experimental)
a -> b udp srcport 40000 dstport 40000 srcmac de:ad:be:ef:00:01 dstmac ff:ff:ff:ff:ff:ff ethertype 0x88b5 payload "CUSTOM_L2_ETHERTYPE"
}
compile {
custom_ethernet_framing(sensor_a, sensor_b)
}

1. Address Resolution Protocol (ARP — RFC 826 / RFC 5227)

ARP resolves IPv4 addresses (0x0800) to 48-bit Ethernet hardware addresses (EtherType 0x0806). Network administrators use ARP captures to validate default gateway resolution, test Dynamic ARP Inspection (DAI) on access switches, simulate Gratuitous ARP (GARP) VIP failovers for HA firewalls/load balancers, and test RFC 5227 Duplicate Address Detection (DAD) probes.

Default Wire Behavior & Dual-Stack IPv6 Adaptation

  • IPv4 Hosts (EtherType 0x0806): Builds a 28-byte ARP header (HTYPE = 1 Ethernet, PTYPE = 0x0800 IPv4, HLEN = 6, PLEN = 4).
  • ARP REQUEST (default): Sets Opcode 1, outer Ethernet Destination MAC to broadcast (ff:ff:ff:ff:ff:ff), and Target Hardware Address (THA) to 00:00:00:00:00:00.
  • ARP REPLY: Sets Opcode 2, outer Ethernet Destination MAC to the target host's unicast MAC, and THA to the target host's MAC.
  • GRATUITOUS (or GARP): Sets Target Protocol Address (TPA) equal to Sender Protocol Address (SPA) and zeroes THA (00:00:00:00:00:00).
  • IPv6 Hosts (EtherType 0x86DD, RFC 4861 Automatic Dual-Stack IPv6 Adaptation): If you run an ARP flow with IPv6 hosts inside compile { ... }, FrameAssembly automatically translates the statements into RFC 4861 ICMPv6 Neighbor Discovery packets with Hop Limit 255:
  • ARP REQUEST becomes an ICMPv6 Neighbor Solicitation (Type 135, Option 1 Source Link-Layer Address) sent to the target's Solicited-Node multicast address (ff02::1:ffxx:xxxx) and Ethernet MAC (33:33:ff:xx:xx:xx).
  • ARP REPLY becomes an ICMPv6 Neighbor Advertisement (Type 136, Solicited + Override flags 0x60, Option 2 Target Link-Layer Address).

ARP Modifiers

ModifierSyntax & AliasesWire Field & DescriptionDefault When Omitted
Request OpcodeREQUESTSets 16-bit OPER = 1 (ARP Request)REQUEST (1)
Reply OpcodeREPLYSets 16-bit OPER = 2 (ARP Reply)REQUEST (1)
Gratuitous FlagGRATUITOUS or GARPSets TPA = SPA and THA = 00:00:00:00:00:00 for VIP announcementsDisabled
Sender MACSHA <mac> or SENDERMAC <mac>48-bit Sender Hardware Address (SHA) in the ARP payloadSending host's MAC
Sender IPSPA <ipv4> or SENDERIP <ipv4>32-bit Sender Protocol Address (SPA, e.g. 0.0.0.0 for RFC 5227 probes)Sending host's IPv4
Target MACTHA <mac> or TARGETMAC <mac>48-bit Target Hardware Address (THA) in the ARP payload00:00:00:00:00:00 (Req) / Dst MAC (Reply)
Target IPTPA <ipv4> or TARGETIP <ipv4>32-bit Target Protocol Address (TPA, the IP being queried or claimed)Receiving host's IPv4

Example 1A: Gateway ARP Resolution, RFC 5227 Probe & HA VIP Failover (GARP)

arp_resolution_and_vip_failover.fasm
host workstation { ip 192.168.10.55 mac 00:50:56:a1:10:55 }
host fw_active { ip 192.168.10.1 mac 00:50:56:fe:00:01 }
host fw_standby { ip 192.168.10.2 mac 00:50:56:fe:00:02 }
host lan_bcast { ip 192.168.10.255 mac ff:ff:ff:ff:ff:ff }
flow arp_resolution_and_vip_failover(client, pri, sec, bcast) {
// 1. Standard broadcast ARP Request from workstation asking "Who has 192.168.10.1?"
client -> pri arp request dstmac ff:ff:ff:ff:ff:ff tha 00:00:00:00:00:00 wait 1ms
// 2. Unicast ARP Reply from the active firewall
client <- pri arp reply wait 5ms
// 3. RFC 5227 Duplicate Address Detection (ARP Probe with Sender IP 0.0.0.0)
client -> bcast arp request sendermac 00:50:56:a1:10:55 senderip 0.0.0.0 targetmac 00:00:00:00:00:00 targetip 192.168.10.55 wait 2ms
// 4. Standby firewall takes over the 192.168.10.1 VIP and broadcasts a Gratuitous ARP
sec -> bcast arp reply gratuitous sendermac 00:50:56:fe:00:02 senderip 192.168.10.1 targetmac ff:ff:ff:ff:ff:ff targetip 192.168.10.1
}
compile {
arp_resolution_and_vip_failover(workstation, fw_active, fw_standby, lan_bcast)
}

Example 1B: Dual-Stack ARP Flow Over Both IPv4 and IPv6

neighbor_lookup.fasm
host app_v4 { ip 10.20.0.10 mac 00:16:3e:10:00:10 }
host gw_v4 { ip 10.20.0.1 mac 00:16:3e:10:00:01 }
host app_v6 { ip 2001:db8:20::10 mac 00:16:3e:60:00:10 }
host gw_v6 { ip 2001:db8:20::1 mac 00:16:3e:60:00:01 }
flow neighbor_lookup(host_ep, router_ep) {
host_ep -> router_ep arp request wait 1ms
host_ep <- router_ep arp reply
}
compile {
// Emits RFC 826 ARP Request + Reply (EtherType 0x0806)
neighbor_lookup(app_v4, gw_v4)
// Automatically emits RFC 4861 ICMPv6 Neighbor Solicitation + Advertisement (Hop Limit 255)
neighbor_lookup(app_v6, gw_v6)
}

Inspect with tshark:

console
$ frameassembly arp_test.fasm
$ tshark -r arp_test.pcap -Y "arp or icmpv6" -T fields -e frame.number -e eth.src -e eth.dst -e arp.opcode -e arp.src.proto_ipv4 -e arp.dst.proto_ipv4

2. Virtual LAN (VLAN — IEEE 802.1Q) & Provider Bridging (QINQ — IEEE 802.1ad)

Both VLAN and QINQ work in two ways:

  1. Cross-Layer Encapsulation Modifiers: Add vlan <vid> [pcp <0..7>] [dei <0..1>] or qinq <outer_svid> inner_vlan <inner_cvid> [pcp <0..7>] [dei <0..1>] to any statement (ARP, STP, OSPF, BGP, TCP, UDP, HTTP, SIP, MODBUS, etc.) to insert 802.1Q or 802.1ad tags ahead of the inner packet.
  2. Standalone Layer 2 Statements: Write a -> b vlan 100 or a -> b qinq 2000 inner_vlan 100 with no upper-layer protocol keyword to emit a tagged Ethernet frame (wrapping a valid inner IP+UDP packet by default, or raw bytes if payload / hexpayload is provided).

VLAN & QINQ Modifiers

ModifierSyntaxValid RangeWire Field & DescriptionDefault
802.1Q VLAN IDVLAN <1..4094>1..4094Inserts an IEEE 802.1Q tag (TPID = 0x8100) with 12-bit VLAN ID1 (if bare vlan)
Priority (CoS)PCP <0..7>0..73-bit IEEE 802.1p Priority Code Point in the 802.1Q / 802.1ad TCI0 (Best Effort)
Drop EligibleDEI <0..1>0..11-bit Drop Eligible Indicator (1 = drop first during queue congestion)0
Outer Service VLANQINQ <1..4094>1..4094Inserts an outer IEEE 802.1ad Service VLAN tag (TPID = 0x88A8)1 (if bare qinq)
Inner Customer VLANINNER_VLAN <1..4094>1..4094Inserts an inner IEEE 802.1Q Customer VLAN tag (TPID = 0x8100) inside QINQ2 (standalone)

Important Syntax Rules:

Valid VLAN Range (1..4094):* IEEE 802.1Q reserves VLAN 0 (priority-tagged frame) and 4095 (0xFFF implementation reserved). FrameAssembly rejects vlan 0 or vlan 4095 with a clear validation error pointing to the invalid VLAN ID.

Double-Tagging Rule:* For IEEE 802.1ad double-tagged frames, always write qinq <outer_svid> inner_vlan <inner_cvid>. Do not combine qinq and vlan on the same statement.

IEEE 802.1p Class of Service (PCP) Reference Table

PCP ValueIEEE 802.1p Traffic ClassTypical DSCP PairingCommon Enterprise & Carrier Use Case
0Best Effort (BE)DSCP 0 (BE)Default untagged/tagged LAN user traffic and HTTP browsing
1Background (BK)DSCP 8 (CS1)Bulk backups (rsync, NAS replication), scavenger traffic (often paired with DEI 1)
2Excellent Effort (EE)DSCP 18 (AF21)Transactional database and ERP traffic
3Critical Applications (CA)DSCP 26 (AF31)Mission-critical enterprise messaging, RoCEv2 RDMA lossless queues (PFC priority 3)
4Video (VI)DSCP 34 (AF41)Conferencing, RTSP streaming, and broadcast IPTV
5Voice (VO)DSCP 46 (EF)VoIP RTP audio (SIP/RTP) and low-latency strict-priority queues
6Internetwork Control (IC)DSCP 48 (CS6)Routing protocols (OSPF, BGP, PIM, BFD) and keepalives
7Network Control (NC)DSCP 56 (CS7)Layer 2 control protocols and high-priority fabric management

Example 2A: Enterprise 802.1Q Trunk with Multi-CoS Traffic (PCP & DEI)

enterprise_8021q_trunk.fasm
host tor_sw_a { ip 172.16.10.1 mac 00:1c:73:10:00:01 }
host tor_sw_b { ip 172.16.10.2 mac 00:1c:73:10:00:02 }
flow enterprise_8021q_trunk(a, b) {
// VLAN 10: High-priority VoIP RTP audio (PCP 5 = Voice, DSCP 46 = EF)
a -> b vlan 10 pcp 5 dei 0 dscp 46 rtp seq 101 timestamp 16000 ssrc 0x11223344 payload "G711_PCMU_FRAME" wait 1ms
// VLAN 20: RoCEv2 lossless storage queue (PCP 3, DSCP 26 = AF31)
a -> b vlan 20 pcp 3 dei 0 dscp 26 udp srcport 51000 dstport 4791 payload "RDMA_WRITE_ONLY" wait 1ms
// VLAN 99: Scavenger backup traffic marked Drop-Eligible (PCP 1, DEI 1, DSCP 8 = CS1)
a -> b vlan 99 pcp 1 dei 1 dscp 8 tcp psh ack srcport 49152 dstport 873 payload "RSYNC_SNAPSHOT_CHUNK"
}
compile {
enterprise_8021q_trunk(tor_sw_a, tor_sw_b)
}

Example 2B: Carrier IEEE 802.1ad QINQ Metro Ethernet Handoff

metro_qinq_handoff.fasm
host cpe_site_a { ip 10.100.40.1 mac 00:50:56:ce:40:01 }
host cpe_site_b { ip 10.100.40.2 mac 00:50:56:ce:40:02 }
flow metro_qinq_handoff(a, b) {
// Carrier S-VLAN 2500 (0x88A8) + Customer C-VLAN 40 (0x8100) carrying eBGP control plane (PCP 6)
a -> b qinq 2500 inner_vlan 40 pcp 6 dscp 48 tcp syn srcport 53000 dstport 179 wait 1ms
a <- b qinq 2500 inner_vlan 40 pcp 6 dscp 48 tcp syn ack wait 1ms
a -> b qinq 2500 inner_vlan 40 pcp 6 dscp 48 tcp ack wait 1ms
a -> b qinq 2500 inner_vlan 40 pcp 6 dscp 48 bgp open asn 65100 holdtime 90 bgpid 10.100.40.1 wait 2ms
a <- b qinq 2500 inner_vlan 40 pcp 6 dscp 48 bgp open asn 65200 holdtime 90 bgpid 10.100.40.2
}
compile {
metro_qinq_handoff(cpe_site_a, cpe_site_b)
}

Inspect with tshark:

console
$ frameassembly trunk_test.fasm
$ tshark -r trunk_test.pcap -T fields -e frame.number -e eth.type -e vlan.id -e vlan.prio -e vlan.dei -e ip.src -e ip.dst

Tracking down miscabled leaf-spine ports or testing how an inventory daemon (lldpd, NetBox, or a VoIP phone provisioning script) parses neighbor discovery frames normally requires live switches on the other end of the patch cord. With LLDP, you can script switch, router, and hypervisor advertisements—including chassis identity, port names, hold times, system hostnames, software banners, and TTL 0 port-shutdown withdrawals—directly in .fasm code.

Default Wire Behavior

  • Framing: Ethernet II (EtherType = 0x88CC) addressed to the IEEE 802.1D Nearest Bridge non-TPMR multicast MAC 01:80:c2:00:00:0e.
  • Mandatory TLVs (Always Emitted):
  1. Chassis ID (TLV Type 1): Subtype 4 (MAC address). Defaults to the sending host's 6-byte MAC address if CHASSIS_ID is omitted, or encodes your custom string if specified.
  2. Port ID (TLV Type 2): Subtype 5 (Interface Name). Defaults to "eth0" if PORT_ID is omitted.
  3. Time To Live (TLV Type 3): 16-bit hold time in seconds. Defaults to 120 seconds if TTL is omitted.
  • Optional TLVs & End Marker: Emits System Name (TLV Type 5) when SYSNAME is specified, System Description (TLV Type 6) when SYSDESC is specified, appends any custom PAYLOAD / HEXPAYLOAD bytes (such as IEEE 802.1 / 802.3 Organizationally Specific TLVs of Type 127), and terminates with the mandatory End Of LLDPDU (TLV Type 0, Length 0) marker.

LLDP Modifiers

ModifierSyntaxWire TLV & DescriptionDefault When Omitted
Chassis IDCHASSIS_ID "<text>"TLV Type 1 (Subtype 4): Switch/server chassis MAC or identifier stringSending host's 6-byte MAC
Port IDPORT_ID "<name>" or <0..65535>TLV Type 2 (Subtype 5): Interface name (e.g. "Ethernet1/1", "ens3f0")"eth0"
Hold Time (TTL)TTL <0..255>TLV Type 3: Neighbor hold time in seconds (0 signals LLDP shutdown/withdraw)120 seconds
System NameSYSNAME "<text>"TLV Type 5: Device FQDN or hostnameOmitted
System DescriptionSYSDESC "<text>"TLV Type 6: NOS version, kernel release, and hardware platform stringOmitted
Custom TLVsHEXPAYLOAD "<hex>"Appends raw TLV bytes (e.g. Type 127 Org-Specific TLVs) before End-of-LLDPDUEmpty

Example: Leaf-Spine Data Center & Hypervisor LLDP Exchange

dc_lldp_discovery.fasm
host spine_01 { ip 10.255.0.1 mac 00:1c:73:a0:01:01 }
host leaf_01 { ip 10.255.0.11 mac 00:1c:73:b0:11:49 }
flow dc_lldp_discovery(spine, leaf) {
// Spine switch advertising 400G uplink port
spine -> leaf lldp chassis_id "00:1c:73:a0:01:01" port_id "Ethernet1/1" ttl 120 sysname "spine-01.dc1.example.net" sysdesc "Arista EOS 4.32.1F 7060X5" wait 5ms
// Leaf switch replying with Management/PVID Org-Specific TLV (Type 127, Len 6, OUI 00-80-C2, Subtype 1, VLAN 100)
spine <- leaf lldp chassis_id "00:1c:73:b0:11:49" port_id "Ethernet49/1" ttl 120 sysname "leaf-01.dc1.example.net" sysdesc "SONiC OS 202511 ToR" hexpayload "fe060080c2010064" wait 5ms
// Port administrative shutdown: TTL 0 instructs neighbor to age out entry immediately
spine <- leaf lldp chassis_id "00:1c:73:b0:11:49" port_id "Ethernet49/1" ttl 0
}
compile {
dc_lldp_discovery(spine_01, leaf_01)
}

Inspect with tshark:

console
$ frameassembly lldp_test.fasm
$ tshark -r lldp_test.pcap -Y "lldp" -V

4. Spanning Tree Protocol (STP / RSTP — IEEE 802.1D / 802.1w)

STP emits classic IEEE 802.1D Configuration BPDUs or IEEE 802.1w Rapid Spanning Tree (RSTP) BPDUs. Use STP to validate Root Bridge elections, test RSTP Proposal/Agreement rapid convergence, or verify that access ports configured with BPDU Guard (spanning-tree bpduguard enable) or Root Guard immediately err-disable when a rogue switch injects a superior BPDU.

Default Wire Behavior

  • Framing: IEEE 802.3 length-framed Ethernet addressed to the Bridge Group multicast MAC 01:80:c2:00:00:00, carrying a 3-byte IEEE 802.2 LLC header (DSAP = 0x42, SSAP = 0x42, Control = 0x03).
  • BPDU Packet Format:
  • Without RSTP: 35-byte IEEE 802.1D Configuration BPDU (Protocol ID = 0x0000, Version = 0, BPDU Type = 0x00).
  • With RSTP: 36-byte IEEE 802.1w Rapid Spanning Tree BPDU (Protocol ID = 0x0000, Version = 2, BPDU Type = 0x02, plus trailing Version 1 Length = 0x00).
  • Timers (MAX_AGE, HELLO_TIME, FORWARD_DELAY) are automatically scaled into IEEE 802.1D 1/256-second wire units (seconds << 8).

STP Modifiers

ModifierSyntaxWire Field & FormatDefault When Omitted
Rapid STP ModeRSTPSets BPDU Version 2, Type 0x02, and appends 1-byte Version 1 Length (0x00)Classic STP (Version 0)
BPDU FlagsFLAGS <0..255>8-bit STP/RSTP flags byte (accepts hex like 0x0e or 0x7c)0x00
Root Bridge IDROOT_ID "<prio>/<mac>"8-byte Root Identifier (2B Priority + 6B MAC, e.g. "4096/00:05:73:a0:00:01")32768 + Source MAC
Sender Bridge IDBRIDGE_ID "<prio>/<mac>"8-byte Transmitting Bridge Identifier (2B Priority + 6B MAC)32768 + Source MAC
Root Path CostCOST <number>32-bit Root Path Cost (0 on Root Bridge, 2000 for 10GbE, 200 for 100GbE)0
Port IdentifierPORT_ID <0..65535>16-bit Port Priority + Port Number (e.g. 0x8001 = priority 128, port 1)0x8001
Max Age TimerMAX_AGE <seconds>Maximum BPDU age in seconds before topology reconvergence20 seconds
Hello TimeHELLO_TIME <seconds>Interval in seconds between Configuration BPDUs2 seconds
Forward DelayFORWARD_DELAY <seconds>Listening/Learning state delay in seconds15 seconds

Syntax Note: Always include the underscore in ROOT_ID and BRIDGE_ID. Both fields accept "<priority>/<mac>" (such as "4096/00:1c:73:00:00:01" or "0x1000/00:1c:73:00:00:01"), a bare MAC address string ("00:1c:73:00:00:01" with default priority 32768), or a 16-character hex string.

STP / RSTP FLAGS Bitmask Reference

Bit(s)Hex MaskFlag / Role MeaningCommon Composite FLAGS Values
Bit 00x01Topology Change (TC) — instructs switches to flush MAC tables faster0x01 (Classic STP Topology Change)
Bit 10x02Proposal (RSTP) — Designated port proposing rapid transition0x0e (0x02 Proposal + 0x0c Designated Role)
Bits 3..20x04 / 0x08 / 0x0cPort Role (RSTP): 0x04 Alternate/Backup, 0x08 Root, 0x0c Designated0x3c (0x0c Designated + 0x10 Learning + 0x20 Forwarding)
Bit 40x10Learning (RSTP) — port is learning MAC addresses0x7c (0x08 Root + 0x10 Learn + 0x20 Fwd + 0x40 Agreement)
Bit 50x20Forwarding (RSTP) — port is forwarding user frames0x3d (Designated + Learn + Fwd + Topology Change 0x01)
Bit 60x40Agreement (RSTP) — Root port acknowledging a Proposal0x81 (Classic STP TC + TCA)
Bit 70x80Topology Change Acknowledgment (TCA)—

Example: RSTP Proposal/Agreement & Rogue Superior BPDU Test

rstp_convergence_and_bpduguard.fasm
host core_root { ip 10.0.1.1 mac 00:1c:73:00:00:01 }
host dist_sw { ip 10.0.1.2 mac 00:1c:73:00:00:02 }
host rogue_sw { ip 10.0.1.99 mac 00:e0:4c:99:99:99 }
flow rstp_convergence_and_bpduguard(root, dist, rogue) {
// 1. Root bridge (Priority 4096) sends RSTP Proposal (0x0e = Proposal + Designated Role)
root -> dist stp rstp root_id "4096/00:1c:73:00:00:01" bridge_id "4096/00:1c:73:00:00:01" port_id 0x8001 cost 0 max_age 20 hello_time 2 forward_delay 15 flags 0x0e wait 2ms
// 2. Distribution switch (Priority 32768) replies with RSTP Agreement (0x7c = Agreement + Fwd + Learn + Root Role)
root <- dist stp rstp root_id "4096/00:1c:73:00:00:01" bridge_id "32768/00:1c:73:00:00:02" port_id 0x8001 cost 2000 max_age 20 hello_time 2 forward_delay 15 flags 0x7c wait 5ms
// 3. Steady-state Designated Forwarding BPDU (0x3c)
root -> dist stp rstp root_id "4096/00:1c:73:00:00:01" bridge_id "4096/00:1c:73:00:00:01" port_id 0x8001 cost 0 max_age 20 hello_time 2 forward_delay 15 flags 0x3c wait 5ms
// 4. Rogue switch on an access port injects a Priority 0 Superior BPDU (triggers Root Guard / BPDU Guard)
rogue -> dist stp rstp root_id "0/00:e0:4c:99:99:99" bridge_id "0/00:e0:4c:99:99:99" port_id 0x8001 cost 0 flags 0x3c
}
compile {
rstp_convergence_and_bpduguard(core_root, dist_sw, rogue_sw)
}

Anyone who has stared at a switch port stuck in Individual or Suspended state knows that debugging IEEE 802.3ad Link Aggregation (LAG / Cisco & Arista Port-Channel / Linux bond0 mode 4) comes down to the 8-bit Actor and Partner state flags. FrameAssembly builds complete 110-byte LACPDUs over Ethernet Slow Protocols (EtherType = 0x8809, destination MAC 01:80:c2:00:00:02)—including Actor (Type 1, Length 20), Partner (Type 2, Length 20), Collector (Type 3, Length 16), and Terminator (Type 0, Length 0) TLVs plus the mandatory 50-byte IEEE 802.3ad zero pad—so you can replay exact bundle bring-up and timeout sequences without a second switch.

LACP Modifiers

ModifierSyntaxWire Field & DescriptionDefault When Omitted
Actor System MACACTOR_SYS <mac>48-bit local switch/server LACP System ID (Priority fixed at 0x8000)Sending host's MAC
Actor KeyACTOR_KEY <0..65535>16-bit local administrative/operational bundle key (e.g. 100 for Po100)1
Actor PortACTOR_PORT <0..65535>16-bit local member port number (1, 2, etc.; Priority fixed at 0x8000)1
Actor StateACTOR_STATE <0..255>8-bit local LACP state bitmask (see table below)0x3d
Partner System MACPARTNER_SYS <mac>48-bit remote peer's LACP System ID as seen by the ActorReceiving host's MAC
Partner KeyPARTNER_KEY <0..65535>16-bit remote peer's operational key1
Partner PortPARTNER_PORT <0..65535>16-bit remote peer's port number1
Partner StatePARTNER_STATE <0..255>8-bit remote peer's state bitmask0x3d

LACP State Byte (ACTOR_STATE / PARTNER_STATE) Bitmask Breakdown

BitHex ValueFlag Name1 (Set)0 (Cleared)
Bit 00x01LACP_ActivityActive (transmits LACPDUs actively)Passive (waits for peer LACPDU)
Bit 10x02LACP_TimeoutShort Timeout (1s fast periodic, 3s timeout)Long Timeout (30s slow periodic, 90s timeout)
Bit 20x04AggregationAggregatable (can join a multi-link LAG)Individual (standalone link only)
Bit 30x08SynchronizationIn Sync (allocated to the correct LAG group)Out of Sync (still negotiating)
Bit 40x10CollectingCollecting (receiving frames on this member link)Not collecting
Bit 50x20DistributingDistributing (transmitting frames on this member)Not distributing
Bit 60x40DefaultedDefaulted (no LACPDU received from partner yet)Using real Partner LACPDU info
Bit 70x80ExpiredExpired (receive state machine timed out)Normal operation

Common Operational Hex Combinations:

  • 0x47 (0x01 + 0x02 + 0x04 + 0x40): Initial Unbundled Port — Active, Short Timeout, Aggregatable, Defaulted (partner unknown).
  • 0x3d (0x01 + 0x04 + 0x08 + 0x10 + 0x20): Healthy Slow-Timer Bundle (Default) — Active, Long Timeout (30s), Aggregatable, In Sync, Collecting, Distributing.
  • 0x3f (0x01 + 0x02 + 0x04 + 0x08 + 0x10 + 0x20): Healthy Fast-Timer Bundle — Active, Short Timeout (1s), Aggregatable, In Sync, Collecting, Distributing.

Example: LACP Port-Channel Bring-Up (Defaulted to In-Sync Collecting/Distributing)

lacp_bond_negotiation.fasm
host tor_sw { ip 10.0.2.1 mac 00:1c:73:11:22:33 }
host linux_hv { ip 10.0.2.2 mac 00:50:56:88:99:aa }
flow lacp_bond_negotiation(sw, hv) {
// 1. Switch port comes up unbundled (0x47 = Active/Short/Agg/Defaulted) with zeroed Partner info
sw -> hv lacp actor_sys 00:1c:73:11:22:33 actor_key 10 actor_port 1 actor_state 0x47 partner_sys 00:00:00:00:00:00 partner_key 0 partner_port 0 partner_state 0x00 wait 2ms
// 2. Linux bond0 replies acknowledging the switch's System MAC/Key/Port and advertising In-Sync (0x0f)
sw <- hv lacp actor_sys 00:50:56:88:99:aa actor_key 10 actor_port 1 actor_state 0x0f partner_sys 00:1c:73:11:22:33 partner_key 10 partner_port 1 partner_state 0x07 wait 2ms
// 3. Both sides transition to full Collecting + Distributing (0x3f = Active/Short/Agg/Sync/Col/Dist)
sw -> hv lacp actor_sys 00:1c:73:11:22:33 actor_key 10 actor_port 1 actor_state 0x3f partner_sys 00:50:56:88:99:aa partner_key 10 partner_port 1 partner_state 0x3f wait 2ms
sw <- hv lacp actor_sys 00:50:56:88:99:aa actor_key 10 actor_port 1 actor_state 0x3f partner_sys 00:1c:73:11:22:33 partner_key 10 partner_port 1 partner_state 0x3f
}
compile {
lacp_bond_negotiation(tor_sw, linux_hv)
}

6. IEEE 802.1X Port-Based Network Access Control (EAPOL — IEEE 802.1X / RFC 3748)

Testing campus 802.1X Network Access Control (NAC) policies or debugging why an IoT supplicant failed EAP-TLS onboarding usually means wrestling with wpa_supplicant certificates and a live RADIUS/ISE lab. FrameAssembly lets you script the entire EAPOL conversation (EtherType = 0x888E, destination MAC 01:80:c2:00:00:03) between the Supplicant (client workstation or IoT endpoint) and the Authenticator (campus switch port or wireless AP)—from EAPOL-Start and Identity requests through EAP-TLS challenges, EAP-Success, and EAPOL-Logoff.

Default Wire Behavior

  • 4-Byte EAPOL Header: Protocol Version (default 1), Packet Type (0 = EAP_PACKET, 1 = START, 2 = LOGOFF, 3 = KEY), and 16-bit Body Length.
  • Nested RFC 3748 EAP Frame (when EAP_PACKET is selected):
  • For EAP_CODE 1 (Request) and EAP_CODE 2 (Response): Emits Code (1B) | Identifier (1B) | Length (2B) | Method/Type (1B) followed by IDENTITY string bytes and any PAYLOAD / HEXPAYLOAD.
  • For EAP_CODE 3 (Success) and EAP_CODE 4 (Failure) with no payload: Emits a strict 4-byte RFC 3748 §4.2 packet (Code | Identifier | Length = 0x0004) with no Method field.

EAPOL Modifiers

ModifierSyntaxValues & DescriptionDefault
802.1X VersionVERSION <1..3>1 (802.1X-2001), 2 (802.1X-2004), 3 (802.1X-2010/2020 MACsec)1
Packet TypeTYPE <START|LOGOFF|KEY|EAP_PACKET>Or bare START, LOGOFF, KEY, EAP_PACKETEAP_PACKET (0)
EAP CodeEAP_CODE <1..4>1 = Request, 2 = Response, 3 = Success, 4 = Failure1 (Request)
EAP IdentifierEAP_ID <0..255>8-bit correlation ID matching a Request to its Response1
EAP MethodEAP_METHOD <0..255>1 = Identity, 4 = MD5-Challenge, 13 = EAP-TLS, 21 = TTLS, 25 = PEAP1 (Identity)
User IdentityIDENTITY "<text>"Username or machine principal (e.g. "host/wkstn01.corp.example.com")Empty

Example: Complete 802.1X Supplicant Onboarding, EAP-TLS & Logoff

dot1x_port_authentication.fasm
host workstation { ip 10.50.10.25 mac 00:24:d7:11:22:33 }
host campus_sw { ip 10.50.10.1 mac 00:1c:73:80:21:10 }
flow dot1x_port_authentication(supp, auth_sw) {
// 1. Supplicant sends EAPOL-Start to 01:80:c2:00:00:03
supp -> auth_sw eapol version 2 type start wait 2ms
// 2. Switch requests EAP Identity (Code 1 = Request, Method 1 = Identity)
supp <- auth_sw eapol version 2 type eap_packet eap_code 1 eap_id 1 eap_method 1 wait 2ms
// 3. Supplicant responds with machine certificate identity (Code 2 = Response, Method 1 = Identity)
supp -> auth_sw eapol version 2 type eap_packet eap_code 2 eap_id 1 eap_method 1 identity "host/wkstn-25.corp.internal" wait 5ms
// 4. Switch initiates EAP-TLS (Method 13) challenge and Supplicant replies with TLS ClientHello fragment
supp <- auth_sw eapol version 2 type eap_packet eap_code 1 eap_id 2 eap_method 13 hexpayload "20" wait 2ms
supp -> auth_sw eapol version 2 type eap_packet eap_code 2 eap_id 2 eap_method 13 payload "EAP_TLS_CLIENT_CERT_VERIFY" wait 5ms
// 5. Switch sends 4-byte EAP-Success (Code 3) and EAPOL-Key descriptor
supp <- auth_sw eapol version 2 type eap_packet eap_code 3 eap_id 2 wait 2ms
supp <- auth_sw eapol version 2 type key hexpayload "02008a00100000000000000001" wait 10ms
// 6. Supplicant disconnects cleanly with EAPOL-Logoff
supp -> auth_sw eapol version 2 type logoff
}
compile {
dot1x_port_authentication(workstation, campus_sw)
}

7. Multiprotocol Label Switching (MPLS — RFC 3032 / RFC 5462)

Capturing real MPLS L3VPN or Segment Routing (SR-MPLS) packets normally requires a multi-router provider-edge (PE) lab with BGP label distribution and full core routing configured just to put a 4-byte shim header on the wire. In FrameAssembly, adding an MPLS label (EtherType = 0x8847)—complete with 20-bit Label ID, 3-bit QoS Traffic Class (EXP/TC), Bottom-of-Stack (BOS) bit, and independent MPLS_TTL—takes a single modifier on any packet statement:

text
 0                   1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                Label                  | EXP |S|       TTL     |
|               (20 bits)               |(3b) |1|    (8 bits)   |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

Like VLAN, MPLS can be used both as a standalone statement (a -> b mpls 16001) and as a cross-layer modifier wrapping any L3/L4/L7 protocol (ICMP, OSPF, BGP, TCP, UDP, HTTP, SNMP3, etc.), optionally combined with outer VLAN or QINQ tags.

MPLS Modifiers & Reserved Labels

ModifierSyntax & AliasesRangeWire Field & DescriptionDefault
MPLS LabelMPLS <0..1048575> or LABEL <0..1048575>0..104857520-bit MPLS Label (0 = IPv4 Explicit Null, 2 = IPv6 Explicit Null, 3 = Implicit Null, 16+ = L3VPN / SR-MPLS)0
Traffic ClassEXP <0..7> or TC <0..7>0..73-bit RFC 5462 Traffic Class (formerly Experimental) QoS bits0
Bottom of StackBOS <0|1>0..11-bit Bottom-of-Stack indicator (1 = last label before IP header)1
MPLS Shim TTLMPLS_TTL <0..255>0..2558-bit MPLS Time-To-Live (independent of inner IP TTL)64

Example: Service Provider MPLS L3VPN & VLAN-Subinterface Forwarding

mpls_l3vpn_and_sr_forwarding.fasm
host pe_west { ip 10.100.1.1 mac 00:1c:73:ce:00:01 }
host pe_east { ip 10.100.1.2 mac 00:1c:73:ce:00:02 }
flow mpls_l3vpn_and_sr_forwarding(west, east) {
// 1. MPLS-labeled ICMPv4 echo across an L3VPN (Label 24010, EXP/TC 5 for Voice/EF, MPLS TTL 64, Inner IP TTL 63)
west -> east mpls 24010 exp 5 bos 1 mpls_ttl 64 ttl 63 dscp 46 icmp echo_request id 501 seq 1 payload "L3VPN_PING" wait 1ms
west <- east mpls 24020 tc 5 bos 1 mpls_ttl 63 ttl 62 dscp 46 icmp echo_reply id 501 seq 1 payload "L3VPN_PONG" wait 2ms
// 2. 802.1Q VLAN 400 subinterface carrying an SR-MPLS labeled HTTPS flow
west -> east vlan 400 pcp 5 mpls 16005 exp 5 bos 1 mpls_ttl 255 tcp syn srcport 51200 dstport 443 wait 1ms
west <- east vlan 400 pcp 5 mpls 16001 exp 5 bos 1 mpls_ttl 254 tcp syn ack
}
compile {
mpls_l3vpn_and_sr_forwarding(pe_west, pe_east)
}

Inspect with tshark:

console
$ frameassembly mpls_test.fasm
$ tshark -r mpls_test.pcap -Y "mpls" -T fields -e frame.number -e vlan.id -e mpls.label -e mpls.exp -e mpls.bottom -e mpls.ttl -e ip.ttl