Skip to content

netprotocols 1.3.0

Choose a tag to compare

@EONRaider EONRaider released this 01 Sep 21:16
6e43d94

Added

  • IEEE 802.1Q VLAN tags (VLAN, 802.1Q-2018 §9.6 / 802.1ad QinQ):
    single and stacked (QinQ 0x88A8, legacy double-tagged 0x9100)
    tags decode as one layer per tag; the Tag Control Information word is
    split into pcp/dei/vid dataclass fields validated in
    __post_init__ (InvalidFieldError), with the packed 16-bit view
    kept as the tci property; VLAN is registered in the
    property-based fuzz suite and the EtherType display names are
    covered by the enum completeness test.
  • IGMPv3 group records (IGMPv3GroupRecord, RFC 3376 §4.2): a v3
    Membership Report (type 0x22) now parses its group-record array on
    demand. IGMP.group_records yields one IGMPv3GroupRecord per record
    (record type + display name, multicast group, source-address list,
    raw auxiliary data) and IGMP.num_group_records reads the count;
    other message types return None. Parsing reads the raw body and
    never re-encodes, so the byte-exact round-trip is preserved, and a
    lying record/source count or a truncated record raises
    InvalidFieldError (bounded — never hangs or over-reads).
  • IGMPv3 query fields (RFC 3376 §4.1): a v3 Membership Query (type
    0x11) now exposes its fields past the group address — the s_flag
    (suppress router-side processing), qrv, qqic, and
    query_source_addresses accessors parse the raw body on demand. A v2
    (8-byte) query and non-query types return None, and a source count
    that runs past the message raises InvalidFieldError.
  • DHCP (netprotocols.DHCP, RFC 2131/2132) — the fixed BOOTP header
    (op, xid, the client/your/server/gateway addresses, the 16-byte client
    hardware address, and the server-name / boot-file fields) decodes in
    full; the magic cookie and TLV options are kept raw and parsed on
    demand. option_map walks the options (concatenating a value split
    across appearances, RFC 3396) and message_type / message_type_name
    read the DHCP message type (option 53). Dispatched from UDP by
    well-known port (67/68); terminal, with a byte-exact round-trip and
    the same bounded-parse contract as DNS (InvalidFieldError on a
    missing cookie or an option that overruns the buffer).
  • GRE (netprotocols.GRE, RFC 2784/2890) — Generic Routing
    Encapsulation, dispatched from IPv4/IPv6 protocol 47. The four-byte
    header (flags/version + protocol type) decodes with the optional
    checksum, key, and sequence-number fields that the flag bits announce
    kept raw and surfaced through accessors. The payload chains onward by
    the protocol_type EtherType, so a GRE-tunnelled IPv4/IPv6 packet
    keeps decoding; the round-trip stays byte-exact regardless of which
    optional fields are present.
  • GRE checksum arm (netprotocols.checksum, RFC 2784 §2.5):
    compute/verify now cover GRE — the internet checksum over the GRE
    header plus its payload with the checksum field zeroed and no
    pseudo-header. compute(gre, payload=...) requires the
    Checksum-Present bit (and its field) and raises InvalidFieldError
    otherwise; verify of a header whose Checksum-Present bit is clear
    returns True — a frame cannot fail a checksum it does not carry —
    mirroring the UDP-over-IPv4 zero rule.
  • Richer address accessors (README roadmap): read-only _address
    properties return stdlib ipaddress objects alongside the canonical
    str fields, for comparison, subnet membership, and arithmetic —
    IPv4.src_address/dst_address and ARP.spa_address/tpa_address
    as ipaddress.IPv4Address, IPv6.src_address/dst_address as
    ipaddress.IPv6Address, and the four DHCP address fields as
    ciaddr_address/yiaddr_address/siaddr_address/giaddr_address.
    Purely additive: the str fields stay the round-tripping
    representation. MAC addresses stay str — the stdlib has no EUI
    type.
  • IPv4 options (IPv4Option, RFC 791 §3.1): the options TLV list
    now parses on demand, mirroring the TCP-option work —
    IPv4.parsed_options yields one IPv4Option per option in wire
    order (kind + kind_name, raw data). The common kinds are named:
    End of Option List and No-Operation (single-byte; EOL ends the parse,
    so padding after it is not returned), Record Route (7), Timestamp
    (68), and Router Alert (148, RFC 2113); unknown kinds keep their raw
    data with a numeric fallback name. Parsing reads the raw options
    bytes and never re-encodes, so the byte-exact round-trip is
    preserved; an option length below the 2-byte minimum or one that runs
    past the options raises InvalidFieldError (bounded — never hangs or
    over-reads).
  • DNS resource records (DNSResourceRecord, RFC 1035 §4.1.3): the
    answer, authority, and additional sections parse on demand —
    DNS.answers / DNS.authorities / DNS.additionals yield records
    (name, type + rtype_name, class, TTL, raw rdata, and a decoded
    rdata_text: IPv4/IPv6 for A/AAAA, the target name for CNAME/NS/PTR,
    and MX/TXT/SOA; hexadecimal otherwise). Parsing reads the raw sections
    and never re-encodes, so the byte-exact round-trip holds; a record or
    compressed name that runs past the message raises InvalidFieldError.
  • TCP options (TCPOption, RFC 9293 §3.1): the options TLV list
    now parses on demand — TCP.parsed_options yields one TCPOption
    per option in wire order (kind + kind_name, raw data, and a
    decoded value where this library understands the kind: the segment
    size for MSS, the shift count for Window Scale, a (tsval, tsecr)
    pair for Timestamps, and (left_edge, right_edge) pairs for SACK;
    RFC 7323/2018). EOL and NOP are single-byte (EOL ends the parse);
    unknown kinds keep their raw data with value degrading to None.
    Parsing reads the raw options bytes and never re-encodes, so the
    byte-exact round-trip is preserved, and an option length below the
    TLV minimum or one that runs past the options raises
    InvalidFieldError (bounded — never hangs or over-reads).
  • ICMP message bodies (RFC 792 / RFC 4443): ICMPv4/ICMPv6 gain a
    raw body field — the message data after the 8-byte header — so a
    decoded message is self-contained (like IGMP/DNS), header_len
    consumes the whole IP payload, and the layer stays terminal with a
    byte-exact round-trip. Echo requests/replies (v4 types 8/0, v6
    128/129) expose identifier / sequence_number split from rest,
    and the error messages (v4 Destination Unreachable / Redirect / Time
    Exceeded / Parameter Problem; v6 types 1-4) expose embedded_packet
    the invoking datagram, decodable as IPv4/IPv6. The accessors read
    on demand and degrade to None for other message types or an empty
    body, never raising. A decoded message now carries its body in
    bytes(layer), so checksum.compute/verify need no separate
    payload for it (passing one for a header-only object still works).
  • IPv6 Neighbor Discovery (NDPOption, RFC 4861): ICMPv6 now
    parses NDP messages on demand — ndp_target_address reads the
    16-byte target of a Neighbor Solicitation/Advertisement (135/136),
    and ndp_options walks the option TLVs of Router
    Solicitation/Advertisement, Neighbor Solicitation/Advertisement, and
    Redirect at each message's own options offset. Each NDPOption
    carries its type + type_name and raw data; a Source/Target
    Link-Layer Address option (1/2) reads back as a MAC string via
    link_layer_address, and Prefix Information (3) / MTU (5) stay raw.
    Non-NDP message types return None. Option lengths count in 8-octet
    units: a zero length (which must not loop), a length past the
    message, or a body shorter than the message's fixed fields raises
  • IPv6 extension-header options (IPv6Option, RFC 8200 §4.2): the
    Hop-by-Hop and Destination Options headers now parse their option
    TLVs on demand — parsed_options yields one IPv6Option per option
    in wire order, padding included (Pad1 is a lone type byte; everything
    else is type/length/data). Each option carries its type +
    type_name (Pad1, PadN, Router Alert per RFC 2711, Jumbo Payload per
    RFC 2675; unknown types keep their numeric value) and raw data, and
    exposes the action-on-unrecognized bits (the two high bits of the
    type) as unrecognized_action. Parsing reads the raw options bytes
    and never re-encodes, so the byte-exact round-trip is preserved; a
    missing length byte or option data that runs past the header raises
    InvalidFieldError (bounded — never hangs or over-reads).
  • DNS over TCP (DNSOverTCP, RFC 1035 §4.2.2): TCP.next_protocol()
    now dispatches application protocols by well-known port
    (layer4._ports.tcp_app_class). DNS over TCP is length-prefixed, so
    port 53 chains through a 2-byte DNSOverTCP length shim — a layer
    between TCP and the DNS message, like a VLAN tag between Ethernet
    and its payload — and the walk reaches the DNS message (records and
    all) at its true offset. Non-DNS ports still end the chain at TCP.

Development

  • The real-capture fixture corpus gained vlan_icmp.pcap (802.1Q
    single tag VID 100 + 802.1ad QinQ S-VID 200 / C-VID 30, over ARP and
    ICMPv4), so the VLAN layer rides the corpus-wide invariants and the
    transport-checksum recompute like every other protocol. It is a
    direct tcpdump capture of real kernel-tagged frames — built over
    802.1Q / 802.1ad vlan devices on a veth pair with VLAN and checksum
    offload disabled so the tags stay in-band in the saved bytes and the
    inner checksums are genuine kernel output; see
    tests/fixtures/MANIFEST.md and scripts/capture_fixtures_vlan.sh.
  • The corpus gained real-capture dhcp.pcap (a DHCP DORA exchange from
    dnsmasq + dhclient) and gre.pcap (IPv4-in-GRE ICMPv4 echo over a
    plain and a keyed kernel tunnel), so the DHCP and GRE layers ride
    the corpus-wide invariants on genuine bytes.
    scripts/capture_fixtures_dhcp.sh and scripts/capture_fixtures_gre.sh
    produce them, and scripts/check_fixtures.py now peels a GRE header to
    verify the tunnelled inner IPv4/ICMP checksums.
  • The corpus gained real-capture dns_tcp.pcap (an A and a TXT query +
    response from dig +tcp against dnsmasq over a veth pair, offload
    disabled), so the TCP application dispatch and the DNSOverTCP
    length shim (#57) ride the corpus-wide invariants and the
    TCP-checksum recompute on genuine bytes — every frame decodes
    Ethernet→IPv4→TCP→DNSOverTCP→DNS with the length prefix agreeing
    with the message it frames and a resolvable answer.
    scripts/capture_fixtures_dns_tcp.sh produces it; the corpus is now
    97 frames across 17 scenarios.
  • Contributor documentation: a CONTRIBUTING.md (dev setup, the QA
    ladder, the decode contract, the add-a-protocol enforcement points,
    and the fixture-capture workflow), a pull-request template, and
    bug-report / protocol-request issue templates under .github/.
  • README refresh: the Protocol coverage table gains the shipped DHCP
    and GRE rows, the DNS row notes resource-record parsing and the
    DNS-over-TCP length shim, and the IGMP row notes the IGMPv3 report
    and query parsing. The Roadmap section now lists only the remaining
    items, linking the open decoder-depth issues (#59-#66, tracked by
    #67), and the stale corpus figure is corrected to 93 frames across 16
    scenarios here and in ARCHITECTURE.md.