netprotocols 2.6.0__tar.gz → 2.7.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.
Files changed (276) hide show
  1. {netprotocols-2.6.0 → netprotocols-2.7.0}/.github/workflows/ci.yml +8 -8
  2. netprotocols-2.7.0/.github/workflows/codeql.yml +100 -0
  3. {netprotocols-2.6.0 → netprotocols-2.7.0}/.github/workflows/fuzz.yml +4 -4
  4. {netprotocols-2.6.0 → netprotocols-2.7.0}/.github/workflows/mutation.yml +2 -2
  5. {netprotocols-2.6.0 → netprotocols-2.7.0}/.github/workflows/release.yml +3 -3
  6. {netprotocols-2.6.0 → netprotocols-2.7.0}/.github/workflows/scorecard.yml +2 -2
  7. {netprotocols-2.6.0 → netprotocols-2.7.0}/ARCHITECTURE.md +220 -10
  8. {netprotocols-2.6.0 → netprotocols-2.7.0}/CHANGELOG.md +28 -0
  9. {netprotocols-2.6.0 → netprotocols-2.7.0}/CONTRIBUTING.md +4 -2
  10. {netprotocols-2.6.0 → netprotocols-2.7.0}/PKG-INFO +39 -23
  11. {netprotocols-2.6.0 → netprotocols-2.7.0}/README.md +38 -22
  12. {netprotocols-2.6.0 → netprotocols-2.7.0}/docs/CLAIMS.md +228 -30
  13. {netprotocols-2.6.0 → netprotocols-2.7.0}/pyproject.toml +16 -4
  14. netprotocols-2.7.0/scripts/capture_fixtures_hsrp.sh +305 -0
  15. netprotocols-2.7.0/scripts/capture_fixtures_ntp.sh +346 -0
  16. netprotocols-2.7.0/scripts/capture_fixtures_radius.sh +338 -0
  17. netprotocols-2.7.0/scripts/capture_fixtures_stun.sh +530 -0
  18. netprotocols-2.7.0/scripts/capture_fixtures_tftp.sh +312 -0
  19. {netprotocols-2.6.0 → netprotocols-2.7.0}/scripts/check_fixtures.py +488 -8
  20. {netprotocols-2.6.0 → netprotocols-2.7.0}/scripts/check_mutmut_survivors.py +1 -1
  21. netprotocols-2.7.0/scripts/filter_codeql_sarif.py +348 -0
  22. {netprotocols-2.6.0 → netprotocols-2.7.0}/src/netprotocols/__init__.py +16 -1
  23. {netprotocols-2.6.0 → netprotocols-2.7.0}/src/netprotocols/_base.py +20 -6
  24. {netprotocols-2.6.0 → netprotocols-2.7.0}/src/netprotocols/_defaults.py +36 -0
  25. {netprotocols-2.6.0 → netprotocols-2.7.0}/src/netprotocols/_tlv.py +20 -3
  26. {netprotocols-2.6.0 → netprotocols-2.7.0}/src/netprotocols/checksum.py +172 -24
  27. {netprotocols-2.6.0 → netprotocols-2.7.0}/src/netprotocols/flow.py +22 -6
  28. {netprotocols-2.6.0 → netprotocols-2.7.0}/src/netprotocols/layer3/ipv6_ext.py +132 -2
  29. {netprotocols-2.6.0 → netprotocols-2.7.0}/src/netprotocols/layer4/sctp.py +4 -3
  30. {netprotocols-2.6.0 → netprotocols-2.7.0}/src/netprotocols/layer7/__init__.py +15 -0
  31. netprotocols-2.7.0/src/netprotocols/layer7/hsrp.py +395 -0
  32. netprotocols-2.7.0/src/netprotocols/layer7/ntp.py +757 -0
  33. netprotocols-2.7.0/src/netprotocols/layer7/radius.py +559 -0
  34. netprotocols-2.7.0/src/netprotocols/layer7/stun.py +784 -0
  35. netprotocols-2.7.0/src/netprotocols/layer7/tftp.py +551 -0
  36. {netprotocols-2.6.0 → netprotocols-2.7.0}/src/netprotocols/packet.py +89 -23
  37. {netprotocols-2.6.0 → netprotocols-2.7.0}/src/netprotocols/walk.py +3 -0
  38. {netprotocols-2.6.0 → netprotocols-2.7.0}/tests/conftest.py +60 -0
  39. {netprotocols-2.6.0 → netprotocols-2.7.0}/tests/fixtures/MANIFEST.md +259 -8
  40. netprotocols-2.7.0/tests/fixtures/codeql/python.sarif +1 -0
  41. netprotocols-2.7.0/tests/fixtures/hsrp.pcap +0 -0
  42. netprotocols-2.7.0/tests/fixtures/ntp.pcap +0 -0
  43. netprotocols-2.7.0/tests/fixtures/radius.pcap +0 -0
  44. netprotocols-2.7.0/tests/fixtures/staging/hsrp.pcap +0 -0
  45. netprotocols-2.7.0/tests/fixtures/staging/ntp.pcap +0 -0
  46. netprotocols-2.7.0/tests/fixtures/staging/radius.pcap +0 -0
  47. netprotocols-2.7.0/tests/fixtures/staging/stun/stun_rfc3489.pcap +0 -0
  48. netprotocols-2.7.0/tests/fixtures/staging/stun.pcap +0 -0
  49. netprotocols-2.7.0/tests/fixtures/staging/tftp.pcap +0 -0
  50. netprotocols-2.7.0/tests/fixtures/stun/stun_rfc3489.pcap +0 -0
  51. netprotocols-2.7.0/tests/fixtures/stun.pcap +0 -0
  52. netprotocols-2.7.0/tests/fixtures/tftp.pcap +0 -0
  53. {netprotocols-2.6.0 → netprotocols-2.7.0}/tests/strategies.py +143 -0
  54. {netprotocols-2.6.0 → netprotocols-2.7.0}/tests/test_checksum.py +1076 -4
  55. {netprotocols-2.6.0 → netprotocols-2.7.0}/tests/test_contract.py +90 -0
  56. {netprotocols-2.6.0 → netprotocols-2.7.0}/tests/test_corpus.py +10 -0
  57. {netprotocols-2.6.0 → netprotocols-2.7.0}/tests/test_docs.py +5 -0
  58. netprotocols-2.7.0/tests/test_filter_codeql_sarif.py +685 -0
  59. {netprotocols-2.6.0 → netprotocols-2.7.0}/tests/test_flow.py +2 -3
  60. {netprotocols-2.6.0 → netprotocols-2.7.0}/tests/test_fuzz.py +538 -0
  61. netprotocols-2.7.0/tests/test_hsrp.py +1257 -0
  62. netprotocols-2.7.0/tests/test_ipv6_routing_final_dst.py +200 -0
  63. netprotocols-2.7.0/tests/test_ntp.py +897 -0
  64. netprotocols-2.7.0/tests/test_radius.py +853 -0
  65. {netprotocols-2.6.0 → netprotocols-2.7.0}/tests/test_sctp.py +38 -0
  66. netprotocols-2.7.0/tests/test_stun.py +1103 -0
  67. netprotocols-2.7.0/tests/test_tftp.py +732 -0
  68. {netprotocols-2.6.0 → netprotocols-2.7.0}/tests/test_tlv.py +93 -0
  69. {netprotocols-2.6.0 → netprotocols-2.7.0}/tests/test_workflows.py +277 -6
  70. {netprotocols-2.6.0 → netprotocols-2.7.0}/uv.lock +103 -98
  71. netprotocols-2.6.0/.github/workflows/codeql.yml +0 -50
  72. {netprotocols-2.6.0 → netprotocols-2.7.0}/.claude/skills/steward/SKILL.md +0 -0
  73. {netprotocols-2.6.0 → netprotocols-2.7.0}/.gitattributes +0 -0
  74. {netprotocols-2.6.0 → netprotocols-2.7.0}/.github/CODEOWNERS +0 -0
  75. {netprotocols-2.6.0 → netprotocols-2.7.0}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
  76. {netprotocols-2.6.0 → netprotocols-2.7.0}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  77. {netprotocols-2.6.0 → netprotocols-2.7.0}/.github/ISSUE_TEMPLATE/protocol_request.yml +0 -0
  78. {netprotocols-2.6.0 → netprotocols-2.7.0}/.github/dependabot.yml +0 -0
  79. {netprotocols-2.6.0 → netprotocols-2.7.0}/.github/pull_request_template.md +0 -0
  80. {netprotocols-2.6.0 → netprotocols-2.7.0}/.github/workflows/docs-lint.yml +0 -0
  81. {netprotocols-2.6.0 → netprotocols-2.7.0}/.github/workflows/pr-title-lint.yml +0 -0
  82. {netprotocols-2.6.0 → netprotocols-2.7.0}/.gitignore +0 -0
  83. {netprotocols-2.6.0 → netprotocols-2.7.0}/LICENSE +0 -0
  84. {netprotocols-2.6.0 → netprotocols-2.7.0}/SECURITY.md +0 -0
  85. {netprotocols-2.6.0 → netprotocols-2.7.0}/benchmarks/baseline.json +0 -0
  86. {netprotocols-2.6.0 → netprotocols-2.7.0}/scripts/_pcap.py +0 -0
  87. {netprotocols-2.6.0 → netprotocols-2.7.0}/scripts/benchmark.py +0 -0
  88. {netprotocols-2.6.0 → netprotocols-2.7.0}/scripts/benchmark_encode.py +0 -0
  89. {netprotocols-2.6.0 → netprotocols-2.7.0}/scripts/benchmark_import.py +0 -0
  90. {netprotocols-2.6.0 → netprotocols-2.7.0}/scripts/capture_fixtures.sh +0 -0
  91. {netprotocols-2.6.0 → netprotocols-2.7.0}/scripts/capture_fixtures_bgp.sh +0 -0
  92. {netprotocols-2.6.0 → netprotocols-2.7.0}/scripts/capture_fixtures_dhcp.sh +0 -0
  93. {netprotocols-2.6.0 → netprotocols-2.7.0}/scripts/capture_fixtures_dns_tcp.sh +0 -0
  94. {netprotocols-2.6.0 → netprotocols-2.7.0}/scripts/capture_fixtures_gre.sh +0 -0
  95. {netprotocols-2.6.0 → netprotocols-2.7.0}/scripts/capture_fixtures_http.sh +0 -0
  96. {netprotocols-2.6.0 → netprotocols-2.7.0}/scripts/capture_fixtures_igmp.sh +0 -0
  97. {netprotocols-2.6.0 → netprotocols-2.7.0}/scripts/capture_fixtures_ipip.sh +0 -0
  98. {netprotocols-2.6.0 → netprotocols-2.7.0}/scripts/capture_fixtures_ipsec.sh +0 -0
  99. {netprotocols-2.6.0 → netprotocols-2.7.0}/scripts/capture_fixtures_llc.sh +0 -0
  100. {netprotocols-2.6.0 → netprotocols-2.7.0}/scripts/capture_fixtures_lldp.sh +0 -0
  101. {netprotocols-2.6.0 → netprotocols-2.7.0}/scripts/capture_fixtures_mpls.sh +0 -0
  102. {netprotocols-2.6.0 → netprotocols-2.7.0}/scripts/capture_fixtures_ospf.sh +0 -0
  103. {netprotocols-2.6.0 → netprotocols-2.7.0}/scripts/capture_fixtures_pim.sh +0 -0
  104. {netprotocols-2.6.0 → netprotocols-2.7.0}/scripts/capture_fixtures_rip.sh +0 -0
  105. {netprotocols-2.6.0 → netprotocols-2.7.0}/scripts/capture_fixtures_sctp.sh +0 -0
  106. {netprotocols-2.6.0 → netprotocols-2.7.0}/scripts/capture_fixtures_supplement.sh +0 -0
  107. {netprotocols-2.6.0 → netprotocols-2.7.0}/scripts/capture_fixtures_vlan.sh +0 -0
  108. {netprotocols-2.6.0 → netprotocols-2.7.0}/scripts/capture_fixtures_vrrp.sh +0 -0
  109. {netprotocols-2.6.0 → netprotocols-2.7.0}/scripts/capture_fixtures_vxlan.sh +0 -0
  110. {netprotocols-2.6.0 → netprotocols-2.7.0}/scripts/extract_http_streams.py +0 -0
  111. {netprotocols-2.6.0 → netprotocols-2.7.0}/scripts/ipsec_fixture_packets.py +0 -0
  112. {netprotocols-2.6.0 → netprotocols-2.7.0}/scripts/llc_fixture_frames.py +0 -0
  113. {netprotocols-2.6.0 → netprotocols-2.7.0}/scripts/mpls_fixture_packets.py +0 -0
  114. {netprotocols-2.6.0 → netprotocols-2.7.0}/scripts/mutmut_survivors.txt +0 -0
  115. {netprotocols-2.6.0 → netprotocols-2.7.0}/scripts/pyodide/check_in_pyodide.py +0 -0
  116. {netprotocols-2.6.0 → netprotocols-2.7.0}/scripts/pyodide/package-lock.json +0 -0
  117. {netprotocols-2.6.0 → netprotocols-2.7.0}/scripts/pyodide/package.json +0 -0
  118. {netprotocols-2.6.0 → netprotocols-2.7.0}/scripts/pyodide/run_in_pyodide.mjs +0 -0
  119. {netprotocols-2.6.0 → netprotocols-2.7.0}/src/netprotocols/_enums.py +0 -0
  120. {netprotocols-2.6.0 → netprotocols-2.7.0}/src/netprotocols/layer2/__init__.py +0 -0
  121. {netprotocols-2.6.0 → netprotocols-2.7.0}/src/netprotocols/layer2/arp.py +0 -0
  122. {netprotocols-2.6.0 → netprotocols-2.7.0}/src/netprotocols/layer2/cdp.py +0 -0
  123. {netprotocols-2.6.0 → netprotocols-2.7.0}/src/netprotocols/layer2/ethernet.py +0 -0
  124. {netprotocols-2.6.0 → netprotocols-2.7.0}/src/netprotocols/layer2/llc.py +0 -0
  125. {netprotocols-2.6.0 → netprotocols-2.7.0}/src/netprotocols/layer2/lldp.py +0 -0
  126. {netprotocols-2.6.0 → netprotocols-2.7.0}/src/netprotocols/layer2/mpls.py +0 -0
  127. {netprotocols-2.6.0 → netprotocols-2.7.0}/src/netprotocols/layer2/stp.py +0 -0
  128. {netprotocols-2.6.0 → netprotocols-2.7.0}/src/netprotocols/layer2/vlan.py +0 -0
  129. {netprotocols-2.6.0 → netprotocols-2.7.0}/src/netprotocols/layer3/__init__.py +0 -0
  130. {netprotocols-2.6.0 → netprotocols-2.7.0}/src/netprotocols/layer3/ah.py +0 -0
  131. {netprotocols-2.6.0 → netprotocols-2.7.0}/src/netprotocols/layer3/esp.py +0 -0
  132. {netprotocols-2.6.0 → netprotocols-2.7.0}/src/netprotocols/layer3/gre.py +0 -0
  133. {netprotocols-2.6.0 → netprotocols-2.7.0}/src/netprotocols/layer3/icmp.py +0 -0
  134. {netprotocols-2.6.0 → netprotocols-2.7.0}/src/netprotocols/layer3/igmp.py +0 -0
  135. {netprotocols-2.6.0 → netprotocols-2.7.0}/src/netprotocols/layer3/ip.py +0 -0
  136. {netprotocols-2.6.0 → netprotocols-2.7.0}/src/netprotocols/layer3/ospf.py +0 -0
  137. {netprotocols-2.6.0 → netprotocols-2.7.0}/src/netprotocols/layer3/pim.py +0 -0
  138. {netprotocols-2.6.0 → netprotocols-2.7.0}/src/netprotocols/layer3/vrrp.py +0 -0
  139. {netprotocols-2.6.0 → netprotocols-2.7.0}/src/netprotocols/layer4/__init__.py +0 -0
  140. {netprotocols-2.6.0 → netprotocols-2.7.0}/src/netprotocols/layer4/_ports.py +0 -0
  141. {netprotocols-2.6.0 → netprotocols-2.7.0}/src/netprotocols/layer4/tcp.py +0 -0
  142. {netprotocols-2.6.0 → netprotocols-2.7.0}/src/netprotocols/layer4/udp.py +0 -0
  143. {netprotocols-2.6.0 → netprotocols-2.7.0}/src/netprotocols/layer7/bgp.py +0 -0
  144. {netprotocols-2.6.0 → netprotocols-2.7.0}/src/netprotocols/layer7/dhcp.py +0 -0
  145. {netprotocols-2.6.0 → netprotocols-2.7.0}/src/netprotocols/layer7/dns.py +0 -0
  146. {netprotocols-2.6.0 → netprotocols-2.7.0}/src/netprotocols/layer7/http.py +0 -0
  147. {netprotocols-2.6.0 → netprotocols-2.7.0}/src/netprotocols/layer7/rip.py +0 -0
  148. {netprotocols-2.6.0 → netprotocols-2.7.0}/src/netprotocols/layer7/vxlan.py +0 -0
  149. {netprotocols-2.6.0 → netprotocols-2.7.0}/src/netprotocols/pcap.py +0 -0
  150. {netprotocols-2.6.0 → netprotocols-2.7.0}/src/netprotocols/py.typed +0 -0
  151. {netprotocols-2.6.0 → netprotocols-2.7.0}/src/netprotocols/registry.py +0 -0
  152. {netprotocols-2.6.0 → netprotocols-2.7.0}/src/netprotocols/utils/__init__.py +0 -0
  153. {netprotocols-2.6.0 → netprotocols-2.7.0}/src/netprotocols/utils/exceptions.py +0 -0
  154. {netprotocols-2.6.0 → netprotocols-2.7.0}/src/netprotocols/utils/ipv4.py +0 -0
  155. {netprotocols-2.6.0 → netprotocols-2.7.0}/src/netprotocols/utils/mac.py +0 -0
  156. {netprotocols-2.6.0 → netprotocols-2.7.0}/tests/fixtures/ah.pcap +0 -0
  157. {netprotocols-2.6.0 → netprotocols-2.7.0}/tests/fixtures/arp_exchange.pcap +0 -0
  158. {netprotocols-2.6.0 → netprotocols-2.7.0}/tests/fixtures/bgp/bgp_session.pcap +0 -0
  159. {netprotocols-2.6.0 → netprotocols-2.7.0}/tests/fixtures/bgp.pcap +0 -0
  160. {netprotocols-2.6.0 → netprotocols-2.7.0}/tests/fixtures/dhcp.pcap +0 -0
  161. {netprotocols-2.6.0 → netprotocols-2.7.0}/tests/fixtures/dns_tcp.pcap +0 -0
  162. {netprotocols-2.6.0 → netprotocols-2.7.0}/tests/fixtures/esp.pcap +0 -0
  163. {netprotocols-2.6.0 → netprotocols-2.7.0}/tests/fixtures/gre.pcap +0 -0
  164. {netprotocols-2.6.0 → netprotocols-2.7.0}/tests/fixtures/http/01.request +0 -0
  165. {netprotocols-2.6.0 → netprotocols-2.7.0}/tests/fixtures/http/01.response +0 -0
  166. {netprotocols-2.6.0 → netprotocols-2.7.0}/tests/fixtures/http/02.request +0 -0
  167. {netprotocols-2.6.0 → netprotocols-2.7.0}/tests/fixtures/http/02.response +0 -0
  168. {netprotocols-2.6.0 → netprotocols-2.7.0}/tests/fixtures/http/03.request +0 -0
  169. {netprotocols-2.6.0 → netprotocols-2.7.0}/tests/fixtures/http/03.response +0 -0
  170. {netprotocols-2.6.0 → netprotocols-2.7.0}/tests/fixtures/http/04.request +0 -0
  171. {netprotocols-2.6.0 → netprotocols-2.7.0}/tests/fixtures/http/04.response +0 -0
  172. {netprotocols-2.6.0 → netprotocols-2.7.0}/tests/fixtures/http/05.request +0 -0
  173. {netprotocols-2.6.0 → netprotocols-2.7.0}/tests/fixtures/http/05.response +0 -0
  174. {netprotocols-2.6.0 → netprotocols-2.7.0}/tests/fixtures/http/06.request +0 -0
  175. {netprotocols-2.6.0 → netprotocols-2.7.0}/tests/fixtures/http/06.response +0 -0
  176. {netprotocols-2.6.0 → netprotocols-2.7.0}/tests/fixtures/http/07.request +0 -0
  177. {netprotocols-2.6.0 → netprotocols-2.7.0}/tests/fixtures/http/07.response +0 -0
  178. {netprotocols-2.6.0 → netprotocols-2.7.0}/tests/fixtures/http/http.pcap +0 -0
  179. {netprotocols-2.6.0 → netprotocols-2.7.0}/tests/fixtures/icmpv4_echo_lo.pcap +0 -0
  180. {netprotocols-2.6.0 → netprotocols-2.7.0}/tests/fixtures/icmpv4_external.pcap +0 -0
  181. {netprotocols-2.6.0 → netprotocols-2.7.0}/tests/fixtures/icmpv4_ttl_exceeded.pcap +0 -0
  182. {netprotocols-2.6.0 → netprotocols-2.7.0}/tests/fixtures/icmpv6_echo_lo.pcap +0 -0
  183. {netprotocols-2.6.0 → netprotocols-2.7.0}/tests/fixtures/igmp.pcap +0 -0
  184. {netprotocols-2.6.0 → netprotocols-2.7.0}/tests/fixtures/ipip.pcap +0 -0
  185. {netprotocols-2.6.0 → netprotocols-2.7.0}/tests/fixtures/ipv4_fragments.pcap +0 -0
  186. {netprotocols-2.6.0 → netprotocols-2.7.0}/tests/fixtures/ipv6_fragments.pcap +0 -0
  187. {netprotocols-2.6.0 → netprotocols-2.7.0}/tests/fixtures/ipv6_mld.pcap +0 -0
  188. {netprotocols-2.6.0 → netprotocols-2.7.0}/tests/fixtures/ipv6_ndp_mld.pcap +0 -0
  189. {netprotocols-2.6.0 → netprotocols-2.7.0}/tests/fixtures/llc.pcap +0 -0
  190. {netprotocols-2.6.0 → netprotocols-2.7.0}/tests/fixtures/lldp.pcap +0 -0
  191. {netprotocols-2.6.0 → netprotocols-2.7.0}/tests/fixtures/mpls/mpls_snaplen.pcap +0 -0
  192. {netprotocols-2.6.0 → netprotocols-2.7.0}/tests/fixtures/mpls.pcap +0 -0
  193. {netprotocols-2.6.0 → netprotocols-2.7.0}/tests/fixtures/ospf.pcap +0 -0
  194. {netprotocols-2.6.0 → netprotocols-2.7.0}/tests/fixtures/pim.pcap +0 -0
  195. {netprotocols-2.6.0 → netprotocols-2.7.0}/tests/fixtures/rip.pcap +0 -0
  196. {netprotocols-2.6.0 → netprotocols-2.7.0}/tests/fixtures/sctp.pcap +0 -0
  197. {netprotocols-2.6.0 → netprotocols-2.7.0}/tests/fixtures/staging/ah.pcap +0 -0
  198. {netprotocols-2.6.0 → netprotocols-2.7.0}/tests/fixtures/staging/arp_exchange.pcap +0 -0
  199. {netprotocols-2.6.0 → netprotocols-2.7.0}/tests/fixtures/staging/bgp/bgp_session.pcap +0 -0
  200. {netprotocols-2.6.0 → netprotocols-2.7.0}/tests/fixtures/staging/bgp.pcap +0 -0
  201. {netprotocols-2.6.0 → netprotocols-2.7.0}/tests/fixtures/staging/dhcp.pcap +0 -0
  202. {netprotocols-2.6.0 → netprotocols-2.7.0}/tests/fixtures/staging/dns_tcp.pcap +0 -0
  203. {netprotocols-2.6.0 → netprotocols-2.7.0}/tests/fixtures/staging/esp.pcap +0 -0
  204. {netprotocols-2.6.0 → netprotocols-2.7.0}/tests/fixtures/staging/gre.pcap +0 -0
  205. {netprotocols-2.6.0 → netprotocols-2.7.0}/tests/fixtures/staging/http.pcap +0 -0
  206. {netprotocols-2.6.0 → netprotocols-2.7.0}/tests/fixtures/staging/icmpv4_echo_lo.pcap +0 -0
  207. {netprotocols-2.6.0 → netprotocols-2.7.0}/tests/fixtures/staging/icmpv4_external.pcap +0 -0
  208. {netprotocols-2.6.0 → netprotocols-2.7.0}/tests/fixtures/staging/icmpv4_ttl_exceeded.pcap +0 -0
  209. {netprotocols-2.6.0 → netprotocols-2.7.0}/tests/fixtures/staging/icmpv6_echo_lo.pcap +0 -0
  210. {netprotocols-2.6.0 → netprotocols-2.7.0}/tests/fixtures/staging/igmp.pcap +0 -0
  211. {netprotocols-2.6.0 → netprotocols-2.7.0}/tests/fixtures/staging/ipip.pcap +0 -0
  212. {netprotocols-2.6.0 → netprotocols-2.7.0}/tests/fixtures/staging/ipv4_fragments.pcap +0 -0
  213. {netprotocols-2.6.0 → netprotocols-2.7.0}/tests/fixtures/staging/ipv6_fragments.pcap +0 -0
  214. {netprotocols-2.6.0 → netprotocols-2.7.0}/tests/fixtures/staging/ipv6_mld.pcap +0 -0
  215. {netprotocols-2.6.0 → netprotocols-2.7.0}/tests/fixtures/staging/ipv6_ndp_mld.pcap +0 -0
  216. {netprotocols-2.6.0 → netprotocols-2.7.0}/tests/fixtures/staging/llc.pcap +0 -0
  217. {netprotocols-2.6.0 → netprotocols-2.7.0}/tests/fixtures/staging/lldp.pcap +0 -0
  218. {netprotocols-2.6.0 → netprotocols-2.7.0}/tests/fixtures/staging/mpls/mpls_snaplen.pcap +0 -0
  219. {netprotocols-2.6.0 → netprotocols-2.7.0}/tests/fixtures/staging/mpls.pcap +0 -0
  220. {netprotocols-2.6.0 → netprotocols-2.7.0}/tests/fixtures/staging/ospf.pcap +0 -0
  221. {netprotocols-2.6.0 → netprotocols-2.7.0}/tests/fixtures/staging/pim.pcap +0 -0
  222. {netprotocols-2.6.0 → netprotocols-2.7.0}/tests/fixtures/staging/rip.pcap +0 -0
  223. {netprotocols-2.6.0 → netprotocols-2.7.0}/tests/fixtures/staging/sctp.pcap +0 -0
  224. {netprotocols-2.6.0 → netprotocols-2.7.0}/tests/fixtures/staging/tcp_http.pcap +0 -0
  225. {netprotocols-2.6.0 → netprotocols-2.7.0}/tests/fixtures/staging/tcp_https.pcap +0 -0
  226. {netprotocols-2.6.0 → netprotocols-2.7.0}/tests/fixtures/staging/udp_dns.pcap +0 -0
  227. {netprotocols-2.6.0 → netprotocols-2.7.0}/tests/fixtures/staging/vlan_icmp.pcap +0 -0
  228. {netprotocols-2.6.0 → netprotocols-2.7.0}/tests/fixtures/staging/vrrp.pcap +0 -0
  229. {netprotocols-2.6.0 → netprotocols-2.7.0}/tests/fixtures/staging/vxlan.pcap +0 -0
  230. {netprotocols-2.6.0 → netprotocols-2.7.0}/tests/fixtures/tcp_http.pcap +0 -0
  231. {netprotocols-2.6.0 → netprotocols-2.7.0}/tests/fixtures/tcp_https.pcap +0 -0
  232. {netprotocols-2.6.0 → netprotocols-2.7.0}/tests/fixtures/udp_dns.pcap +0 -0
  233. {netprotocols-2.6.0 → netprotocols-2.7.0}/tests/fixtures/vlan_icmp.pcap +0 -0
  234. {netprotocols-2.6.0 → netprotocols-2.7.0}/tests/fixtures/vrrp.pcap +0 -0
  235. {netprotocols-2.6.0 → netprotocols-2.7.0}/tests/fixtures/vxlan.pcap +0 -0
  236. {netprotocols-2.6.0 → netprotocols-2.7.0}/tests/fuzz/fuzz_decode_frame.py +0 -0
  237. {netprotocols-2.6.0 → netprotocols-2.7.0}/tests/fuzz/fuzz_http_decode.py +0 -0
  238. {netprotocols-2.6.0 → netprotocols-2.7.0}/tests/test_ah.py +0 -0
  239. {netprotocols-2.6.0 → netprotocols-2.7.0}/tests/test_arp.py +0 -0
  240. {netprotocols-2.6.0 → netprotocols-2.7.0}/tests/test_bgp.py +0 -0
  241. {netprotocols-2.6.0 → netprotocols-2.7.0}/tests/test_cdp.py +0 -0
  242. {netprotocols-2.6.0 → netprotocols-2.7.0}/tests/test_dhcp.py +0 -0
  243. {netprotocols-2.6.0 → netprotocols-2.7.0}/tests/test_diagnostics.py +0 -0
  244. {netprotocols-2.6.0 → netprotocols-2.7.0}/tests/test_dns.py +0 -0
  245. {netprotocols-2.6.0 → netprotocols-2.7.0}/tests/test_enums.py +0 -0
  246. {netprotocols-2.6.0 → netprotocols-2.7.0}/tests/test_esp.py +0 -0
  247. {netprotocols-2.6.0 → netprotocols-2.7.0}/tests/test_ethernet.py +0 -0
  248. {netprotocols-2.6.0 → netprotocols-2.7.0}/tests/test_exports.py +0 -0
  249. {netprotocols-2.6.0 → netprotocols-2.7.0}/tests/test_extract_http_streams.py +0 -0
  250. {netprotocols-2.6.0 → netprotocols-2.7.0}/tests/test_gre.py +0 -0
  251. {netprotocols-2.6.0 → netprotocols-2.7.0}/tests/test_http.py +0 -0
  252. {netprotocols-2.6.0 → netprotocols-2.7.0}/tests/test_icmp.py +0 -0
  253. {netprotocols-2.6.0 → netprotocols-2.7.0}/tests/test_igmp.py +0 -0
  254. {netprotocols-2.6.0 → netprotocols-2.7.0}/tests/test_ip.py +0 -0
  255. {netprotocols-2.6.0 → netprotocols-2.7.0}/tests/test_ipip.py +0 -0
  256. {netprotocols-2.6.0 → netprotocols-2.7.0}/tests/test_ipv6_ext.py +0 -0
  257. {netprotocols-2.6.0 → netprotocols-2.7.0}/tests/test_llc.py +0 -0
  258. {netprotocols-2.6.0 → netprotocols-2.7.0}/tests/test_lldp.py +0 -0
  259. {netprotocols-2.6.0 → netprotocols-2.7.0}/tests/test_mpls.py +0 -0
  260. {netprotocols-2.6.0 → netprotocols-2.7.0}/tests/test_mutmut_gate.py +0 -0
  261. {netprotocols-2.6.0 → netprotocols-2.7.0}/tests/test_ospf.py +0 -0
  262. {netprotocols-2.6.0 → netprotocols-2.7.0}/tests/test_packet.py +0 -0
  263. {netprotocols-2.6.0 → netprotocols-2.7.0}/tests/test_pattern_matching.py +0 -0
  264. {netprotocols-2.6.0 → netprotocols-2.7.0}/tests/test_pcap.py +0 -0
  265. {netprotocols-2.6.0 → netprotocols-2.7.0}/tests/test_pim.py +0 -0
  266. {netprotocols-2.6.0 → netprotocols-2.7.0}/tests/test_registry.py +0 -0
  267. {netprotocols-2.6.0 → netprotocols-2.7.0}/tests/test_rip.py +0 -0
  268. {netprotocols-2.6.0 → netprotocols-2.7.0}/tests/test_stp.py +0 -0
  269. {netprotocols-2.6.0 → netprotocols-2.7.0}/tests/test_tcp.py +0 -0
  270. {netprotocols-2.6.0 → netprotocols-2.7.0}/tests/test_udp.py +0 -0
  271. {netprotocols-2.6.0 → netprotocols-2.7.0}/tests/test_utils.py +0 -0
  272. {netprotocols-2.6.0 → netprotocols-2.7.0}/tests/test_version.py +0 -0
  273. {netprotocols-2.6.0 → netprotocols-2.7.0}/tests/test_vlan.py +0 -0
  274. {netprotocols-2.6.0 → netprotocols-2.7.0}/tests/test_vrrp.py +0 -0
  275. {netprotocols-2.6.0 → netprotocols-2.7.0}/tests/test_vxlan.py +0 -0
  276. {netprotocols-2.6.0 → netprotocols-2.7.0}/tests/test_walk.py +0 -0
@@ -33,7 +33,7 @@ jobs:
33
33
  runs-on: ubuntu-latest
34
34
  steps:
35
35
  - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
36
- - uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0
36
+ - uses: astral-sh/setup-uv@07a047e608db84b3d8c06cd9a7c7dcc9c355c18e # v10.3.2
37
37
  with:
38
38
  python-version: "3.12"
39
39
  - run: uv run ruff check
@@ -43,7 +43,7 @@ jobs:
43
43
  runs-on: ubuntu-latest
44
44
  steps:
45
45
  - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
46
- - uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0
46
+ - uses: astral-sh/setup-uv@07a047e608db84b3d8c06cd9a7c7dcc9c355c18e # v10.3.2
47
47
  with:
48
48
  python-version: "3.12"
49
49
  - run: uv run mypy
@@ -55,7 +55,7 @@ jobs:
55
55
  python-version: ["3.12", "3.13", "3.14"]
56
56
  steps:
57
57
  - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
58
- - uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0
58
+ - uses: astral-sh/setup-uv@07a047e608db84b3d8c06cd9a7c7dcc9c355c18e # v10.3.2
59
59
  with:
60
60
  python-version: ${{ matrix.python-version }}
61
61
  - run: uv run --frozen pytest --cov=netprotocols --cov-report=term
@@ -70,7 +70,7 @@ jobs:
70
70
  runs-on: ubuntu-latest
71
71
  steps:
72
72
  - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
73
- - uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0
73
+ - uses: astral-sh/setup-uv@07a047e608db84b3d8c06cd9a7c7dcc9c355c18e # v10.3.2
74
74
  with:
75
75
  python-version: "3.12"
76
76
  - name: Export dev dependencies
@@ -86,7 +86,7 @@ jobs:
86
86
  runs-on: ubuntu-latest
87
87
  steps:
88
88
  - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
89
- - uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0
89
+ - uses: astral-sh/setup-uv@07a047e608db84b3d8c06cd9a7c7dcc9c355c18e # v10.3.2
90
90
  - run: uv build
91
91
  - run: uv run --isolated --no-project --with dist/*.whl python -c "import netprotocols; print(netprotocols.__version__)"
92
92
  # Builds are supposed to be a pure function of the source tree, so
@@ -119,11 +119,11 @@ jobs:
119
119
  runs-on: ubuntu-latest
120
120
  steps:
121
121
  - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
122
- - uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0
122
+ - uses: astral-sh/setup-uv@07a047e608db84b3d8c06cd9a7c7dcc9c355c18e # v10.3.2
123
123
  with:
124
124
  python-version: "3.12"
125
125
  - run: uv build
126
- - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
126
+ - uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
127
127
  with:
128
128
  node-version: "22"
129
129
  - run: npm ci
@@ -151,7 +151,7 @@ jobs:
151
151
  runs-on: ubuntu-latest
152
152
  steps:
153
153
  - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
154
- - uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0
154
+ - uses: astral-sh/setup-uv@07a047e608db84b3d8c06cd9a7c7dcc9c355c18e # v10.3.2
155
155
  with:
156
156
  python-version: "3.12"
157
157
  - name: Corpus decode throughput
@@ -0,0 +1,100 @@
1
+ name: CodeQL
2
+
3
+ # Advanced-setup workflow (a checked-in file) rather than GitHub's
4
+ # built-in "default setup", so this analysis lives in version control
5
+ # alongside the rest of this repository's CI (ci.yml, fuzz.yml,
6
+ # release.yml, mutation.yml, scorecard.yml) and its action pins are
7
+ # reviewable the same way theirs are. Default setup is configured
8
+ # through repository settings instead of a file, so it isn't available
9
+ # to author from here.
10
+ on:
11
+ push:
12
+ branches: [master]
13
+ pull_request:
14
+ branches: [master]
15
+ schedule:
16
+ # Arbitrary; weekly on Sundays, clear of fuzz.yml's and mutation.yml's
17
+ # daily 03:00/04:00 UTC slots and scorecard.yml's own Sunday 05:00
18
+ # UTC slot.
19
+ - cron: "0 6 * * 0"
20
+
21
+ permissions:
22
+ contents: read
23
+
24
+ concurrency:
25
+ group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
26
+ cancel-in-progress: true
27
+
28
+ jobs:
29
+ analyze:
30
+ name: Analyze (python)
31
+ runs-on: ubuntu-latest
32
+ permissions:
33
+ actions: read
34
+ contents: read
35
+ security-events: write
36
+ steps:
37
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
38
+
39
+ # Python is interpreted, so there is nothing for autobuild (or a
40
+ # manual build step) to do: build-mode: none has CodeQL extract
41
+ # the database straight from source instead. This is the build
42
+ # mode the CodeQL Action's own docs recommend for interpreted
43
+ # languages, and it sidesteps the question of an autobuild step
44
+ # fitting this project's uv-based build at all.
45
+ - uses: github/codeql-action/init@9b3254085494b55a0f7c4c2b3d2d52cc712ce370 # v4.39.1
46
+ with:
47
+ languages: python
48
+ build-mode: none
49
+
50
+ # analyze writes the SARIF without uploading it, the filter step
51
+ # drops one query's results on two paths, and upload-sarif uploads
52
+ # what is left. py/weak-sensitive-data-hashing flags the MD5 that
53
+ # RADIUS (#265) and STUN (#267) mandate, which their tests and
54
+ # scripts/check_fixtures.py compute over published test secrets
55
+ # with usedforsecurity=False to verify captured traffic.
56
+ # The CodeQL config can't scope a query to paths: query-filters
57
+ # drops it everywhere, src/ included, and paths-ignore drops the
58
+ # paths from every query. So the filter removes only that query's
59
+ # results whose primary location is under tests/ or scripts/; the
60
+ # query still runs on src/, every other query on all three.
61
+ #
62
+ # upload: failure-only is the action's documented mode for a
63
+ # workflow that post-processes the SARIF: analyze uploads nothing
64
+ # when it succeeds. It still marks the analysis as completed, so
65
+ # init's post step does not upload a failed-run SARIF for a later
66
+ # step's failure either: if the filter or upload-sarif fails, the
67
+ # job goes red and no new analysis replaces the previous one
68
+ # (upload-sarif can fail after uploading, while it waits for
69
+ # processing). That fails closed; unfiltered SARIF is never
70
+ # uploaded. (Any value the action does not recognize means
71
+ # `always`, which would upload before the filter runs.)
72
+ #
73
+ # Neither analyze nor upload-sarif sets `category`, and that is
74
+ # what keeps the analysis the same one: with no category the CLI
75
+ # writes no runs[].automationDetails, and both actions then fill
76
+ # it in from the workflow path, job id and matrix,
77
+ # ".github/workflows/codeql.yml:analyze/", the same in both
78
+ # steps of this job. Existing alerts keep tracking (fixed ones
79
+ # close, the rest stay) rather than reappearing under a new
80
+ # category. Setting one on either step alone would start a second
81
+ # analysis next to the first.
82
+ - id: analyze
83
+ uses: github/codeql-action/analyze@9b3254085494b55a0f7c4c2b3d2d52cc712ce370 # v4.39.1
84
+ with:
85
+ upload: failure-only
86
+
87
+ # Rewrites each *.sarif under analyze's output directory, at any
88
+ # depth and skipping symlinks (as upload-sarif finds them), in
89
+ # place and logs one line per file, by its path under the
90
+ # directory, naming every result it dropped; malformed SARIF fails
91
+ # the job instead of uploading unfiltered results.
92
+ # Stdlib only, so the runner's own python3 runs it.
93
+ - name: Drop weak-hashing results on tests/ and scripts/
94
+ env:
95
+ SARIF_DIR: ${{ steps.analyze.outputs.sarif-output }}
96
+ run: python3 scripts/filter_codeql_sarif.py "$SARIF_DIR"
97
+
98
+ - uses: github/codeql-action/upload-sarif@9b3254085494b55a0f7c4c2b3d2d52cc712ce370 # v4.39.1
99
+ with:
100
+ sarif_file: ${{ steps.analyze.outputs.sarif-output }}
@@ -38,7 +38,7 @@ jobs:
38
38
  runs-on: ubuntu-latest
39
39
  steps:
40
40
  - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
41
- - uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0
41
+ - uses: astral-sh/setup-uv@07a047e608db84b3d8c06cd9a7c7dcc9c355c18e # v10.3.2
42
42
  with:
43
43
  python-version: "3.12"
44
44
  # Keyed by run id with a prefix restore-key: a fixed key would
@@ -65,7 +65,7 @@ jobs:
65
65
  # repo write access to re-run the job locally.
66
66
  - name: Upload the failing example database
67
67
  if: failure()
68
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
68
+ uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
69
69
  with:
70
70
  name: hypothesis-nightly-failure-${{ github.run_id }}
71
71
  path: .hypothesis
@@ -85,7 +85,7 @@ jobs:
85
85
  runs-on: ubuntu-latest
86
86
  steps:
87
87
  - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
88
- - uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0
88
+ - uses: astral-sh/setup-uv@07a047e608db84b3d8c06cd9a7c7dcc9c355c18e # v10.3.2
89
89
  with:
90
90
  python-version: "3.12"
91
91
  - name: Run Atheris against decode_frame
@@ -98,7 +98,7 @@ jobs:
98
98
  python tests/fuzz/fuzz_http_decode.py -max_total_time=300
99
99
  - name: Upload crash artifacts
100
100
  if: failure()
101
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
101
+ uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
102
102
  with:
103
103
  name: atheris-crash-${{ github.run_id }}
104
104
  path: crash-*
@@ -30,7 +30,7 @@ jobs:
30
30
  runs-on: ubuntu-latest
31
31
  steps:
32
32
  - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
33
- - uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0
33
+ - uses: astral-sh/setup-uv@07a047e608db84b3d8c06cd9a7c7dcc9c355c18e # v10.3.2
34
34
  with:
35
35
  python-version: "3.12"
36
36
  # mutmut hashes each mutated file plus the tests that cover it and
@@ -104,7 +104,7 @@ jobs:
104
104
  # doesn't exist is silently skipped.
105
105
  - name: Upload mutmut results on failure
106
106
  if: failure()
107
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
107
+ uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
108
108
  with:
109
109
  name: mutmut-nightly-failure-${{ github.run_id }}
110
110
  path: |
@@ -35,7 +35,7 @@ jobs:
35
35
  contents: read
36
36
  steps:
37
37
  - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
38
- - uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0
38
+ - uses: astral-sh/setup-uv@07a047e608db84b3d8c06cd9a7c7dcc9c355c18e # v10.3.2
39
39
  - run: uv build
40
40
  # The QA ladder cannot catch a mistagged release: every check
41
41
  # passes because the code is fine and only the tag is wrong. Read
@@ -99,7 +99,7 @@ jobs:
99
99
  --pyproject pyproject.toml --mc-type library \
100
100
  -o "netprotocols-${GITHUB_REF_NAME#v}.cdx.json"
101
101
  - name: Upload release assets
102
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
102
+ uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
103
103
  with:
104
104
  name: release-assets
105
105
  path: |
@@ -123,7 +123,7 @@ jobs:
123
123
  steps:
124
124
  - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
125
125
  - name: Download release assets
126
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
126
+ uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
127
127
  with:
128
128
  name: release-assets
129
129
  # CHANGELOG.md is the single source of truth for release notes, so
@@ -48,7 +48,7 @@ jobs:
48
48
  # Upload the SARIF results as a workflow artifact, per the action's
49
49
  # standard setup.
50
50
  - name: Upload artifact
51
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
51
+ uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
52
52
  with:
53
53
  name: SARIF file
54
54
  path: results.sarif
@@ -56,6 +56,6 @@ jobs:
56
56
 
57
57
  # Upload the results to GitHub's code scanning dashboard.
58
58
  - name: Upload to code-scanning
59
- uses: github/codeql-action/upload-sarif@2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2 # v4.38.2
59
+ uses: github/codeql-action/upload-sarif@9b3254085494b55a0f7c4c2b3d2d52cc712ce370 # v4.39.1
60
60
  with:
61
61
  sarif_file: results.sarif
@@ -214,6 +214,42 @@ raises. Ethernet's zero padding reaches only a message with no entries,
214
214
  since one entry already fills the minimum frame. See
215
215
  [`layer7/rip.py`](src/netprotocols/layer7/rip.py)'s module docstring.
216
216
 
217
+ `NTP` is the same case: after its 48-byte header come NTPv4 extension
218
+ fields (RFC 7822) and a MAC, both optional, up to the end of the UDP
219
+ datagram, which nothing in the message bounds. It keeps the whole
220
+ buffer after the header, so `bytes(NTP.decode(x)) == x`, and its
221
+ accessors split it: extension fields until what is left has a MAC's
222
+ length (4, 20 or 24 bytes, the rule RFC 7822 §3's minimum field length
223
+ is there for), then the MAC, and anything else as `remainder`. A
224
+ 4-byte Ethernet FCS after a plain message has a crypto-NAK's length,
225
+ and only the UDP length could tell them apart. Past the header
226
+ neither `decode()` nor any accessor raises, and Ethernet's padding
227
+ never reaches it: the header alone fills the minimum frame. See
228
+ [`layer7/ntp.py`](src/netprotocols/layer7/ntp.py)'s module docstring.
229
+
230
+ `TFTP` has no length field either, but most of its messages bound
231
+ themselves: an ACK is 4 bytes, an ERROR ends at the zero byte after its
232
+ message, and a request or an OACK after its last whole option pair, a
233
+ zero byte where the next name would start ending the options. So it
234
+ obeys rule 3 like a header with a length field,
235
+ `bytes(TFTP.decode(x)) == x[:header_len]`, and leaves Ethernet's
236
+ padding (which every ACK, at 4 bytes, gets in an untagged frame over
237
+ IPv4 with no options) and an FCS undecoded, except that a trailer
238
+ holding a zero byte can be read in, which only the UDP length could
239
+ rule out (#235): after a name with no value, the trailer's first zero
240
+ byte ends that value, so padding decodes `("tsize", "")` and takes one
241
+ byte, and after whole pairs an FCS that holds a name, a zero byte, a
242
+ value and a zero byte, such as `12 00 34 00`, reads as one more pair. A
243
+ DATA message cannot bound itself at all: its data has no terminator, so
244
+ it runs to the end of the buffer, and a trailer joins it, as it joins
245
+ an ICMP `body`. Its
246
+ strings are terminated rather than length-prefixed, so a missing
247
+ terminator is TFTP's truncation case: a filename, mode or error message
248
+ with no zero byte after it raises `TruncatedHeaderError`, as a lying
249
+ length does in rule 2, while an option pair cut short is left undecoded,
250
+ since only UDP's length could tell it from a trailer. See
251
+ [`layer7/tftp.py`](src/netprotocols/layer7/tftp.py)'s module docstring.
252
+
217
253
  `VRRP` has no length field either, but needs no walk: its version and
218
254
  address count give its exact length (8 bytes, the addresses, and
219
255
  VRRPv2's 8 bytes of authentication data), so it obeys rule 3 like a
@@ -363,12 +399,15 @@ unless the Security Association uses a confidentiality-free transform
363
399
  (e.g. NULL, RFC 2410, or AES-GMAC, RFC 4543), and at an offset only
364
400
  the SA determines, so the decoder never reads it).
365
401
 
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
402
+ `BGP`, `STUN`, `MPLS` and `LLC` are the four exceptions: they answer
403
+ from decode context rather than from a wire field alone. A BGP message
368
404
  carries no payload: a TCP segment can hold several messages back to
369
405
  back, each decoded as its own layer, so `BGP.next_protocol()` names the
370
406
  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
407
+ begin another one, `None` when they do not. STUN over TCP is the same
408
+ case, and `STUN.next_protocol()` answers `STUN` the same way, when the
409
+ 8 bytes after the message begin like a message's header, its magic
410
+ cookie in place. An MPLS label stack carries
372
411
  a packet but no field naming its protocol (see *MPLS: dispatch by
373
412
  convention* below), so `MPLS.next_protocol()` infers one, partly from
374
413
  the first byte after the stack. SAP 0x42 carries GARP's PDUs (Protocol
@@ -382,11 +421,12 @@ records what it saw on the instance in a field kept out of the
382
421
  dataclass's comparison and hash (`compare=False`, the mechanism `HTTP`
383
422
  uses for its `_message` parse cache). Two equal headers compare equal
384
423
  however they were found, and can still chain differently. The rules of
385
- the decode contract hold for all three unchanged; see *Port-based
424
+ the decode contract hold for all four unchanged; see *Port-based
386
425
  dispatch is best-effort* below,
387
426
  [`_base.py`](src/netprotocols/_base.py)'s *Decode context in
388
427
  next_protocol*, the module docstrings of
389
- [`layer7/bgp.py`](src/netprotocols/layer7/bgp.py) and
428
+ [`layer7/bgp.py`](src/netprotocols/layer7/bgp.py),
429
+ [`layer7/stun.py`](src/netprotocols/layer7/stun.py) and
390
430
  [`layer2/mpls.py`](src/netprotocols/layer2/mpls.py), and
391
431
  [`layer2/llc.py`](src/netprotocols/layer2/llc.py)'s *DSAP 0x42*.
392
432
 
@@ -453,8 +493,15 @@ flowchart LR
453
493
  U -->|4789| VX[VXLAN]
454
494
  VX -->|always| E
455
495
  U -->|520| R[RIP]
496
+ U -->|69, the request only| TF[TFTP]
497
+ U -->|1812, 1813| RAD[RADIUS]
498
+ U -->|123| NT[NTP]
499
+ U -->|1985| HS[HSRP]
456
500
  T -->|179| BGP[BGP message]
457
501
  BGP -->|another message in the segment| BGP
502
+ U -->|3478| STUN[STUN message]
503
+ T -->|3478| STUN
504
+ STUN -->|another message in the segment| STUN
458
505
  ```
459
506
 
460
507
  IPv6 extension headers (Hop-by-Hop Options, Routing, Fragment,
@@ -722,7 +769,7 @@ header cannot reject more than it does: RFC 7348 says to ignore its
722
769
  reserved bits, so rejecting those would refuse real traffic, such as
723
770
  the Group Based Policy extension's.
724
771
 
725
- RIP on UDP 520 rejects the most, because two of its four header bytes
772
+ RIP on UDP 520 rejects far more, because two of its four header bytes
726
773
  have values no RIP message carries: `RIP.decode()` requires version 1
727
774
  or 2 and a nonzero command, which 510 of the 65,536 two-byte prefixes
728
775
  satisfy, so about 99.2% of random datagrams are rejected. Measured the
@@ -736,6 +783,112 @@ does a RIPv1 message with nonzero must-be-zero fields, which RFC 1058
736
783
  tells a receiver to ignore but a decoder should show. Past the header,
737
784
  any 20 bytes are an entry.
738
785
 
786
+ RADIUS on UDP 1812 and 1813 (RFC 2865, RFC 2866) rejects a little
787
+ more again, through its Length alone: `RADIUS.decode()` requires at least
788
+ the 20 bytes of its header (`InvalidFieldError` below that) and no more
789
+ than the buffer holds (`TruncatedHeaderError` past it). A random
790
+ 300-byte payload therefore passes only when its third and fourth bytes
791
+ read 20-300, 281 of 65,536 values, and one of n bytes with n - 19 of
792
+ them, which over lengths drawn uniformly from 0-600 averages 281 too.
793
+ Measured the same way (`random.Random` seeds 0-2), the walk stopped
794
+ for about 99.6% in each case (99.57% expected): the 300-byte payloads
795
+ and the 0-600 byte ones, padded or not, on either port. The Code is
796
+ not checked: every value decodes, and the codes of RFC 2865, RFC 2866,
797
+ RFC 5997 and RFC 5176 are named (IANA assigns others, such as 6-10,
798
+ 21-34 and 50-52, which are not). Nor are the attributes: a packet
799
+ whose attribute list does not walk still decodes, and
800
+ `parsed_attributes` raises when asked, so an accepted guess can still
801
+ read as garbage. Bytes past the Length are padding (RFC 2865 §3) and
802
+ stay undecoded, past `packet.consumed`. The walk does not yet end the
803
+ buffer at UDP's length (#235), though, so a Length that overstates the
804
+ datagram reaches into whatever follows it in the frame, such as an
805
+ Ethernet FCS, and reads it as attributes instead of raising. Only 1812
806
+ and 1813 are registered: the legacy 1645 and 1646, and Dynamic
807
+ Authorization's 3799 (RFC 5176, whose Disconnect and CoA packets share
808
+ the format), need `decode_as`.
809
+
810
+ TFTP on UDP 69 (RFC 1350) rejects more: `TFTP.decode()` requires an
811
+ opcode of 1-6, which 6 of the 65,536 two-byte prefixes are, and then the
812
+ mandatory fields that opcode carries, whole. Measured the same way, over
813
+ three seeds, the walk stopped for 99.98-100% of the 300-byte payloads
814
+ sent to 69 and of the 0-600 byte ones unpadded and padded: about 5 of
815
+ the 90,000 decoded a `TFTP` layer, a count small enough to vary by a few
816
+ between runs. The opcode alone lets through 6 in 65,536, about 8 of
817
+ 90,000; of those, a DATA, ACK or OACK almost always decodes, but an RRQ
818
+ or WRQ needs two zero bytes after its opcode and an ERROR one after its
819
+ code, which random bytes lack about two thirds and one third of the
820
+ time, so about 6 are expected. What the port cannot do is find the
821
+ transfer. Only the request goes to port 69: the server answers from an
822
+ ephemeral port of its own, its transfer identifier, and every DATA, ACK,
823
+ OACK and ERROR after that runs between two ephemeral ports (RFC 1350
824
+ §4), which no port table can name. So a stateless walk decodes a `TFTP`
825
+ layer for the RRQ or WRQ alone, and stops at UDP for the rest of the
826
+ transfer, as for any datagram between unregistered ports; of the 33
827
+ frames in `tests/fixtures/tftp.pcap`, the 6 requests decode one. A
828
+ caller who follows the transfer has the server's port from the source
829
+ port of its first reply, and names it for the walk,
830
+ `decode_as={"udp.port": {tid: TFTP}}`, which covers both directions
831
+ since every later datagram has it as one of its two ports; tracking the
832
+ transfer is the caller's, as reassembly is for `HTTP` and `BGP`. A
833
+ server that answers from port 69 itself, which RFC 1350 does not allow,
834
+ has its replies dispatched by the source-port fallback.
835
+
836
+ NTP on UDP 123 (RFC 5905) rejects less: of the first byte's 256
837
+ values, 96 are a time message, a version from 1 to 4 and a mode other
838
+ than 6 or 7, the control messages (RFC 9327's mode 6, ntpd's private
839
+ mode 7) whose headers `NTP.decode()` does not read; anything shorter
840
+ than the 48-byte header is rejected too. Measured the same way, the
841
+ walk stopped for 61.2-62.9% of the 300-byte payloads (`random.Random`
842
+ seeds 0-2), against the 62.5% the first byte predicts, and for
843
+ 65.5-66.3% of the 0-600 byte ones, padded or not: padding never makes
844
+ a payload 48 bytes long. Past the first byte any 47 bytes are a
845
+ header, and the stratum, the Reference ID and the timestamps are not
846
+ judged, so a Kiss-o'-Death and a reserved stratum decode.
847
+
848
+ The first byte is judged before the length, so every control message
849
+ raises `InvalidFieldError` with `field` `"mode"`, however long, and a
850
+ bad version raises with `field` `"version"`; only a time message's
851
+ first byte, or an empty buffer, can end in `TruncatedHeaderError`.
852
+ That makes real traffic fail a strict walk: `ntpq` (mode 6, a 12-byte
853
+ request and longer responses) and `ntpdc` (mode 7) talk to port 123,
854
+ and `decode_frame()` raises on every one of their messages. Walk them
855
+ with `lax=True`, which keeps Ethernet, IP and UDP and reports the
856
+ rejection as `stopped_by`. A typed way to take a port out of dispatch for one
857
+ walk, and decoding mode 6, are planned follow-ups.
858
+
859
+ HSRP on UDP 1985 (RFC 2281) rejects far more, though less than BGP and
860
+ STUN below: its first three bytes must be a Version of 0, one of the
861
+ three Op Codes (Hello, Coup, Resign) and one of the six States, which
862
+ 18 of the 2^24 values of those bytes are, about one in 932,000, and
863
+ the payload must then hold the whole 20-byte message. Measured the
864
+ same way (`random.Random` seeds 0-2, each 10,000 random 300-byte
865
+ payloads and 10,000 of uniformly random length 0-600, the latter
866
+ walked both unpadded and padded: 90,000 walks to port 1985 in all),
867
+ about 0.1 of those walks are expected to decode an HSRP layer
868
+ (90,000 x 18 / 2^24 = 0.097, a little less in fact, since a few of the
869
+ random-length payloads are too short to reach 20 bytes), and 2 did:
870
+ the same 46-byte payload of seed 2, whose first
871
+ bytes, `00 00 04`, are a Hello in Speak, once unpadded and once padded.
872
+ The other 89,998 ended with `stopped_by` set. Padding cannot make a message,
873
+ since a minimum-size frame leaves 18 bytes after the UDP header. With
874
+ the Version forced to 0,
875
+ `HSRP.decode()` accepted 31 of 100,000 random 300-byte payloads
876
+ (`random.Random(0)`), near the 27 expected. Past those three bytes any
877
+ 17 are a message: the timers, the Priority, the Group, the Reserved
878
+ byte, the Authentication Data and the Virtual IP Address are not
879
+ judged.
880
+
881
+ The guard is also what keeps HSRPv2 (Cisco's TLV-based successor,
882
+ sent to 224.0.0.102 on the same port) from being read as HSRPv1: an
883
+ HSRPv2 message starts with a TLV type, 1, where HSRPv1 has its
884
+ Version, so `HSRP.decode()` rejects every one at `version`, and it
885
+ rejects Cisco's Op Code 3 interface-state Advertise, version 0 but
886
+ laid out as a TLV, at `opcode`. Each byte is judged before the length,
887
+ so either is reported as what it is however short. Neither is decoded,
888
+ and as with NTP's control messages a strict walk raises on them: walk
889
+ such traffic with `lax=True`. Only 1985 is registered; HSRPv2 over
890
+ IPv6 uses port 2029, which nothing here decodes.
891
+
739
892
  BGP on TCP 179 (RFC 4271) has the strongest guard of any port entry:
740
893
  every message opens with a 16-byte Marker of all ones, which random
741
894
  bytes match once in 2^128, and then a Length and Type whose bounds
@@ -770,6 +923,32 @@ offload off, carries 7. Reassembling
770
923
  the stream is the caller's, as it is for `HTTP` below: see
771
924
  [`layer7/bgp.py`](src/netprotocols/layer7/bgp.py)'s module docstring.
772
925
 
926
+ STUN on UDP and TCP 3478 (RFC 8489, RFC 5389) is guarded nearly as
927
+ well, by its fixed header: the two bits above the Message Type must be
928
+ zero, the 32-bit Magic Cookie `0x2112A442` must follow the Message
929
+ Length, and the Length must be a multiple of 4, so random bytes pass
930
+ once in 2^36, before the Length is held against the buffer. Measured
931
+ the same way, every walk to port 3478 ended with `stopped_by` set and
932
+ none decoded a STUN layer, over UDP for 10,000 random 300-byte payloads
933
+ and 10,000 of uniformly random length 0-600, padded or not, and over
934
+ TCP for 10,000 random 300-byte segments (`random.Random` seeds 0-2).
935
+ With the cookie forced in place, `STUN.decode()` accepted 28 of
936
+ 100,000 random 300-byte payloads (`random.Random(0)`), near the 27
937
+ expected: a quarter have the two top bits clear, and 71 of the 65,536
938
+ Lengths are multiples of 4 that fit in the 280 bytes after the header.
939
+ The cookie is also what separates STUN from RFC 3489, its first
940
+ version, whose 16-byte transaction ID took the cookie's place:
941
+ `STUN.decode()` rejects a classic message at `magic_cookie`, which is
942
+ what RFC 5389 put the cookie there for. Over TCP, messages follow one
943
+ another with no framing but their own Length, as BGP's do, and chain
944
+ the same way, so `max_depth` bounds correctness here too: a message
945
+ can be a bare 20-byte header, the default leaves room for 29 after
946
+ Ethernet, IPv4 and TCP, and a 1448-byte segment holds 72, so a strict
947
+ walk of a valid segment can raise `MaxDepthExceededError` and a lax
948
+ one stop short. BGP's two ways past it work the same, with
949
+ `start=STUN`; the corpus's TCP segments carry 2. See
950
+ [`layer7/stun.py`](src/netprotocols/layer7/stun.py)'s module docstring.
951
+
773
952
  A rejected guess takes the ordinary decode-error path: the chain keeps
774
953
  the layers it did decode and records the failure. An accepted one
775
954
  yields a layer that decoded cleanly and is wrong, and nothing in the
@@ -842,6 +1021,16 @@ one transport that does not use the internet checksum: its CRC32c
842
1021
  (`crc32c()`, RFC 9260 Appendix A) covers the whole packet with no
843
1022
  pseudo-header, and goes on the wire least significant byte first, so
844
1023
  `compute()` returns it byte-swapped to match the field as decoded.
1024
+ STUN's FINGERPRINT is a checksum that lives in an attribute rather
1025
+ than a header field: the IEEE CRC-32 of the message up to it, XORed
1026
+ with `0x5354554E` (RFC 8489 §14.7). `compute()` returns it for a
1027
+ message whose last attribute is one, `verify()` accepts a message with
1028
+ none, and `with_checksums()` rewrites it. A message has none only when
1029
+ its attributes walk whole, none of them a FINGERPRINT, and its last 8
1030
+ bytes do not begin like one, so a corrupted earlier Length cannot hide
1031
+ it from `verify()`. MESSAGE-INTEGRITY, the keyed
1032
+ HMAC beside it, needs the credential's key, which this library never
1033
+ has, so it is carried raw, like an AH ICV.
845
1034
  VRRP's checksum is the internet checksum of the message alone in
846
1035
  VRRPv2 and behind the IP pseudo-header in VRRPv3. RFC 5798 §5.2.8
847
1036
  cites only IPv6's pseudo-header; adding IPv4's too is keepalived's
@@ -849,6 +1038,20 @@ default, and `compute()`'s. RFC 9568, which obsoletes RFC 5798, leaves
849
1038
  it out over IPv4, and `verify()` returns `False` for an advert
850
1039
  checksummed that way.
851
1040
 
1041
+ Behind an IPv6 Routing header with segments left, RFC 8200 §8.1 puts
1042
+ the route's *final* destination in the pseudo-header, while `ip.dst`
1043
+ is the next hop. `compute()`, `verify()` and `flow_key()` take the
1044
+ Routing header as a keyword-only `routing`, and `Packet.with_checksums()`
1045
+ and `Packet.flow_key()` pass the last one with segments left between
1046
+ the nearest IP header and the transport (a receiver skips one with
1047
+ none left: RFC 8200 §4.4 for unrecognized types, and each type's own
1048
+ processing rules, RFC 2460 §4.4, RFC 6554 §4.2 and RFC 8754 §4.3, so
1049
+ the route ends at the last that has some).
1050
+ `IPv6Routing.final_destination(dst)` resolves it for types 0, 2, 3
1051
+ (RPL, from `dst`'s prefix) and 4 (SRH, Segment List[0]); for another
1052
+ type, malformed data, no segments left or IPv4 the address stays
1053
+ `ip.dst`.
1054
+
852
1055
  That is also why `SCTP.decode()` bounds its own extent (see "The decode
853
1056
  contract" above): otherwise Ethernet's padding would land inside the
854
1057
  CRC's span, and a packet under 46 bytes from a real NIC would fail to
@@ -879,15 +1082,17 @@ src/netprotocols/
879
1082
  ├── __init__.py public API re-exports, __version__, registry install
880
1083
  ├── _base.py Protocol ABC, decode contract, address helpers
881
1084
  ├── _tlv.py walk_kind_length_value(): shared TCP/IPv4/IPv6
882
- │ options and PIM Hello options TLV walker
1085
+ │ options, PIM Hello options and STUN attributes
1086
+ │ TLV walker
883
1087
  ├── _enums.py EtherType, IPProtocol, ARPOperation (imports nothing)
884
1088
  ├── registry.py public dispatch tables: register(), Registry
885
1089
  ├── walk.py decode_frame(): the shipped chain walker
886
1090
  ├── _defaults.py the built-in decoder map, installed at import
887
1091
  ├── packet.py Packet composition, with_checksums(), flow_key()
888
1092
  ├── checksum.py RFC 1071 internet_checksum, SCTP's crc32c,
889
- │ compute, verify (CDP's odd-byte sum among them),
890
- │ OSPF's LSA Fletcher checksum
1093
+ │ compute, verify (CDP's odd-byte sum and STUN's
1094
+ │ FINGERPRINT among them), OSPF's LSA Fletcher
1095
+ │ checksum
891
1096
  ├── flow.py FlowKey, flow_key(): canonical bidirectional keys
892
1097
  ├── pcap.py read_captures()/read_pcap()/read_pcapng(): classic
893
1098
  │ pcap and pcapng readers, from bytes not filenames
@@ -909,7 +1114,12 @@ src/netprotocols/
909
1114
  │ dispatch)
910
1115
  ├── layer7/ dns.py, dhcp.py, http.py (the HTTP/1.1 grammar
911
1116
  │ and the HTTP codec class), vxlan.py, rip.py
912
- │ (RIPv1 + RIPv2), bgp.py (BGP-4 messages)
1117
+ │ (RIPv1 + RIPv2), bgp.py (BGP-4 messages),
1118
+ │ tftp.py (TFTP, with RFC 2347 options),
1119
+ │ radius.py (RADIUS + RADIUSAttribute),
1120
+ │ stun.py (STUN messages, over UDP and TCP),
1121
+ │ ntp.py (NTPv1-v4 time messages),
1122
+ │ hsrp.py (HSRPv1 Hello, Coup and Resign)
913
1123
  └── utils/ validators (mac.py, ipv4.py), exceptions.py
914
1124
  tests/ one file per protocol + test_contract.py
915
1125
  (truncation, lying lengths, chain walks),