Repository navigation
Conversation
added 4 commits
October 20, 2026 15:04
Frames carrying an 802.2 LLC header are not Ethernet II: the type/length field holds a length, not an EtherType. Without an llc header the only way to express one is to hand-compute the length and pass the whole LLC payload as an opaque hex blob: eth ... et 0x0026 data hex 4242030000... which cannot be matched per field and has to be recomputed by hand whenever anything above it changes size. llc_fill_defaults sums its own 3 bytes plus every header above it and writes the result into the "et" field of the header below, following ipv4_fill_defaults for the summation and ef-icmp.c for reaching one step down the stack. That covers a VLAN tag between eth and llc too, since the tag carries the same field name last. Nothing was needed to stop eth_fill_defaults from clobbering the length: frame_to_buf runs frame_fill_defaults from the top of the stack downwards, so by the time ether_type_fill_defaults looks at "et" the value is already set and it returns early. An explicit "et" on the command line still wins, for the same reason. A computed length above 1500 is refused rather than written. 802.3 reads 1536 and up as an EtherType, so "llc stp data repeat 1500 0" would otherwise emit 0x0602 and a receiver would misparse it, and past 65535 the value would not fit parse_bytes(buf, 2) at all. An explicit "et" still wins, so a test can build the malformed frame on purpose. DSAP, SSAP and control default to the STP SAPs (0x42/0x42/0x03), the only current user.
IEEE 802.1D-1998 clause 9.3 Bridge Protocol Data Units, on top of the
llc header added in the previous commit:
ef hex eth dmac 01:80:c2:00:00:00 smac 00:11:22:33:44:55 \
llc stp root_prio 0x8000 tc 1
Configuration and TCN are two header templates rather than one header
with the trailing fields suppressed. The shrink-the-header trick in
ef-sv.c and ef-mld.c drops a trailing optional part of one message; a
TCN is a different message whose only content is the four-byte common
part, and suppressing 31 of 35 fields would leave every config field
name accepted on a TCN command line and silently ignored. The MRP
precedent of one template per message type is the better fit.
Each bridge identifier is split into its priority and MAC halves
(root_prio/root_mac, bridge_prio/bridge_mac) so either can be asserted
on its own.
The four timer fields are exposed as their raw 16-bit wire value rather
than converted to seconds, so a test can write the exact octets; the
unit is in each field's help text. Their defaults, and those of the
bridge identifiers, describe a root bridge advertising the table 8-3
recommended timers.
tc and tcack are 1-bit fields aliased onto bit 0 and bit 7 of flags via
explicit bit_offset, so either the whole byte or a single flag can be
set. Under rx matching the two compose: "flags ign tc 1" masks the byte
down to bit 0 and still requires it set.
Only bit 0 (tc) and bit 7 (tcack) were named. The six bits between them are what distinguishes the rapid protocol from classic STP -- they carry the proposal/agreement handshake of 802.1Q clause 13.35 and the sender's port role -- and none of that could be expressed except by spelling out the whole byte: ef hex eth llc stp flags 0x3e which says nothing about which bits matter. The eight fields of 802.1Q table 13-5 are now all named: proposal, role, learning, forwarding and agreement alongside the existing two. role is two bits wide rather than two more one-bit aliases, since the pair is a single enumerated value: 0 unknown, 1 alternate or backup, 2 root, 3 designated. Every alias carries rx_match_skip so that it only enters the receive mask when it is named. Without it an alias is masked in by default, and "flags ign" cannot narrow the byte: it already produced a mask of 0x81 rather than the 0x01 its own test comment described, and with six more aliases would have produced 0xff, making the "ign" useless. The assertion is corrected to the value the comment always claimed. rx_match_skip alone would make "tc ign" a no-op: an ignored field writes nothing to the mask, so the flags byte around it would still be compared in full and a BPDU differing only in tc rejected. The aliases are therefore marked as overlays, and an overlay given "ign" (tracked by a new rx_match_ign) writes zeros over its bits once the rest of the mask is built. "tc ign" compares every flag bit but tc, and "flags ign agreement 1" still compares agreement alone. Only overlays do this. IFH headers overlap fields too, but as alternative encodings of the same bits, and there an ignored field must not erase the mask of one that is compared: "ifh-oc1 ign b1-rew-op 0 b0-masq-port ign" keeps b1-rew-op compared in full. Every header without overlay fields gets the mask it had before, byte for byte.
The rapid protocol's own BPDU could not be built. Setting "ver 2" on the
stp header changed the version octet and nothing else, leaving a 35-octet
body where 802.1Q clause 14.4 requires 36 -- the configuration BPDU's 35
followed by the Version 1 Length, transmitted as 0. The receive rule in
that clause is version 2, type 0x02 and 36 or more octets, so what the
stp header produced was discarded as malformed by anything implementing
it:
ef hex eth dmac 01:80:c2:00:00:00 smac 00:11:22:33:44:55 \
llc stp_rst role 3 proposal 1 learning 1 forwarding 1
A third template rather than a longer HDR_STP, for two reasons. A
permanently 36-byte HDR_STP would make every configuration BPDU
malformed instead. Growing it on demand from a frame_fill_defaults hook
is not available either: a hook can shrink a header, which is what
ef-sv.c does to drop a trailing TLV, but frame_mask_to_buf() never runs
those hooks, so the value buf and the mask buf would disagree on the
header's size and rx matching would write past the shorter one. The
existing argument for keeping stp and stp_tcn apart therefore carries
over unchanged, and the MRP precedent of one template per message type
now holds for three.
The field list is duplicated rather than shared, following ef-mrp.c and
ef-oam.c, where the common fields of four and five templates are spelled
out in each. A shared array cannot work here regardless: def_offset()
assigns bit_offset per header, so one array behind two templates would
have the second pass overwrite the first's offsets.
ver and type default to 2 and 0x02, and v1_len to 0; everything else
carries the same defaults as the configuration BPDU, so the two differ
on the wire only where the standard says they should.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds 802.2 LLC framing and the three spanning-tree BPDUs, so STP and RSTP frames can be built and matched field by field instead of as opaque hex.
Headers
llc: DSAP/SSAP/control, defaulting to the STP SAPs (0x42/0x42/0x03). It writes the 802.3 length into theetfield of the header below it, eth or the outermost VLAN tag. A computed length above 1500 is refused; an explicitetstill wins.stp: 802.1D configuration BPDU, 35 octets.stp_rst: 802.1Q clause 14.4 RST BPDU, 36 octets: the configuration body plus Version 1 Length.stp ver 2cannot stand in for it, because it leaves the body one octet short and a receiver discards it as malformed.stp_tcn: topology change notification, 4 octets.The three BPDUs are separate templates, following the MRP precedent of one template per message type. The commit messages give the reasons.
Flags octet
All eight bits of 802.1Q table 13-5 are named:
tc,proposal,role(2 bits),learning,forwarding,agreement,tcack. The whole byte is still available asflags.For rx matching the bit fields compose with
flags:flags ign agreement 10x40tc ign0xferole ign0xf3Change to shared code
tc ignneeded a change to the mask builder insrc/ef.c.field_tgains two members:overlaymarks a field that is a view of bits another field already covers, andrx_match_ignrecords that a field was givenign. After the existing mask loop, which is unchanged, an ignored overlay writes zeros over its own bits.Only the 14 STP/RST flag aliases set
overlay, so every other header's mask is unchanged. IFH headers also overlap fields, but as alternative encodings, so they must not get this behaviour. A test pinsifh-oc1 ign b1-rew-op 0 b0-masq-port ignto its current mask.Testing
test/test-llc.cxx, andtest/test-stp.cxxwith byte-for-byte BPDU layouts, every flag bit, rx masks and the IFH case.cteston its own; the last passes 643 assertions.master.Not covered: MST BPDUs (802.1Q clause 14.6, version 3).