netprotocols 2.4.0__tar.gz → 2.6.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {netprotocols-2.4.0 → netprotocols-2.6.0}/ARCHITECTURE.md +354 -50
- {netprotocols-2.4.0 → netprotocols-2.6.0}/CHANGELOG.md +52 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/CONTRIBUTING.md +12 -7
- {netprotocols-2.4.0 → netprotocols-2.6.0}/PKG-INFO +65 -30
- {netprotocols-2.4.0 → netprotocols-2.6.0}/README.md +64 -29
- {netprotocols-2.4.0 → netprotocols-2.6.0}/SECURITY.md +12 -0
- netprotocols-2.6.0/benchmarks/baseline.json +13 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/docs/CLAIMS.md +361 -36
- {netprotocols-2.4.0 → netprotocols-2.6.0}/pyproject.toml +2 -2
- netprotocols-2.6.0/scripts/capture_fixtures_bgp.sh +495 -0
- netprotocols-2.6.0/scripts/capture_fixtures_llc.sh +348 -0
- netprotocols-2.6.0/scripts/capture_fixtures_lldp.sh +313 -0
- netprotocols-2.6.0/scripts/capture_fixtures_mpls.sh +259 -0
- netprotocols-2.6.0/scripts/capture_fixtures_ospf.sh +348 -0
- netprotocols-2.6.0/scripts/capture_fixtures_pim.sh +373 -0
- netprotocols-2.6.0/scripts/capture_fixtures_rip.sh +285 -0
- netprotocols-2.6.0/scripts/capture_fixtures_vrrp.sh +349 -0
- netprotocols-2.6.0/scripts/check_fixtures.py +1497 -0
- netprotocols-2.6.0/scripts/llc_fixture_frames.py +189 -0
- netprotocols-2.6.0/scripts/mpls_fixture_packets.py +253 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/src/netprotocols/__init__.py +91 -2
- {netprotocols-2.4.0 → netprotocols-2.6.0}/src/netprotocols/_base.py +89 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/src/netprotocols/_defaults.py +90 -7
- netprotocols-2.6.0/src/netprotocols/_enums.py +180 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/src/netprotocols/_tlv.py +58 -36
- netprotocols-2.6.0/src/netprotocols/checksum.py +565 -0
- netprotocols-2.6.0/src/netprotocols/layer2/__init__.py +26 -0
- netprotocols-2.6.0/src/netprotocols/layer2/cdp.py +642 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/src/netprotocols/layer2/ethernet.py +87 -6
- netprotocols-2.6.0/src/netprotocols/layer2/llc.py +481 -0
- netprotocols-2.6.0/src/netprotocols/layer2/lldp.py +522 -0
- netprotocols-2.6.0/src/netprotocols/layer2/mpls.py +392 -0
- netprotocols-2.6.0/src/netprotocols/layer2/stp.py +634 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/src/netprotocols/layer2/vlan.py +45 -4
- netprotocols-2.6.0/src/netprotocols/layer3/__init__.py +76 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/src/netprotocols/layer3/ip.py +12 -11
- netprotocols-2.6.0/src/netprotocols/layer3/ospf.py +971 -0
- netprotocols-2.6.0/src/netprotocols/layer3/pim.py +1025 -0
- netprotocols-2.6.0/src/netprotocols/layer3/vrrp.py +377 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/src/netprotocols/layer7/__init__.py +14 -0
- netprotocols-2.6.0/src/netprotocols/layer7/bgp.py +1162 -0
- netprotocols-2.6.0/src/netprotocols/layer7/rip.py +570 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/src/netprotocols/packet.py +9 -2
- {netprotocols-2.4.0 → netprotocols-2.6.0}/src/netprotocols/registry.py +141 -16
- {netprotocols-2.4.0 → netprotocols-2.6.0}/src/netprotocols/utils/exceptions.py +14 -11
- {netprotocols-2.4.0 → netprotocols-2.6.0}/src/netprotocols/walk.py +87 -25
- {netprotocols-2.4.0 → netprotocols-2.6.0}/tests/conftest.py +113 -0
- netprotocols-2.6.0/tests/fixtures/MANIFEST.md +635 -0
- netprotocols-2.6.0/tests/fixtures/bgp/bgp_session.pcap +0 -0
- netprotocols-2.6.0/tests/fixtures/bgp.pcap +0 -0
- netprotocols-2.6.0/tests/fixtures/llc.pcap +0 -0
- netprotocols-2.6.0/tests/fixtures/lldp.pcap +0 -0
- netprotocols-2.6.0/tests/fixtures/mpls/mpls_snaplen.pcap +0 -0
- netprotocols-2.6.0/tests/fixtures/mpls.pcap +0 -0
- netprotocols-2.6.0/tests/fixtures/ospf.pcap +0 -0
- netprotocols-2.6.0/tests/fixtures/pim.pcap +0 -0
- netprotocols-2.6.0/tests/fixtures/rip.pcap +0 -0
- netprotocols-2.6.0/tests/fixtures/staging/bgp/bgp_session.pcap +0 -0
- netprotocols-2.6.0/tests/fixtures/staging/bgp.pcap +0 -0
- netprotocols-2.6.0/tests/fixtures/staging/llc.pcap +0 -0
- netprotocols-2.6.0/tests/fixtures/staging/lldp.pcap +0 -0
- netprotocols-2.6.0/tests/fixtures/staging/mpls/mpls_snaplen.pcap +0 -0
- netprotocols-2.6.0/tests/fixtures/staging/mpls.pcap +0 -0
- netprotocols-2.6.0/tests/fixtures/staging/ospf.pcap +0 -0
- netprotocols-2.6.0/tests/fixtures/staging/pim.pcap +0 -0
- netprotocols-2.6.0/tests/fixtures/staging/rip.pcap +0 -0
- netprotocols-2.6.0/tests/fixtures/staging/vrrp.pcap +0 -0
- netprotocols-2.6.0/tests/fixtures/vrrp.pcap +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/tests/strategies.py +449 -0
- netprotocols-2.6.0/tests/test_bgp.py +1283 -0
- netprotocols-2.6.0/tests/test_cdp.py +922 -0
- netprotocols-2.6.0/tests/test_checksum.py +1456 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/tests/test_contract.py +110 -5
- {netprotocols-2.4.0 → netprotocols-2.6.0}/tests/test_corpus.py +22 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/tests/test_docs.py +96 -1
- netprotocols-2.6.0/tests/test_enums.py +46 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/tests/test_ethernet.py +4 -2
- netprotocols-2.6.0/tests/test_fuzz.py +1531 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/tests/test_ipv6_ext.py +6 -1
- netprotocols-2.6.0/tests/test_llc.py +812 -0
- netprotocols-2.6.0/tests/test_lldp.py +819 -0
- netprotocols-2.6.0/tests/test_mpls.py +1012 -0
- netprotocols-2.6.0/tests/test_ospf.py +1219 -0
- netprotocols-2.6.0/tests/test_pim.py +1273 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/tests/test_registry.py +160 -12
- netprotocols-2.6.0/tests/test_rip.py +767 -0
- netprotocols-2.6.0/tests/test_stp.py +1139 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/tests/test_tlv.py +225 -0
- netprotocols-2.6.0/tests/test_vrrp.py +707 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/uv.lock +1 -1
- netprotocols-2.4.0/benchmarks/baseline.json +0 -13
- netprotocols-2.4.0/scripts/check_fixtures.py +0 -753
- netprotocols-2.4.0/src/netprotocols/_enums.py +0 -103
- netprotocols-2.4.0/src/netprotocols/checksum.py +0 -255
- netprotocols-2.4.0/src/netprotocols/layer2/__init__.py +0 -5
- netprotocols-2.4.0/src/netprotocols/layer3/__init__.py +0 -33
- netprotocols-2.4.0/tests/fixtures/MANIFEST.md +0 -260
- netprotocols-2.4.0/tests/test_checksum.py +0 -602
- netprotocols-2.4.0/tests/test_enums.py +0 -22
- netprotocols-2.4.0/tests/test_fuzz.py +0 -646
- {netprotocols-2.4.0 → netprotocols-2.6.0}/.claude/skills/steward/SKILL.md +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/.gitattributes +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/.github/CODEOWNERS +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/.github/ISSUE_TEMPLATE/protocol_request.yml +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/.github/dependabot.yml +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/.github/pull_request_template.md +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/.github/workflows/ci.yml +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/.github/workflows/codeql.yml +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/.github/workflows/docs-lint.yml +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/.github/workflows/fuzz.yml +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/.github/workflows/mutation.yml +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/.github/workflows/pr-title-lint.yml +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/.github/workflows/release.yml +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/.github/workflows/scorecard.yml +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/.gitignore +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/LICENSE +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/scripts/_pcap.py +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/scripts/benchmark.py +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/scripts/benchmark_encode.py +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/scripts/benchmark_import.py +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/scripts/capture_fixtures.sh +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/scripts/capture_fixtures_dhcp.sh +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/scripts/capture_fixtures_dns_tcp.sh +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/scripts/capture_fixtures_gre.sh +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/scripts/capture_fixtures_http.sh +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/scripts/capture_fixtures_igmp.sh +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/scripts/capture_fixtures_ipip.sh +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/scripts/capture_fixtures_ipsec.sh +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/scripts/capture_fixtures_sctp.sh +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/scripts/capture_fixtures_supplement.sh +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/scripts/capture_fixtures_vlan.sh +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/scripts/capture_fixtures_vxlan.sh +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/scripts/check_mutmut_survivors.py +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/scripts/extract_http_streams.py +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/scripts/ipsec_fixture_packets.py +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/scripts/mutmut_survivors.txt +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/scripts/pyodide/check_in_pyodide.py +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/scripts/pyodide/package-lock.json +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/scripts/pyodide/package.json +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/scripts/pyodide/run_in_pyodide.mjs +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/src/netprotocols/flow.py +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/src/netprotocols/layer2/arp.py +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/src/netprotocols/layer3/ah.py +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/src/netprotocols/layer3/esp.py +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/src/netprotocols/layer3/gre.py +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/src/netprotocols/layer3/icmp.py +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/src/netprotocols/layer3/igmp.py +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/src/netprotocols/layer3/ipv6_ext.py +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/src/netprotocols/layer4/__init__.py +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/src/netprotocols/layer4/_ports.py +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/src/netprotocols/layer4/sctp.py +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/src/netprotocols/layer4/tcp.py +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/src/netprotocols/layer4/udp.py +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/src/netprotocols/layer7/dhcp.py +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/src/netprotocols/layer7/dns.py +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/src/netprotocols/layer7/http.py +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/src/netprotocols/layer7/vxlan.py +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/src/netprotocols/pcap.py +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/src/netprotocols/py.typed +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/src/netprotocols/utils/__init__.py +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/src/netprotocols/utils/ipv4.py +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/src/netprotocols/utils/mac.py +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/tests/fixtures/ah.pcap +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/tests/fixtures/arp_exchange.pcap +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/tests/fixtures/dhcp.pcap +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/tests/fixtures/dns_tcp.pcap +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/tests/fixtures/esp.pcap +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/tests/fixtures/gre.pcap +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/tests/fixtures/http/01.request +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/tests/fixtures/http/01.response +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/tests/fixtures/http/02.request +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/tests/fixtures/http/02.response +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/tests/fixtures/http/03.request +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/tests/fixtures/http/03.response +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/tests/fixtures/http/04.request +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/tests/fixtures/http/04.response +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/tests/fixtures/http/05.request +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/tests/fixtures/http/05.response +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/tests/fixtures/http/06.request +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/tests/fixtures/http/06.response +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/tests/fixtures/http/07.request +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/tests/fixtures/http/07.response +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/tests/fixtures/http/http.pcap +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/tests/fixtures/icmpv4_echo_lo.pcap +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/tests/fixtures/icmpv4_external.pcap +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/tests/fixtures/icmpv4_ttl_exceeded.pcap +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/tests/fixtures/icmpv6_echo_lo.pcap +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/tests/fixtures/igmp.pcap +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/tests/fixtures/ipip.pcap +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/tests/fixtures/ipv4_fragments.pcap +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/tests/fixtures/ipv6_fragments.pcap +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/tests/fixtures/ipv6_mld.pcap +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/tests/fixtures/ipv6_ndp_mld.pcap +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/tests/fixtures/sctp.pcap +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/tests/fixtures/staging/ah.pcap +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/tests/fixtures/staging/arp_exchange.pcap +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/tests/fixtures/staging/dhcp.pcap +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/tests/fixtures/staging/dns_tcp.pcap +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/tests/fixtures/staging/esp.pcap +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/tests/fixtures/staging/gre.pcap +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/tests/fixtures/staging/http.pcap +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/tests/fixtures/staging/icmpv4_echo_lo.pcap +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/tests/fixtures/staging/icmpv4_external.pcap +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/tests/fixtures/staging/icmpv4_ttl_exceeded.pcap +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/tests/fixtures/staging/icmpv6_echo_lo.pcap +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/tests/fixtures/staging/igmp.pcap +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/tests/fixtures/staging/ipip.pcap +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/tests/fixtures/staging/ipv4_fragments.pcap +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/tests/fixtures/staging/ipv6_fragments.pcap +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/tests/fixtures/staging/ipv6_mld.pcap +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/tests/fixtures/staging/ipv6_ndp_mld.pcap +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/tests/fixtures/staging/sctp.pcap +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/tests/fixtures/staging/tcp_http.pcap +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/tests/fixtures/staging/tcp_https.pcap +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/tests/fixtures/staging/udp_dns.pcap +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/tests/fixtures/staging/vlan_icmp.pcap +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/tests/fixtures/staging/vxlan.pcap +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/tests/fixtures/tcp_http.pcap +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/tests/fixtures/tcp_https.pcap +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/tests/fixtures/udp_dns.pcap +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/tests/fixtures/vlan_icmp.pcap +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/tests/fixtures/vxlan.pcap +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/tests/fuzz/fuzz_decode_frame.py +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/tests/fuzz/fuzz_http_decode.py +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/tests/test_ah.py +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/tests/test_arp.py +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/tests/test_dhcp.py +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/tests/test_diagnostics.py +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/tests/test_dns.py +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/tests/test_esp.py +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/tests/test_exports.py +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/tests/test_extract_http_streams.py +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/tests/test_flow.py +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/tests/test_gre.py +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/tests/test_http.py +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/tests/test_icmp.py +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/tests/test_igmp.py +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/tests/test_ip.py +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/tests/test_ipip.py +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/tests/test_mutmut_gate.py +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/tests/test_packet.py +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/tests/test_pattern_matching.py +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/tests/test_pcap.py +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/tests/test_sctp.py +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/tests/test_tcp.py +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/tests/test_udp.py +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/tests/test_utils.py +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/tests/test_version.py +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/tests/test_vlan.py +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/tests/test_vxlan.py +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/tests/test_walk.py +0 -0
- {netprotocols-2.4.0 → netprotocols-2.6.0}/tests/test_workflows.py +0 -0
|
@@ -140,7 +140,10 @@ the same three rules (spelled out in [`_base.py`](src/netprotocols/_base.py)):
|
|
|
140
140
|
1. **It receives the entire remaining buffer** — everything from the
|
|
141
141
|
start of its header to the end of the captured frame — and parses
|
|
142
142
|
the fixed portion with `_struct.unpack_from`. A buffer too short
|
|
143
|
-
for the fixed portion raises `TruncatedHeaderError`.
|
|
143
|
+
for the fixed portion raises `TruncatedHeaderError`. One enclosing
|
|
144
|
+
length shortens that buffer in a walk: after an Ethernet or VLAN
|
|
145
|
+
header holding an IEEE 802.3 Length, every later layer's buffer ends
|
|
146
|
+
where the Length ends the LLC PDU (see *802.3 framing* below).
|
|
144
147
|
2. **Variable-length headers finish the job themselves.** IPv4 reads
|
|
145
148
|
its IHL and TCP reads its data offset, sanity-checks it (5–15, and
|
|
146
149
|
the declared bytes must exist in the buffer — a header that *lies*
|
|
@@ -151,7 +154,7 @@ the same three rules (spelled out in [`_base.py`](src/netprotocols/_base.py)):
|
|
|
151
154
|
how many bytes this header consumed — a *property*, so IPv4 answers
|
|
152
155
|
`ihl * 4` and TCP answers `data_offset * 4`.
|
|
153
156
|
|
|
154
|
-
**
|
|
157
|
+
**Three protocols vary this.** The first is text framing. `HTTP` has no
|
|
155
158
|
fixed portion to unpack: `decode()` scans for the empty line that ends
|
|
156
159
|
the head, then for the end the head's framing gives the body, so rule 1
|
|
157
160
|
is a scan rather than an unpack and `header_len` covers the whole
|
|
@@ -185,6 +188,113 @@ read as a chunk header) parses as a phantom chunk, so `verify()` is the
|
|
|
185
188
|
reliable signal. See
|
|
186
189
|
[`layer4/sctp.py`](src/netprotocols/layer4/sctp.py)'s module docstring.
|
|
187
190
|
|
|
191
|
+
The third is `PIM`. It has no length either, and bounds itself by its
|
|
192
|
+
own structure:
|
|
193
|
+
a Join/Prune, Register-Stop or Assert ends where its counts and
|
|
194
|
+
encoded addresses say, so anything after it is left undecoded, and a
|
|
195
|
+
Hello, whose options run to the end of the message, leaves a run of
|
|
196
|
+
zero bytes where the next option would start, as SCTP leaves one where
|
|
197
|
+
a chunk would. A Register's encapsulated packet runs to the end of the
|
|
198
|
+
buffer, which its checksum, over the first 8 bytes only, does not
|
|
199
|
+
cover. Past the 4-byte header it never raises either: a body its
|
|
200
|
+
parser cannot read is kept whole, for `PIM.message` to report. See
|
|
201
|
+
[`layer3/pim.py`](src/netprotocols/layer3/pim.py)'s module docstring.
|
|
202
|
+
|
|
203
|
+
`RIP` has the same problem one layer up: its 20-byte entries fill the
|
|
204
|
+
rest of the UDP datagram, and the walk does not trim to UDP's length
|
|
205
|
+
either. It keeps the whole buffer after its 4-byte header, as ICMPv4
|
|
206
|
+
and IGMP do, leaving no trailing bytes, so `bytes(RIP.decode(x)) == x`.
|
|
207
|
+
Its accessors read whole entries, up to the RFC 4822 authentication
|
|
208
|
+
trailer that the first entry's Packet Length locates (when the bytes
|
|
209
|
+
there start as a trailer does, `0xFFFF 0x0001`), and report what
|
|
210
|
+
is left, fewer than 20 bytes, as `remainder`: an entry a snap length
|
|
211
|
+
cut, or a trailer such as an Ethernet FCS, which only the UDP length
|
|
212
|
+
could tell apart. Past the header neither `decode()` nor any accessor
|
|
213
|
+
raises. Ethernet's zero padding reaches only a message with no entries,
|
|
214
|
+
since one entry already fills the minimum frame. See
|
|
215
|
+
[`layer7/rip.py`](src/netprotocols/layer7/rip.py)'s module docstring.
|
|
216
|
+
|
|
217
|
+
`VRRP` has no length field either, but needs no walk: its version and
|
|
218
|
+
address count give its exact length (8 bytes, the addresses, and
|
|
219
|
+
VRRPv2's 8 bytes of authentication data), so it obeys rule 3 like a
|
|
220
|
+
header with a length field and leaves Ethernet's padding undecoded. It
|
|
221
|
+
must: a VRRPv3 advert for one IPv4 address is 12 bytes, padded on the
|
|
222
|
+
wire, and VRRPv3's pseudo-header carries the message's length.
|
|
223
|
+
`STP` is the same case at layer 2: a BPDU has no length field, but its
|
|
224
|
+
BPDU Type gives its exact length (4, 35 or 36 octets), so it leaves
|
|
225
|
+
whatever follows undecoded, including the extra octets of a PDU longer
|
|
226
|
+
than its BPDU, which a receiver ignores (§14.4 of the IEEE 802.1 draft
|
|
227
|
+
the module cites). See
|
|
228
|
+
[`layer2/stp.py`](src/netprotocols/layer2/stp.py)'s module docstring.
|
|
229
|
+
|
|
230
|
+
`LLDP` has no length for the whole LLDPDU either, but bounds itself
|
|
231
|
+
by its last TLV: its TLVs each carry a Length, and the End of LLDPDU
|
|
232
|
+
TLV ends the walk, so it obeys rule 3 like a header with a length
|
|
233
|
+
field and leaves Ethernet's padding and an FCS undecoded. A TLV whose
|
|
234
|
+
Length runs past the buffer raises `TruncatedHeaderError`, as a lying
|
|
235
|
+
length does in rule 2. Wireshark's dissector notes that 802.1AB-2016
|
|
236
|
+
made the End TLV optional, so an LLDPDU that reaches the end of the
|
|
237
|
+
buffer without one decodes too; there nothing marks where a trailer
|
|
238
|
+
starts, so the walk reads one as TLVs: two or more zero bytes read as
|
|
239
|
+
an End TLV, and other bytes as another TLV or a framing error. The
|
|
240
|
+
mandatory Chassis ID, Port ID and Time To Live TLVs are read by
|
|
241
|
+
accessors from the first three positions, which answer `None` rather
|
|
242
|
+
than raise when the order is broken, since that is not framing. See
|
|
243
|
+
[`layer2/lldp.py`](src/netprotocols/layer2/lldp.py)'s module
|
|
244
|
+
docstring.
|
|
245
|
+
|
|
246
|
+
**802.3 framing.** Bytes 12-13 of an Ethernet header are a type or a
|
|
247
|
+
length (IEEE 802.3 Clause 3.2.6): from 0x0600 an EtherType, up to 1500
|
|
248
|
+
the length of an IEEE 802.2 LLC PDU, and undefined between, where the
|
|
249
|
+
walk ends. `Ethernet.ethertype` holds the raw value either way, and
|
|
250
|
+
`VLAN` reads the 2 bytes after a tag the same way. Neither `LLC` nor
|
|
251
|
+
`SNAP` has a length of its own, and a short PDU arrives padded to
|
|
252
|
+
Ethernet's minimum, often with an FCS after it; STP's BPDUs are that
|
|
253
|
+
short, and CDP's checksum covers exactly its PDU. So the Length is the
|
|
254
|
+
one enclosing length `decode_frame()` honours: after a header that
|
|
255
|
+
holds one (a class setting `_BOUNDS_PAYLOAD`, answering
|
|
256
|
+
`_payload_extent()`, both private while #235 settles the public form),
|
|
257
|
+
it ends the buffer it hands every later layer at the PDU's end. A
|
|
258
|
+
bound past the end of the frame bounds nothing, so a frame a snap
|
|
259
|
+
length cut decodes what was captured; a later, looser bound never
|
|
260
|
+
widens an earlier one; and a Length shorter than the LLC header leaves
|
|
261
|
+
`LLC.decode()` too few bytes, so the walk raises
|
|
262
|
+
`TruncatedHeaderError`. IPv4's, IPv6's and UDP's lengths are still not
|
|
263
|
+
honoured (#235), so padding behind a short IPv4 packet in Ethernet II
|
|
264
|
+
framing reaches its last layer as before. A third-party tag header
|
|
265
|
+
(an E-tag, a VN-tag) can answer `LLC` from its own `next_protocol()`,
|
|
266
|
+
but can bound the PDU after it only through that private hook, pending
|
|
267
|
+
#235. Called directly,
|
|
268
|
+
`LLC.decode()` has no Length to go by and leaves the rest of its buffer
|
|
269
|
+
to the caller, who can slice `frame[14 : 14 + eth.length]`. See
|
|
270
|
+
[`layer2/llc.py`](src/netprotocols/layer2/llc.py)'s module docstring.
|
|
271
|
+
|
|
272
|
+
`CDP`, which SNAP dispatches at OUI 00-00-0C PID 0x2000, is the first
|
|
273
|
+
decoder behind that bound. It has no length of its own: its TLVs run to
|
|
274
|
+
the end of the PDU, and its checksum covers the whole PDU, so in a walk
|
|
275
|
+
it decodes the PDU and nothing else. Its `decode()` walks TLVs
|
|
276
|
+
to the end of the buffer it is handed and stops early, without
|
|
277
|
+
raising, only where every byte left is zero, so Ethernet's zero padding
|
|
278
|
+
is left undecoded when it is called directly; rule 3 holds for those
|
|
279
|
+
zeros only, as for SCTP. Otherwise a TLV Length below 4 raises
|
|
280
|
+
`InvalidFieldError`, 1-3 bytes left after the last TLV raise
|
|
281
|
+
`TruncatedHeaderError`, and so does a TLV whose Length runs past the
|
|
282
|
+
buffer, in a walk too, where those bytes are inside the 802.3 Length.
|
|
283
|
+
Wireshark's dissector instead stops at a Length below 4 whatever
|
|
284
|
+
follows, and marks 1-3 bytes left malformed even when they are zero.
|
|
285
|
+
The zero rule has one blind spot: zeros after the last TLV but inside
|
|
286
|
+
the 802.3 Length belong to the PDU the sender declared, yet a walk
|
|
287
|
+
leaves them undecoded without raising, so `consumed` stopping short of
|
|
288
|
+
the Length's end is the only structural signal. `verify()` sums the
|
|
289
|
+
bytes decoded, without them; where the TLVs end at an odd offset on a
|
|
290
|
+
nonzero byte and the sender summed the whole Length, it returns
|
|
291
|
+
`False`.
|
|
292
|
+
`scripts/check_fixtures.py` rejects such a frame, and Wireshark marks
|
|
293
|
+
1-3 such zeros malformed and shows 4 or more as a TLV of invalid
|
|
294
|
+
Length 0.
|
|
295
|
+
See [`layer2/cdp.py`](src/netprotocols/layer2/cdp.py)'s module
|
|
296
|
+
docstring.
|
|
297
|
+
|
|
188
298
|
Decoding accepts `bytes` or `memoryview`. Views are a decode-time
|
|
189
299
|
transient only: every stored field is a scalar or materialized
|
|
190
300
|
`bytes`, so no decoded object ever aliases the buffer it came from.
|
|
@@ -216,15 +326,19 @@ except ProtocolError as e:
|
|
|
216
326
|
print(e.protocol, e.field, e.offset, e.frame_offset, e.expected, e.actual)
|
|
217
327
|
```
|
|
218
328
|
|
|
219
|
-
`offset` is relative to whatever buffer the error was found in
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
`
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
`
|
|
329
|
+
`offset` is relative to whatever buffer the error was found in. An
|
|
330
|
+
error raised by `decode()` is relative to that call's `data` argument,
|
|
331
|
+
whatever `field` names: OSPF's framing errors name `field="body"` (or
|
|
332
|
+
`"auth_trailer"`) and still count from the start of the OSPF header,
|
|
333
|
+
which is what lets `decode_frame` rebase them. An error raised later,
|
|
334
|
+
by an accessor that parses a raw `bytes` attribute on demand
|
|
335
|
+
(`options`, `body`, `sections`, `chunks`), is relative to that
|
|
336
|
+
attribute — a TCP option error's `offset` is relative to
|
|
337
|
+
`header.options`, an ICMPv6 NDP option's or an IGMPv3 group record's
|
|
338
|
+
to `body`, not to the frame. `decode_frame` is the only code holding
|
|
339
|
+
the cursor needed to rebase a layer's own offset to the whole frame,
|
|
340
|
+
so it does — the result lands in `frame_offset`, left `None` by a
|
|
341
|
+
bare `SomeClass.decode()` call with no frame to rebase against. A
|
|
228
342
|
`__post_init__` validation error (values, not bytes) leaves `offset`
|
|
229
343
|
`None` too; there is nothing to be positioned in. `HTTP` is the
|
|
230
344
|
exception on both counts: its `field` names a grammar element
|
|
@@ -249,24 +363,66 @@ unless the Security Association uses a confidentiality-free transform
|
|
|
249
363
|
(e.g. NULL, RFC 2410, or AES-GMAC, RFC 4543), and at an offset only
|
|
250
364
|
the SA determines, so the decoder never reads it).
|
|
251
365
|
|
|
366
|
+
`BGP`, `MPLS` and `LLC` are the three exceptions: they answer from
|
|
367
|
+
decode context rather than from a wire field alone. A BGP message
|
|
368
|
+
carries no payload: a TCP segment can hold several messages back to
|
|
369
|
+
back, each decoded as its own layer, so `BGP.next_protocol()` names the
|
|
370
|
+
next message *beside* it — `BGP` when the bytes after this message
|
|
371
|
+
begin another one, `None` when they do not. An MPLS label stack carries
|
|
372
|
+
a packet but no field naming its protocol (see *MPLS: dispatch by
|
|
373
|
+
convention* below), so `MPLS.next_protocol()` infers one, partly from
|
|
374
|
+
the first byte after the stack. SAP 0x42 carries GARP's PDUs (Protocol
|
|
375
|
+
Identifier 0x0001) beside STP's BPDUs (0x0000), so `LLC.decode()`
|
|
376
|
+
records, for a DSAP 0x42 PDU it would dispatch, whether the two octets
|
|
377
|
+
after its Control are present and not 0x0000, and `LLC.next_protocol()`
|
|
378
|
+
then answers `None`, whatever `llc.dsap` holds at 0x42; fewer than two
|
|
379
|
+
octets still reach `STP`, which reports the truncation. Only the bytes
|
|
380
|
+
can say, and `next_protocol()` is handed none, so each `decode()`
|
|
381
|
+
records what it saw on the instance in a field kept out of the
|
|
382
|
+
dataclass's comparison and hash (`compare=False`, the mechanism `HTTP`
|
|
383
|
+
uses for its `_message` parse cache). Two equal headers compare equal
|
|
384
|
+
however they were found, and can still chain differently. The rules of
|
|
385
|
+
the decode contract hold for all three unchanged; see *Port-based
|
|
386
|
+
dispatch is best-effort* below,
|
|
387
|
+
[`_base.py`](src/netprotocols/_base.py)'s *Decode context in
|
|
388
|
+
next_protocol*, the module docstrings of
|
|
389
|
+
[`layer7/bgp.py`](src/netprotocols/layer7/bgp.py) and
|
|
390
|
+
[`layer2/mpls.py`](src/netprotocols/layer2/mpls.py), and
|
|
391
|
+
[`layer2/llc.py`](src/netprotocols/layer2/llc.py)'s *DSAP 0x42*.
|
|
392
|
+
|
|
252
393
|
```mermaid
|
|
253
394
|
flowchart LR
|
|
254
395
|
E[Ethernet] -->|0x0806| A[ARP]
|
|
255
396
|
E -->|0x0800| I4[IPv4]
|
|
256
397
|
E -->|0x86DD| I6[IPv6]
|
|
257
398
|
E -->|0x8100, 0x88A8, 0x9100| V[VLAN tag]
|
|
399
|
+
E -->|length ≤ 1500| L[LLC]
|
|
400
|
+
V -->|length ≤ 1500| L
|
|
401
|
+
L -->|DSAP 0xAA, SSAP 0xAA, UI| SN[SNAP]
|
|
402
|
+
L -->|DSAP 0x42, UI or I, Protocol ID 0| STP[STP BPDU]
|
|
403
|
+
SN -->|OUI 00-00-00 or 00-00-F8, PID 0x0806| A
|
|
404
|
+
SN -->|OUI 00-00-00 or 00-00-F8, PID 0x0800| I4
|
|
405
|
+
SN -->|OUI 00-00-0C, PID 0x2000| CDP[CDP]
|
|
258
406
|
V -->|0x8100, 0x88A8, 0x9100| V
|
|
259
407
|
V -->|0x0806| A
|
|
260
408
|
V -->|0x0800| I4
|
|
261
409
|
V -->|0x86DD| I6
|
|
410
|
+
E -->|0x8847, 0x8848| M[MPLS label stack]
|
|
411
|
+
V -->|0x8847, 0x8848| M
|
|
412
|
+
M -->|bottom label 0, or first nibble 4| I4
|
|
413
|
+
M -->|bottom label 2, or first nibble 6| I6
|
|
414
|
+
E -->|0x88CC| LD[LLDP]
|
|
415
|
+
V -->|0x88CC| LD
|
|
262
416
|
I4 -->|1| IC4[ICMPv4]
|
|
263
417
|
I4 -->|6| T[TCP]
|
|
264
418
|
I4 -->|17| U[UDP]
|
|
265
419
|
I4 -->|132| S[SCTP]
|
|
420
|
+
I4 -->|103| P[PIM]
|
|
266
421
|
I6 -->|58| IC6[ICMPv6]
|
|
267
422
|
I6 -->|6| T
|
|
268
423
|
I6 -->|17| U
|
|
269
424
|
I6 -->|132| S
|
|
425
|
+
I6 -->|103| P
|
|
270
426
|
I4 -->|4| I4
|
|
271
427
|
I4 -->|41| I6
|
|
272
428
|
I6 -->|4| I4
|
|
@@ -290,8 +446,15 @@ flowchart LR
|
|
|
290
446
|
AH6 -->|6| T
|
|
291
447
|
AH6 -->|17| U
|
|
292
448
|
AH6 -->|50| ESP
|
|
449
|
+
I4 -->|89| OSPF[OSPFv2]
|
|
450
|
+
I4 -->|112| VR[VRRP]
|
|
451
|
+
I6 -->|112| VR6[VRRPv6]
|
|
452
|
+
X -->|112| VR6
|
|
293
453
|
U -->|4789| VX[VXLAN]
|
|
294
454
|
VX -->|always| E
|
|
455
|
+
U -->|520| R[RIP]
|
|
456
|
+
T -->|179| BGP[BGP message]
|
|
457
|
+
BGP -->|another message in the segment| BGP
|
|
295
458
|
```
|
|
296
459
|
|
|
297
460
|
IPv6 extension headers (Hop-by-Hop Options, Routing, Fragment,
|
|
@@ -306,12 +469,15 @@ offset rule applies to fragmented IPv4.
|
|
|
306
469
|
|
|
307
470
|
IPsec's AH chains the same way, through its own `next_header`, onward
|
|
308
471
|
to anything the IP header before it could have carried (the diagram
|
|
309
|
-
draws only the transport, ESP and extension-header edges). It
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
472
|
+
draws only the transport, ESP and extension-header edges). It comes as
|
|
473
|
+
a pair for that reason: `AH` after IPv4 and `AHv6` after IPv6 have one
|
|
474
|
+
wire format but dispatch through their own family's table, so IPv4 →
|
|
475
|
+
AH → `0` stops rather than conjuring a Hop-by-Hop layer, while IPv6 →
|
|
476
|
+
AH → Destination Options — an order RFC 8200 §4.1 permits — keeps
|
|
477
|
+
decoding. VRRP is the other pair, for a different reason: it ends the
|
|
478
|
+
walk, but its list of virtual addresses is 4 bytes an entry after IPv4
|
|
479
|
+
and 16 after IPv6, which nothing in the VRRP header says, so `VRRP`
|
|
480
|
+
reads the one and its subclass `VRRPv6` the other. ESP, the other IPsec header,
|
|
315
481
|
ends the walk: what it carries is named only inside its protected
|
|
316
482
|
trailer.
|
|
317
483
|
|
|
@@ -325,13 +491,45 @@ bitfields — `pcp`, `dei`, `vid` — as dataclass fields validated in
|
|
|
325
491
|
property (the same pattern `IPv6` uses for `version` /
|
|
326
492
|
`traffic_class` / `flow_label`).
|
|
327
493
|
|
|
494
|
+
**MPLS: dispatch by convention.** An MPLS label stack (RFC 3032) sits
|
|
495
|
+
where a VLAN tag does, after Ethernet's EtherType (0x8847, or 0x8848
|
|
496
|
+
for a multicast frame whose top label is upstream-assigned, RFC 5332),
|
|
497
|
+
and decodes as one `MPLS` layer however many 4-byte entries it holds:
|
|
498
|
+
the stack ends at the entry with its Bottom of Stack bit set, so it
|
|
499
|
+
bounds itself, and a deep stack counts once against `max_depth`. What
|
|
500
|
+
it does not have is a field naming the packet it carries. RFC 3032
|
|
501
|
+
§2.2 leaves that to the binding of the label popped at the egress,
|
|
502
|
+
which a capture never shows, so `MPLS.next_protocol()` follows a
|
|
503
|
+
convention. A bottom label reserved for a payload settles it — 0, IPv4
|
|
504
|
+
Explicit NULL, is IPv4 and 2, IPv6 Explicit NULL, is IPv6, whatever
|
|
505
|
+
the bytes say, while 13 (GAL) and 14 (OAM Alert) are followed by a
|
|
506
|
+
channel header or an OAM message and stop the walk. Otherwise the
|
|
507
|
+
first nibble after the stack is read as an IP version, 4 or 6, as
|
|
508
|
+
routers that load-balance IP over MPLS do (RFC 4928); anything else, a
|
|
509
|
+
pseudowire control word's 0 among them, stops the walk. The class
|
|
510
|
+
comes from the `ethertype` table at 0x0800 or 0x86DD, so a `registry`
|
|
511
|
+
or `decode_as` override reaches the labelled packet too. The nibble is
|
|
512
|
+
a guess, as a port is: an Ethernet pseudowire sent without a control
|
|
513
|
+
word, whose inner destination MAC address happens to begin with 4 or
|
|
514
|
+
6, decodes as an IP header, which is the misreading RFC 4928 and RFC
|
|
515
|
+
4385's control word exist to prevent. GRE dispatches its Protocol Type
|
|
516
|
+
through the `ethertype` table too, so MPLS in GRE (RFC 4023) reaches
|
|
517
|
+
`MPLS` with no code of its own. See
|
|
518
|
+
[`layer2/mpls.py`](src/netprotocols/layer2/mpls.py)'s module
|
|
519
|
+
docstring.
|
|
520
|
+
|
|
328
521
|
The numbers on the arrows are the wire values — EtherTypes out of
|
|
329
|
-
Ethernet, IP protocol numbers out of IPv4/IPv6
|
|
522
|
+
Ethernet, IP protocol numbers out of IPv4/IPv6; MPLS's arrows, which
|
|
523
|
+
no wire field names, are the convention above — and they live in
|
|
330
524
|
[`_enums.py`](src/netprotocols/_enums.py) as `EtherType` and
|
|
331
525
|
`IPProtocol`. IPv4 and IPv6 share one registry because the number
|
|
332
526
|
space is shared (1 is ICMPv4, 58 is ICMPv6, 6 is TCP, 17 is UDP, 132
|
|
333
|
-
is SCTP — no collisions; 51 is AH
|
|
334
|
-
family's own).
|
|
527
|
+
is SCTP, 103 is PIM — no collisions; 51 is AH and 112 is VRRP in
|
|
528
|
+
both, each decoded by a class of each family's own). 89 is the
|
|
529
|
+
exception: it is OSPF in both families, but OSPFv2 (RFC 2328) after
|
|
530
|
+
IPv4 and OSPFv3 (RFC 5340), a different wire format, after IPv6, and
|
|
531
|
+
this library decodes only OSPFv2, so `OSPF` is reached from IPv4
|
|
532
|
+
alone.
|
|
335
533
|
|
|
336
534
|
A complete frame walk is a five-line loop — which is exactly why it
|
|
337
535
|
ships as [`decode_frame()`](src/netprotocols/walk.py) rather than as
|
|
@@ -344,11 +542,13 @@ packet = decode_frame(frame) # Packet(Ethernet(...), IPv4(...), TCP(...
|
|
|
344
542
|
Underneath it is the loop the two answers imply:
|
|
345
543
|
|
|
346
544
|
```python
|
|
347
|
-
layers, cursor, protocol = [], 0, start
|
|
545
|
+
layers, cursor, end, protocol = [], 0, len(frame), start
|
|
348
546
|
while protocol is not None:
|
|
349
|
-
header = protocol.decode(frame[cursor:])
|
|
547
|
+
header = protocol.decode(frame[cursor:end])
|
|
350
548
|
layers.append(header)
|
|
351
549
|
cursor += header.header_len
|
|
550
|
+
if header._BOUNDS_PAYLOAD: # an 802.3 Length (see above)
|
|
551
|
+
end = _bounded(header, cursor, end)
|
|
352
552
|
protocol = header.next_protocol(registry)
|
|
353
553
|
```
|
|
354
554
|
|
|
@@ -365,12 +565,14 @@ Wrapping each frame in a `memoryview` internally measured *slower*
|
|
|
365
565
|
**Where do those arrows live?** Not in the protocol classes. Every
|
|
366
566
|
one of them is a row in a dispatch table owned by
|
|
367
567
|
[`registry.py`](src/netprotocols/registry.py), and `next_protocol()`
|
|
368
|
-
is a single `dict.get` against one of
|
|
568
|
+
is a single `dict.get` against one of seven tables named after the wire
|
|
369
569
|
field they dispatch on:
|
|
370
570
|
|
|
371
571
|
| Table | Dispatches on | Read by |
|
|
372
572
|
|---|---|---|
|
|
373
|
-
| `ethertype` | Ethernet II EtherType | `Ethernet`, `VLAN`, `GRE` |
|
|
573
|
+
| `ethertype` | Ethernet II EtherType (from Ethernet and VLAN, 0x0600 and up only; from GRE and SNAP, any value) | `Ethernet`, `VLAN`, `GRE`, `SNAP` (OUI 00-00-00, 00-00-F8); `MPLS` at 0x0800 and 0x86DD, by convention |
|
|
574
|
+
| `llc.dsap` | IEEE 802.2 LLC DSAP | `LLC` |
|
|
575
|
+
| `llc.snap` | SNAP `(oui << 16) \| pid`, any other OUI | `SNAP` |
|
|
374
576
|
| `ip.proto` | IPv4 `protocol` | `IPv4`, `AH` |
|
|
375
577
|
| `ip.proto.v6` | IPv6 `next_header` | `IPv6`, extension headers, `AHv6` |
|
|
376
578
|
| `udp.port` | UDP well-known port | `UDP` |
|
|
@@ -379,7 +581,7 @@ field they dispatch on:
|
|
|
379
581
|
`ip.proto.v6` **inherits** `ip.proto`: the two share a number space
|
|
380
582
|
but not a table, because the four extension headers must be reachable
|
|
381
583
|
only inside an IPv6 chain. Registering in `ip.proto` reaches both,
|
|
382
|
-
except at a key `ip.proto.v6` owns itself (51, below); registering in
|
|
584
|
+
except at a key `ip.proto.v6` owns itself (51 and 112, below); registering in
|
|
383
585
|
`ip.proto.v6` reaches the v6 chain alone. Inheritance
|
|
384
586
|
is resolved when you register, not when you look up, so the gating
|
|
385
587
|
costs nothing on the hot path — an IPv4 packet with `protocol=0`
|
|
@@ -387,14 +589,27 @@ cannot conjure a Hop-by-Hop layer because the table it reads has no
|
|
|
387
589
|
entry for `0`.
|
|
388
590
|
|
|
389
591
|
A child's own registration also *shadows* the inherited entry, which
|
|
390
|
-
is how AH's
|
|
391
|
-
then `AH` in `ip.proto
|
|
592
|
+
is how AH's and VRRP's pairs are wired: `AHv6` is registered in
|
|
593
|
+
`ip.proto.v6` first, then `AH` in `ip.proto` (and `VRRPv6`, then
|
|
594
|
+
`VRRP`, likewise), and the parent write is not inherited over a
|
|
392
595
|
key the child already owns. That works the other way round too: a
|
|
393
|
-
third party replacing protocol 51 in `ip.proto` (`override=True`)
|
|
596
|
+
third party replacing protocol 51 or 112 in `ip.proto` (`override=True`)
|
|
394
597
|
changes IPv4 dispatch only, and must register in `ip.proto.v6` as well
|
|
395
598
|
to reach IPv6. `_defaults.FAMILY_SPECIFIC_NUMBERS` names the numbers
|
|
396
|
-
wired this way, and the tests hold the two tables to differing at
|
|
397
|
-
those keys
|
|
599
|
+
wired this way, and the tests hold the two tables to differing only at
|
|
600
|
+
those keys, the IPv6-only numbers and the IPv4-only one below.
|
|
601
|
+
|
|
602
|
+
A child can also own a key with no class. Protocol 89 is OSPFv2 in
|
|
603
|
+
`ip.proto` and would be inherited into `ip.proto.v6`, where it means
|
|
604
|
+
OSPFv3, so every OSPFv3 packet would fail to decode as OSPFv2; instead
|
|
605
|
+
the defaults withhold 89 from `ip.proto.v6` (`_defaults.IPV4_ONLY_NUMBERS`)
|
|
606
|
+
before registering `OSPF` in `ip.proto`. A withheld key is skipped by
|
|
607
|
+
inheritance as an override is, so an IPv6 packet with next header 89
|
|
608
|
+
ends its walk at the IPv6 header, as it did before `OSPF` existed, and
|
|
609
|
+
a later write to `ip.proto` does not reach it; an OSPFv3 decoder
|
|
610
|
+
registered directly in `ip.proto.v6` fills it. `OSPF.decode()` also
|
|
611
|
+
rejects any version but 2, for a caller who sends it OSPFv3 on purpose
|
|
612
|
+
(`start=` or `decode_as`).
|
|
398
613
|
|
|
399
614
|
A row need not name a class of its own. IP-in-IP (protocols 4 and 41)
|
|
400
615
|
has no header between the outer IP header and the inner one, so its
|
|
@@ -405,16 +620,18 @@ therefore take the nearest one before it, as `Packet.with_checksums()`
|
|
|
405
620
|
and `Packet.flow_key()` do, not the first.
|
|
406
621
|
|
|
407
622
|
**Extending the walk without forking.** The tables are public. A
|
|
408
|
-
protocol this library does not implement —
|
|
409
|
-
|
|
623
|
+
protocol this library does not implement — a proprietary telemetry
|
|
624
|
+
header, say, sent under 0x88B5, which IANA's EtherType registry lists
|
|
625
|
+
as "IEEE Std 802 - Local Experimental Ethertype", so no built-in
|
|
626
|
+
decoder holds it — is one registration away from participating in
|
|
410
627
|
frame walks like any built-in:
|
|
411
628
|
|
|
412
629
|
```python
|
|
413
630
|
from netprotocols import Protocol
|
|
414
631
|
from netprotocols.registry import register
|
|
415
632
|
|
|
416
|
-
@register("ethertype",
|
|
417
|
-
class
|
|
633
|
+
@register("ethertype", 0x88B5)
|
|
634
|
+
class Telemetry(Protocol):
|
|
418
635
|
...
|
|
419
636
|
```
|
|
420
637
|
|
|
@@ -437,19 +654,23 @@ inside `install()` and run once from `__init__.py` after every
|
|
|
437
654
|
protocol class exists. Shared enums stay in the dependency-free
|
|
438
655
|
`_enums.py`. The layer modules bind their table at import, and every
|
|
439
656
|
class a header chooses by a field value (EtherType, IP protocol, port)
|
|
440
|
-
comes out of a table, never from an import.
|
|
657
|
+
comes out of a table, never from an import. Three modules do import
|
|
441
658
|
another's class, where there is no field to look up. `layer3/icmp.py`
|
|
442
659
|
names `IPv4` and `IPv6` to start the walk of the packet an ICMP error
|
|
443
|
-
quotes, which is not dispatch
|
|
444
|
-
directly, because a VXLAN payload is always an Ethernet frame
|
|
445
|
-
|
|
446
|
-
|
|
660
|
+
quotes, which is not dispatch; `layer7/vxlan.py` names `Ethernet`
|
|
661
|
+
directly, because a VXLAN payload is always an Ethernet frame; and
|
|
662
|
+
`layer2/ethernet.py` names `LLC`, because whatever an 802.3 Length's
|
|
663
|
+
value, an LLC header follows it. None can form a cycle: `ip.py` and
|
|
664
|
+
`llc.py` import no other layer module, and `ethernet.py` imports only
|
|
665
|
+
`llc.py`.
|
|
447
666
|
|
|
448
667
|
## Port-based dispatch is best-effort
|
|
449
668
|
|
|
450
|
-
The `ethertype` and `ip.proto` tables dispatch on a single
|
|
451
|
-
authoritative field: it exists to say what follows
|
|
452
|
-
|
|
669
|
+
The `ethertype`, `llc.*` and `ip.proto` tables dispatch on a single
|
|
670
|
+
authoritative field: it exists to say what follows (MPLS, which looks
|
|
671
|
+
up the `ethertype` table by convention, is the exception described
|
|
672
|
+
above). The two port tables break that model, and differ in kind
|
|
673
|
+
rather than just in name.
|
|
453
674
|
A port is a *guess* — any service may run on any port, and the
|
|
454
675
|
discriminator is split across the source and destination ports.
|
|
455
676
|
Dispatch therefore checks the destination port first (a request
|
|
@@ -501,6 +722,54 @@ header cannot reject more than it does: RFC 7348 says to ignore its
|
|
|
501
722
|
reserved bits, so rejecting those would refuse real traffic, such as
|
|
502
723
|
the Group Based Policy extension's.
|
|
503
724
|
|
|
725
|
+
RIP on UDP 520 rejects the most, because two of its four header bytes
|
|
726
|
+
have values no RIP message carries: `RIP.decode()` requires version 1
|
|
727
|
+
or 2 and a nonzero command, which 510 of the 65,536 two-byte prefixes
|
|
728
|
+
satisfy, so about 99.2% of random datagrams are rejected. Measured the
|
|
729
|
+
same way, the walk stopped for about 99.2% in each case: the 300-byte
|
|
730
|
+
payloads sent to 520, and the 0-600 byte ones unpadded and padded, the
|
|
731
|
+
differences between them within sampling noise; an empty datagram
|
|
732
|
+
padded to the minimum is rejected too, since the padding reads as
|
|
733
|
+
version 0. It goes no further. The obsolete and extension commands
|
|
734
|
+
(TraceOn, TraceOff, the triggered and update messages) decode, and so
|
|
735
|
+
does a RIPv1 message with nonzero must-be-zero fields, which RFC 1058
|
|
736
|
+
tells a receiver to ignore but a decoder should show. Past the header,
|
|
737
|
+
any 20 bytes are an entry.
|
|
738
|
+
|
|
739
|
+
BGP on TCP 179 (RFC 4271) has the strongest guard of any port entry:
|
|
740
|
+
every message opens with a 16-byte Marker of all ones, which random
|
|
741
|
+
bytes match once in 2^128, and then a Length and Type whose bounds
|
|
742
|
+
depend on each other (§6.1). Measured the same way, over 10,000 random
|
|
743
|
+
300-byte payloads and 10,000 of uniformly random length 0-600, padded
|
|
744
|
+
or not, every walk to port 179 ended with `stopped_by` set and none
|
|
745
|
+
decoded a BGP layer; with the Marker forced to all ones, the Length and
|
|
746
|
+
Type checks alone still rejected 9,998 to 10,000 of 10,000 random
|
|
747
|
+
300-byte payloads (`random.Random` seeds 0-4). What the port cannot tell the decoder is where a message
|
|
748
|
+
starts: TCP cuts its byte stream where it likes, so a segment can hold
|
|
749
|
+
several messages, the tail of one, or the head of the next. Each
|
|
750
|
+
message decodes as one `BGP` layer, and `BGP.next_protocol()` answers
|
|
751
|
+
`BGP` while another message begins in the same segment. It is handed no
|
|
752
|
+
bytes, so `BGP.decode()` records the answer on the instance, in a field
|
|
753
|
+
outside its comparison and hash (see *Walking a frame* above). A
|
|
754
|
+
segment that ends inside a message raises `TruncatedHeaderError` at
|
|
755
|
+
that message (lax mode keeps the ones before it); one that begins
|
|
756
|
+
inside a message fails the Marker check, or raises
|
|
757
|
+
`TruncatedHeaderError` if it is shorter than a header; a bare ACK, like
|
|
758
|
+
one on port 53, sends an empty payload into the decoder. A segment of many small messages is a deep
|
|
759
|
+
chain, and here `max_depth` bounds correctness, not just cost: the
|
|
760
|
+
default leaves room for 29 messages after Ethernet, IPv4 and TCP, and
|
|
761
|
+
valid segments hold more. A 1448-byte one holds 76 KEEPALIVEs or 30
|
|
762
|
+
one-prefix UPDATEs, and a capture taken with GRO or TSO on, over
|
|
763
|
+
loopback or on a jumbo-frame link sees segments of up to 64 KB, so a
|
|
764
|
+
strict walk raises `MaxDepthExceededError` on valid traffic and a lax
|
|
765
|
+
one stops with `stopped_by` set and `consumed` short of the frame. A
|
|
766
|
+
larger `max_depth` or a second walk,
|
|
767
|
+
`decode_frame(frame[packet.consumed:], start=BGP)`, decodes the rest;
|
|
768
|
+
the busiest segment of the captured session in the corpus, taken with
|
|
769
|
+
offload off, carries 7. Reassembling
|
|
770
|
+
the stream is the caller's, as it is for `HTTP` below: see
|
|
771
|
+
[`layer7/bgp.py`](src/netprotocols/layer7/bgp.py)'s module docstring.
|
|
772
|
+
|
|
504
773
|
A rejected guess takes the ordinary decode-error path: the chain keeps
|
|
505
774
|
the layers it did decode and records the failure. An accepted one
|
|
506
775
|
yields a layer that decoded cleanly and is wrong, and nothing in the
|
|
@@ -526,9 +795,12 @@ bytes instead.
|
|
|
526
795
|
|
|
527
796
|
Every class also works in reverse. Constructors take the friendly
|
|
528
797
|
forms (string addresses, integer fields) and validate some of them in
|
|
529
|
-
`__post_init__`: Ethernet, ARP and IPv4 check address formats (IPv6
|
|
530
|
-
and
|
|
531
|
-
`bytes()
|
|
798
|
+
`__post_init__`: Ethernet, ARP and IPv4 check address formats (IPv6,
|
|
799
|
+
DHCP, VRRP and OSPF do not, so a malformed address there fails only
|
|
800
|
+
in `bytes()`; for OSPF's `router_id`, `area_id` and its records'
|
|
801
|
+
address fields, such as an LSA header's `advertising_router`, that
|
|
802
|
+
is the `OSError` from `socket.inet_pton`), and IPv4's IHL and TCP's
|
|
803
|
+
data offset must agree with
|
|
532
804
|
`len(options)`. For a valid header, `bytes()` re-emits the exact
|
|
533
805
|
on-wire form. Constructors do not check everything decode does, so
|
|
534
806
|
some headers can be built that decode would never produce (see
|
|
@@ -562,19 +834,41 @@ verbatim** — exactly what you want when faithfully re-encoding
|
|
|
562
834
|
captured traffic. Computing and verifying them is explicit, via
|
|
563
835
|
`netprotocols.checksum`: `compute()`/`verify()` handle the IPv4 header
|
|
564
836
|
checksum, ICMPv4, and TCP/UDP/ICMPv6 with their IPv4/IPv6
|
|
565
|
-
pseudo-headers,
|
|
837
|
+
pseudo-headers, PIM (the pseudo-header over IPv6 only, a Register's
|
|
838
|
+
first 8 bytes only), and `Packet.with_checksums(payload)` fills a whole
|
|
566
839
|
stack before sending. (The RFC 768 subtlety lives where it belongs:
|
|
567
840
|
the `0x0000 → 0xFFFF` substitution applies to UDP alone.) SCTP is the
|
|
568
841
|
one transport that does not use the internet checksum: its CRC32c
|
|
569
842
|
(`crc32c()`, RFC 9260 Appendix A) covers the whole packet with no
|
|
570
843
|
pseudo-header, and goes on the wire least significant byte first, so
|
|
571
844
|
`compute()` returns it byte-swapped to match the field as decoded.
|
|
845
|
+
VRRP's checksum is the internet checksum of the message alone in
|
|
846
|
+
VRRPv2 and behind the IP pseudo-header in VRRPv3. RFC 5798 §5.2.8
|
|
847
|
+
cites only IPv6's pseudo-header; adding IPv4's too is keepalived's
|
|
848
|
+
default, and `compute()`'s. RFC 9568, which obsoletes RFC 5798, leaves
|
|
849
|
+
it out over IPv4, and `verify()` returns `False` for an advert
|
|
850
|
+
checksummed that way.
|
|
572
851
|
|
|
573
852
|
That is also why `SCTP.decode()` bounds its own extent (see "The decode
|
|
574
853
|
contract" above): otherwise Ethernet's padding would land inside the
|
|
575
854
|
CRC's span, and a packet under 46 bytes from a real NIC would fail to
|
|
576
855
|
verify.
|
|
577
856
|
|
|
857
|
+
OSPF carries two checksums. The packet's is the internet checksum of
|
|
858
|
+
the whole packet but its 64-bit Authentication field, with no
|
|
859
|
+
pseudo-header, and `compute()` covers only the packet's own bytes, so
|
|
860
|
+
an RFC 5613 LLS block after it never enters; under cryptographic
|
|
861
|
+
authentication (AuType 2, or 3 from RFC 7474) none is computed (the
|
|
862
|
+
digest protects the packet), so `compute()` raises, `verify()` returns
|
|
863
|
+
`True` and `with_checksums()` leaves the field alone. Each LSA's LS
|
|
864
|
+
Checksum is an ISO 8473 Fletcher checksum of the LSA but its LS Age;
|
|
865
|
+
an LSA is not a layer, so it has functions of its own:
|
|
866
|
+
`lsa_checksum()` generates the field with `fletcher_checksum()`, and
|
|
867
|
+
`verify_lsa()` checks it the way RFC 905 B.4 does, by both running sums
|
|
868
|
+
being zero, so a byte stored as `0x00` where the generator writes
|
|
869
|
+
`0xff` passes; and, since RFC 2328 §12.1.7 says the field cannot be
|
|
870
|
+
zero, it fails a field of `0x0000` even where those sums are zero.
|
|
871
|
+
|
|
578
872
|
`Packet` is the thin composition layer: it holds an ordered tuple of
|
|
579
873
|
headers and `bytes(packet)` joins them, ready for a raw socket.
|
|
580
874
|
|
|
@@ -585,27 +879,37 @@ src/netprotocols/
|
|
|
585
879
|
├── __init__.py public API re-exports, __version__, registry install
|
|
586
880
|
├── _base.py Protocol ABC, decode contract, address helpers
|
|
587
881
|
├── _tlv.py walk_kind_length_value(): shared TCP/IPv4/IPv6
|
|
588
|
-
│ options TLV walker
|
|
882
|
+
│ options and PIM Hello options TLV walker
|
|
589
883
|
├── _enums.py EtherType, IPProtocol, ARPOperation (imports nothing)
|
|
590
884
|
├── registry.py public dispatch tables: register(), Registry
|
|
591
885
|
├── walk.py decode_frame(): the shipped chain walker
|
|
592
886
|
├── _defaults.py the built-in decoder map, installed at import
|
|
593
887
|
├── packet.py Packet composition, with_checksums(), flow_key()
|
|
594
888
|
├── checksum.py RFC 1071 internet_checksum, SCTP's crc32c,
|
|
595
|
-
│ compute, verify
|
|
889
|
+
│ compute, verify (CDP's odd-byte sum among them),
|
|
890
|
+
│ OSPF's LSA Fletcher checksum
|
|
596
891
|
├── flow.py FlowKey, flow_key(): canonical bidirectional keys
|
|
597
892
|
├── pcap.py read_captures()/read_pcap()/read_pcapng(): classic
|
|
598
893
|
│ pcap and pcapng readers, from bytes not filenames
|
|
599
|
-
├── layer2/ ethernet.py, arp.py, vlan.py (802.1Q / 802.1ad)
|
|
894
|
+
├── layer2/ ethernet.py, arp.py, vlan.py (802.1Q / 802.1ad),
|
|
895
|
+
│ llc.py (802.2 LLC + SNAP, after an 802.3 Length),
|
|
896
|
+
│ cdp.py (Cisco Discovery Protocol, after SNAP),
|
|
897
|
+
│ lldp.py (LLDP, IEEE 802.1AB),
|
|
898
|
+
│ mpls.py (MPLS label stack: MPLS + MPLSLabel),
|
|
899
|
+
│ stp.py (802.1D / 802.1w BPDUs)
|
|
600
900
|
├── layer3/ ip.py (IPv4 + IPv6), icmp.py (ICMPv4 + ICMPv6),
|
|
601
901
|
│ igmp.py, gre.py, esp.py (IPsec ESP),
|
|
602
902
|
│ ah.py (IPsec AH: AH + AHv6),
|
|
903
|
+
│ ospf.py (OSPFv2: OSPF + its message records),
|
|
904
|
+
│ pim.py (PIM-SM),
|
|
905
|
+
│ vrrp.py (VRRPv2/v3: VRRP + VRRPv6),
|
|
603
906
|
│ ipv6_ext.py (Hop-by-Hop, Routing, Fragment,
|
|
604
907
|
│ Destination Options)
|
|
605
908
|
├── layer4/ tcp.py, udp.py, sctp.py, _ports.py (port-table
|
|
606
909
|
│ dispatch)
|
|
607
910
|
├── layer7/ dns.py, dhcp.py, http.py (the HTTP/1.1 grammar
|
|
608
|
-
│ and the HTTP codec class), vxlan.py
|
|
911
|
+
│ and the HTTP codec class), vxlan.py, rip.py
|
|
912
|
+
│ (RIPv1 + RIPv2), bgp.py (BGP-4 messages)
|
|
609
913
|
└── utils/ validators (mac.py, ipv4.py), exceptions.py
|
|
610
914
|
tests/ one file per protocol + test_contract.py
|
|
611
915
|
(truncation, lying lengths, chain walks),
|